○ TDD란?

켄트백이 만든 테스트 주도 개발(Test Driven Development)을 뜻한다.
개발 전 테스트 코드를 작성한 후, 테스트코드를 통과하기위한 기능 코드를 작성한다.


기본 사이클은 레드, 그린, 리팩터링이다.
-. 레드 : 실패하는 테스트를 작성한다
-. 그린 : 실패 테스트를 성공하기 위한 코드를 작성한다.
-. 리팩터링 : 코드를 예쁘게 정리한다.

○ TDD는 왜 사용하는가?
-. 테스트를 우선 작성하면 작성하고자하는 요구사항에 대한 분석과 이해가 필요하므로 시스템 전반적인 설계가 향상됨
-. 버그가 줄어들며 고치기 쉬운 코드가 됨
-. 필요한만큼만 코딩하며 오버엔지니어링을 방지
-. 복잡함이 덜해지며 코드퀄리티가 향상됨

○ TDD의 종류
- 유닛테스트 : 프로그램의 구성요소가 제대로 움직이는가
- 통합테스트 : 복수의 구성요소가 제대로 움직이는가
- 리그레션 테스트 : 변경이 일어났을 때 원래 기능에 변화가 없는가
- 도입 테스트 : 실제 환경에서의 테스트

데이터 테이블을 CSV 파일로 생성하는데

CSV 파일마다 헤더 확인하고 내용에 집어넣는걸 자동화 할 수 없을까 하는 생각이 들어

자동으로 헤더를 추출해 클래스에 집어넣도록 해서

재활용성을 올린 CSV 로드 파일을 만들어보았다.

 

Addressable과 UniTask를 사용하고있다.

 

 

1. 튜플 구조를 이용해 클래스의 멤버 변수들을 추출한다 

//튜플구조
var members = new List<(MemberInfo member, Type type)>();
foreach(var header in headers)
{
    //필드 : public int ID
    var field = typeof(T).GetField(header);
    if(field != null)
    {
        //튜플구조
        members.Add((field, field.FieldType));
        continue;
    }

    //프로퍼티 : public int ID { get; set; }
    var property = typeof(T).GetProperty(header);
    if(property != null && property.CanWrite)
    {
        //튜플구조
        members.Add((property, property.PropertyType));
        continue;
    }

    members.Add((null, null));
}

 

 

2. 튜플 구조분해를 이용해 멤버변수에 CSV 값을 대응해서 넣는다.

 

T classInstance = new T();

for(int memberIndex = 0; memberIndex < members.Count; memberIndex++)
{
    //튜플 구조분해
    var (member, type) = members[memberIndex];

    if(member == null) continue;

    var raw = values[memberIndex].Trim();
    var convertedRaw = ConvertTo(raw, type);

    if (member is FieldInfo fInfo)
        fInfo.SetValue(classInstance, convertedRaw);
    else if (member is PropertyInfo proInfo)
        proInfo.SetValue(classInstance, convertedRaw);
}

 

 

 

-. 이하 코드 전문

using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

public class DataTableReader
{
    /// <summary>
    /// DataTable 취득
    /// </summary>
    public async UniTask<List<T>> LoadDataTable<T>(string path) where T : new()
    {
        var result = new List<T>();
        var data = await AddressableLoader.Instance.LoadAssetAsync<TextAsset>(path);
        var csvText = data.text;

        var lines = csvText.Split(new[] { '\r', '\n' }, System.StringSplitOptions.RemoveEmptyEntries);

        var headers = lines[0].Split(',');

        //튜플구조
        var members = new List<(MemberInfo member, Type type)>();
        foreach(var header in headers)
        {
            //필드 : public int ID
            var field = typeof(T).GetField(header);
            if(field != null)
            {
                //튜플구조
                members.Add((field, field.FieldType));
                continue;
            }

            //프로퍼티 : public int ID { get; set; }
            var property = typeof(T).GetProperty(header);
            if(property != null && property.CanWrite)
            {
                //튜플구조
                members.Add((property, property.PropertyType));
                continue;
            }

            members.Add((null, null));
        }

        for(int lineIndex = 1; lineIndex < lines.Length; lineIndex++)
        {
            var values = lines[lineIndex].Split(",");
            if (values.Length == 0 || string.IsNullOrWhiteSpace(values[0]))
                continue;

            T classInstance = new T();

            for(int memberIndex = 0; memberIndex < members.Count; memberIndex++)
            {
                //튜플 구조분해
                var (member, type) = members[memberIndex];

                if(member == null) continue;

                var raw = values[memberIndex].Trim();
                var convertedRaw = ConvertTo(raw, type);

                if (member is FieldInfo fInfo)
                    fInfo.SetValue(classInstance, convertedRaw);
                else if (member is PropertyInfo proInfo)
                    proInfo.SetValue(classInstance, convertedRaw);
            }
            result.Add(classInstance);
        }
        return result;
    }

    private object ConvertTo(string value, Type type)
    {
        if (type == typeof(int)) return int.Parse(value);
        if (type == typeof(float)) return float.Parse(value);
        if (type == typeof(double)) return double.Parse(value);
        if (type == typeof(bool)) return bool.Parse(value.ToLower());
        if (type == typeof(string)) return value;
        if (type.IsEnum) return Enum.Parse(type, value);

