켄트백이 만든 테스트 주도 개발(Test Driven Development)을 뜻한다. 개발 전 테스트 코드를 작성한 후, 테스트코드를 통과하기위한 기능 코드를 작성한다.
기본 사이클은 레드, 그린, 리팩터링이다. -. 레드 : 실패하는 테스트를 작성한다 -. 그린 : 실패 테스트를 성공하기 위한 코드를 작성한다. -. 리팩터링 : 코드를 예쁘게 정리한다.
○ TDD는 왜 사용하는가? -. 테스트를 우선 작성하면 작성하고자하는 요구사항에 대한 분석과 이해가 필요하므로 시스템 전반적인 설계가 향상됨 -. 버그가 줄어들며 고치기 쉬운 코드가 됨 -. 필요한만큼만 코딩하며 오버엔지니어링을 방지 -. 복잡함이 덜해지며 코드퀄리티가 향상됨
○ TDD의 종류 - 유닛테스트 : 프로그램의 구성요소가 제대로 움직이는가 - 통합테스트 : 복수의 구성요소가 제대로 움직이는가 - 리그레션 테스트 : 변경이 일어났을 때 원래 기능에 변화가 없는가 - 도입 테스트 : 실제 환경에서의 테스트
//튜플구조
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);
}
}