        // nullable 지원
        var underlying = Nullable.GetUnderlyingType(type);
        if (underlying != null)
        {
            if (string.IsNullOrWhiteSpace(value)) return null;
            return Convert.ChangeType(value, underlying);
        }

        // 그 외 일반 타입
        return Convert.ChangeType(value, type);
    }
}

 

 

 

 

리눅스에서 서버를 돌릴 경우 크래시 덤프를 뜨기가 힘들다.

이번에는 윈도우와 리눅스 둘 다 사용이 가능한 breakpad로 리눅스 덤프를 뜨는 방법을 알아보고자 한다.

 

리눅스는 ubuntu를 사용한다.

 


1. 필요한 라이브러리 인스톨

breakpad에서 필요한 라이브러리들을 미리 인스톨한다.

 

①GIT

 

 

② DEPOT TOOLS

break pad를 사용하기 위해 선행적으로 받아야한다.

 

사이트는 아래와 같다.

https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up

 

 

1) git을 이용해 depot_tools를 받아온다

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

 

 

2) PATH 설정을 한다.

export PATH=/path/to/depot_tools:$PATH

여기에서 ['path/to']는 나의 PATH를 입력한다.

 

 

 

③ make / g++

 

sudo apt install make
sudo apt install g++

 

c++ 빌드 환경을 만들기위해 make와 g++을 설치해준다.

 

 

④ zlib1g-dev

 

sudo apt install zlib1g-dev

 

zlib1g dev버전으로 설치해준다.

 

 


2. BREAK PAD 설치

break pad의 홈페이지는 아래와 같다.

https://chromium.googlesource.com/breakpad/breakpad/

 

breakpad/breakpad - Git at Google

Breakpad Breakpad is a set of client and server components which implement a crash-reporting system. Getting started (from main) First, download depot_tools and ensure that they’re in your PATH.Create a new directory for checking out the source code (it

chromium.googlesource.com

 

 

 

git clone https://chromium.googlesource.com/breakpad/breakpad

 

git으로 breakpad를 clone해온다.

 

 

 

 

[

[Myproject]에 breakpad 폴더를 만든 후 fetch 해준다.

 

-. 폴더 만들기

mkdir breakpad
cd breakpad

 

-. fetch

fetch breakpad

 

 

 

 

fetch가 완료되었으면

src폴더로 이동해,

configure과 make를 이용해 breakpad를 빌드해준다

 

cd src
./configure
make

 

 


 

3. BREAK PAD사용

 

 

내부에서 null에러를 일으키는 임시 코드를 작성했다.

 

 

① exception handler import

 

② dump callback 작성

홈페이지에 있는 예시 그대로 작성하였다.

 

 

#include <iostream>
#include <thread>
#include "client/linux/handler/exception_handler.h"

static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
void* context, bool succeeded) {
  printf("Dump path: %s\n", descriptor.path());
  return succeeded;
}


void ThreadFunction(int* ptr)
{
        std::cout<<*ptr<<std::endl;
}

int main()
{
          google_breakpad::MinidumpDescriptor descriptor("/tmp");
          google_breakpad::ExceptionHandler eh(descriptor, NULL, dumpCallback, NULL, true, -1);
        //null PTR을 cout : nullptr에러를 일으킨다
        int* ptr = nullptr;
        std::thread threadTest(ThreadFunction, ptr);

        //쓰레드 종료시까지 대기
        threadTest.join();
        return 0;
}

 

③빌드

g++ -I/home/ubuntu/MyProject/breakpad/src/src BreakPadTest.cpp -o test.out -lbreakpad_client

 


 

4. 덤프 확인

 

크래시가 나면 덤프는 PATH설정한 곳으로 나오게 된다.

예시의 경우 /tmp에 덤프가 생성된다.

 

① 덤프 심볼릭 만들기

 

breakpad/src/src/tools/linux/dump_syms/dump_syms ./test.out > test.out.sym

 

덤프 심볼릭을 만든다.

이때 심볼릭은 빌드파일과 같은 이름으로 해야한다.

 

 

② 심볼릭 고유 코드 확인

 

head -n1 test.out.sym

head -n1을 이용하여 심볼릭이 어디를 나타내는지 확인한다

 

③ 심볼릭의 헤더와 같은 위치로 폴더 만들기

 

심볼릭의 헤더가 가리키는 위치에 폴더를 만든다.

그렇게 하지않으면 덤프에서 심볼릭을 찾을 수 없다.

mkdir -p ./symbols/test.out/C742B6BB5BEBFBC5F73889A0908E34610
mv test.out.sym ./symbols/test.out/C742B6BB5BEBFBC5F73889A0908E34610/

 

④ 크래시가 난 위치 확인

 

마지막으로,

breakpad/src/src/processor/minidump_stackwalk 68d64630-fceb-4e30-ba3ee3bc-e3aacc30.dmp ./symbols

 

를 이용하면 아래와 같은 결과가 나온다.

 

0 test.out의 ThreadFunction의 14번째 줄에서 크래시가 난 것을 알 수 있다.

 

 

 

정확히 크래시가 난 곳의 위치 확인이 가능하다.

 

 


윈도우에서는 visual studio에 덤프를 넣기만 해도 디버깅이 가능한 것으로 알고있다.

리눅스에서 뽑은 덤프도 visual studio에서 가능한지는 확인해볼 필요가 있어보인다.

+ Recent posts