3. TDD 란?
Test Driven Development
테스트가 개발을 운전(Driven)한다.
Programmer Test
프로그래머가 직접 설치하는 자동화된 테스트
White Box Test
QA 팀의 테스트는 Black Box Test
4. 테스트 실패
TDD의 순환
과정 불과 몇 분밖에 걸
리지 않는다.
테스트
코드 작성
작성
TEST (ShieldLevelStartsFull){
TEST (ShieldLevelStartsFull){
Shield shield;
Shield shield;
CHECK_EQUAL (Shield::kMaxLevel, shield.GetLevel());
CHECK_EQUAL (Shield::kMaxLevel, shield.GetLevel());
}
}
Shield::Shield() : m_level (Shield::kMaxLevel){
체크 인
Shield::Shield() : m_level (Shield::kMaxLevel){
}
}
체크 인
리팩토링
테스트 통과
테스트 통과
6. 피보나치 수열 시연
피보나치 수열
0, 1, 1, 2, 3, 5, 8, 13, 21......의 형태의 수
열. 즉, 첫 번째 항의 값은 0 이고 두 번째 항
의 값은 1일 때 이후의 항들은 이전의 두 항
을 더한 값으로 만들어지는 수열을 말한다.
수열의 공식은 다음과 같다. fn = fn-1 + fn-
2 (단, f0 = 0, f1 = 1, n = 2, 3, 4, ....)
7. 피보나치 수열 1
fn = fn-1 + fn-2
(단, f0 = 0, f1 = 1, n = 2, 3, 4, ....)
재귀호출을 이용
10. UnitTest++ 기능 1/3
FIXTURE
TEST_FIXTURE
JUnit 의 setUp, tearDown 과 같은 역할
예 : DB 테스트
TEST(DBTest) {{ TEST(DBTest1) {{
TEST(DBTest) TEST(DBTest1)
SQL sql; SQL sql;
SQL sql; SQL sql;
sql.connect(); sql.connect();
sql.connect(); sql.connect();
// 실제 테스트 코드 // 실제 테스트 코드
// 실제 테스트 코드 // 실제 테스트 코드
sql.close(); sql.close();
sql.close(); sql.close();
}} }}
struct FixtureSQL {
FixtureSQL() { sql.connect(); }
~FixtureSQL() { sql.close() }
SQL sql;
};
TEST_FIXTURE (FixtureSQL, DBTest) {
// sql.xxx 실제 테스트
11. UnitTest++ 기능 2/3
TimeConstraint
실행 시간이 일정 이상 지나면
테스트 fail 로 간주.
TestResult r;
TimeConstraint t(10, result, TestDetails(“”, “”, “”, 0);
TimeHelpers::SleepMs(20);
CHECK_EQUAL(1, result.GetFailureCount());
Crash 검사
12. UnitTest++ 기능 3/3
Suite
Two Stage Test
1단계
리소스 로딩 이전에
로직 테스트, 순수한 의미의 UnitTest
2단계
리소스 로딩 후에
월드 지형 버그, 스킬, 퀘스트 등 데이터 로딩이 필요한 테스트
지형의 이동 가능 여부 등
성능 테스트
같은 함수를 100만번 부를 때 0.01초 내에 리턴되는지 검사
매번 검사하기 부담스러우므로 command 명령으로 가끔씩 수동
으로 테스트하기.
17. 리니지2
리니지2 업데이트 일지
CHRONICLE 01 - 전란을 부르는 자들
CHRONICLE 02 - 풍요의 시대
CHRONICLE 03 - 눈뜨는 어둠
CHRONICLE 04 - 운명의 계승자들
CHRONICLE 05 - Death of Blood
혼돈의 왕좌 Interlude - 그 시작을 말하다
혼돈의 왕좌 - The kamael (2007)
계속되는 업데이트 & 변경되는 기획
18. 왜 개발자가 Test 까지?
QA 팀이 있으신가요?
없는 회사가 대부분
QA 팀이 있어도
최고의 QA 팀이 있어도 버그는 막을 수 없다.
Lineage2 팀의 QA 팀은 최고입니다.
마감직전에 발견되는 버그가 가장 큰 문제를 일으킨다.
결국 욕은 프로그래머가 먹고,
야근도 해야 한다. 미리 Test를 이용, 버그를 막아보자.
버그가 생기면
수익 감소
악플뿐 아니라 웹진기사가 뜨는 경우까지!
19. QA 팀은 역시 필요합니다.
수동
자동
사용성 테스팅
스토리 테스트
비즈니스 의도 탐색적 테스팅
(제품 설계)
단위 테스트 특성 테스팅
개발자 의도 보안 테스팅
부하 테스팅
(코드 설계) 조합 테스팅
…
자동 도구
20. Test Driven Debugging?
일반적인 디버깅 방법은?
1. 버그 리포트 시스템에 새로운 버그 추가
2. 게임 스크립트 데이타 받아서 컴파일
3. 서버들 빌드 후 loading
1. 여기까지 5~10분은 걸림.
4. 클라이언트 1개~3개 실행
1. 역시나 3분 이상 소모됨
5. 재현
1. 재현하기 힘든 경우라면?
2. 혈맹 전쟁을 테스트하려면? 혈원 15명 이상이 접속
해야 테스트 가능
6. 코드 수정
7. 3번으로 돌아가서 확인
21. Test Driven Debugging!!
TDD 를 이용할 때
디버그 관리자에 새로운 버그 추가
1.
게임 스크립트 데이타 받아서 컴파일
2.
서버들 빌드 후 loading
3.
여기까지 5~10분은 걸림.
1.
스크립트 없이 테스트 할 수 있는 경우가 많음.
2.
클라이언트 1개~3개 실행
4.
역시나 3분 이상 소모됨
1.
클라이언트 없이 실행 가능.
2.
재현
5.
재현하기 힘든 경우라면?
1.
혈맹 전쟁을 테스트하려면? 혈원 15명 이상이 접속해야 테스트 가능
2.
직접 확률을 지정하거나, 코드에서 loop 돌릴 수 있다.
3.
코드 수정
6.
3번으로 돌아가서 확인
7.
한 번 만들어진 테스트는 계속 남는다.
8.
22. Regression Test
변경되지 않은 기능은 ‘예전과 동일하게 동
작함’을 보장하는 테스트
Characterization Test
현재 상태를 그대로 테스트로 추가
CPlayer* pMe = ...;
CHECK_EQUAL(0, pMe->GetLife()); // Test Failed
CHECK_EQUAL(644, pMe->GetLife()); // Test 성공
리펙토링을 하기 전 필수적인 작업
일종의 TLP(Test Last Programming)
23. Regression Test
2년 전의 전투 관련 서버 코드가 어떻게 돌아
가는지 보고 싶다면
2년 전 Server 소스 snapshot 받아서 빌드
같은 날의 Client 소스 snapshot 받아서 빌드
같은 날의 게임 스크립트 데이타 로딩
DB 스키마 셋팅
등등등...
24. Regression Test in TDD
2년 전에 전투 관련 서버 코드가 어떻게 돌아
가는지 보고 싶다면
2년 전 Server 소스 snapshot 받아서 빌드
같은 날의 Client 소스 snapshot 받아서 빌드
같은 날의 게임 스크립트 데이타 로딩
DB 스키마 셋팅
등등등...
심지어 예전 코드가 어떻게 실행되는지를 직
접 Break Point 잡고 Trace 할 수 있다.
25. Branch & Merge
Branch 후 Merge 작업
Merge 하면서 다른 팀원이 바꾸어 놓은 코드
때문에 버그 발생
1차적으로는 지속적인 통합을 권장
2차적으로는 UnitTest 를 통해서 다른 팀원들에게
지켜야 할 가이드라인을 제시
26. Working Effectively
with Legacy Code
Seams
Sprout Method / Class
Breaking Dependencies
Interception Points
Pinch Point Traps
Targeted Testing
Sensing Variable
Construction Test
Hack Points
28. TDD Tips 1
가장 쉽게 만들 수 있는 것부터 테스트에 추가한다.
Multithread 테스트는 포기한다.
#if defined(UnitTestDefined) && defined(_DEBUG)
팀원들을 안심시켜라.
Release 빌드에서는
file 에서 오른쪽 버튼 -> general 탭 에서 exclude file
from build
테스트를 빠르게 유지
Disk I/O 를 최소화한다.
스크립트, Database dependency 를 최소화 할 수 있다.
29. TDD Tips 2
기존 코드에 테스트 추가하기
test 없는 private 보다 test 있는 public 이 안전
멤버변수도 parameter 로 넘기면 test 만들기 쉬
워진다.
마찬가지로 전역변수도 parameter 로 넘겨주자.
이제 아예 static 멤버함수로 만들자.
좀 더 쉽게 테스트를 만들 수 있다.
30. TDD Tips 3
breakpoint -> trace 는 대신
필요한 곳에 CHECK 테스트를 추가한다.
임의성 테스트
Windows 프로그램에서 콘솔 띄우기
TDD 돌릴 것인지 여부를 설정파일로 결정
주의!
직접 테스트도 병행해야 한다.
31. 임의성 테스트
타격 크리티컬 같이 random 값이 들어가는 계산은
어떻게 테스트 할 수 있을까?
int GetRand() const {
#if defined(_DEBUG) && defined(UnitTestDefined)
if (bSettedRandomValue) {
return MyTestUnit ::Inst().m_Random;
}
#endif
return ::rand();
}
TEST_FIXTURE(FixtureUser2, CheckMagicCritical){
int playerLevel = 60;
const double bonus = 50.0;
MyTestUnit ::Inst().m_Random = 100.0; // 무조건 성공시키겠다.
CHECK_EQUAL(true, IsAttackCritical(player, playerLevel, bonus));
MyTestUnit ::Inst().m_Random = 0.0; // 무조건 실패시키겠다.
CHECK_EQUAL(false, IsAttackCritical(...));
38. 테스트 - 일반원칙
망가질 가능성이 있는 모든 것을 테스트한다.
망가지는 모든 것을 테스트한다.
새 코드는 무죄가 증명되기 전까지는 유죄.
적어도 제품 코드만큼 테스트 코드를 작성한
다.
컴파일을 할 때마다 지역 테스트를 실행한다.
저장소에 체크인하기 전에 모든 테스트를 실
행해 본다.
39. 자문해 봐야 할 사항
이 코드가 옳게 동작한다면, 어떻게 그것을
알 수 있는가?
이것을 어떻게 테스트할 것인가?
'그밖에' 어떤 것이 잘못될 수 있는가?
이와 똑같은 종류의 문제가 다른 곳에서도 일
어날 수 있을까?
40. 무엇을 테스트해야 하는가
RIGHT-BICEP
Right : 결과가 옳은가?
Boundary : 모든 경계 조건이 CORRECT한가?
Inverse : 역관계를 확인할 수 있는가?
Cross-check : 다른 수단을 사용해서 결과를 교차
확인 할 수 있는가?
Error condition : 에러 조건을 강제로 만들어낼 수
있는가?
Performance : 성능 특성이 한도내에 있는가?
41. 좋은 테스트는 A-TRIP해야 한다.
Automatic(자동적)
Through(철저함)
Repeatable(반복 가능)
Independent(독립적)
Professional(전문적)
42. CORRECT 경계 조건
Conformance(형식 일치) : 값의 형식이 예상한 형식과 일치하는가?
Ordering(순서) : 적절히 순서대로 되어 있거나 그렇지 않은 값인가?
Range(범위) : 적당한 최소값과 최대값 사이에 있는 값인가?
Reference(참조) : 코드가 자기가 직접 제어하지 않는 외부 코드를
참조하는가?
Existence(존재성) : 값이 존재하는가?
Cardinality(개체 수) : 확실히 충분한 값이 존재하는가?
Time(시간) : 모든 것이 순서대로 일어나는가? 제시간에? 때맞추어?
출처 : 실용주의 프로그래머를 위한 단위 테스트 with JUnit
43. 테스트 기피를 위한 변명
시간이 오래 걸린다.
개발 초기에는 기획 변경이 잦아서 테스트를
만들어 봐야 소용없다.
44. 시간이 오래 걸린다 -> 맞습니다
2개월에서 1년까지는 시간이 더 걸립니다.
모 게임사의 XP 실패담.
테스트 코드가 2 만 라인이 안 되는 Product Code 보다 8배 정도 많음.
사람들이 #ifdef 로 테스트 코드를 무시하기 시작함.
다른 사람이 망가뜨린 테스트를 대신 고치는 일이 계속되면서 짜증 증가
CODE
45. 그러나!
서비스를 오래 하려면?
기획 경화 현상
이거 고쳤다가
잘못 되면 어쩔려고 그래요?
예전 구현을 손 대지
않으려고
땜빵식 구현/기획을
추가하면서
점점 더 고치기 힘들어짐.
버그/핵 에 대처 능력이
떨어지게 된다.
46. 기획이 자주 변경된다.
Fragile Test
지금 아니면 할 수 없습니다.
초반부터
테스트 코드를
추가하면,
더욱 더 단단한
코드를
얻을 수 있고,
신뢰할 수 있는
테스트 집합을
구축할 수 있다.
48. UnitTest 의 어려운 점
팀원들에게 같이 하자고 꼬시는 게 가장 어려움
왜 일을 더 해야 하는지(테스트 코딩)를 설득하기가 어려움
일부만 UnitTest 를 한다면
다른 팀원이 수정한 내용이 Test 를 실패시키는 바람에 갈등 유발
Mock 객체를 부주의하게 사용해서
UnitTest define 을 끈 채로 빌드하면 에러 발생!
비정상적인 로직이 동작하게 할 수 있음
기존 가정을 깨는 Seam Code 를 추가하는 도중에
없던 문제를 발생시킬 수 있음
테스트 코드는 제품코드가 아니라는 생각 때문에 막 코딩해 버림
테스트 코드 자체가 주체할 수 없게 됨
그럼에도 불구하고 지켜봐 주고 도와준 팀원들에게 감사!!!
49. 결론
테스트 프레임워크 구축은 쉽지 않다.
그러나 노력한 만큼 복리로 돌려받을 수 있다.
테스터의 입장에서 코드를 바라보게 된다.
(코드 품질이 향상되고, 좋은 버릇이 생긴다.)
모든 방법을 동원해서 테스트하라.
상상력이 필요합니다.
TDD 는 도구이지 목표가 아니다.
1900 년 초부터 UnitTest 는 시작되었습니다.
50. 참고자료
http://unittest-cpp.sourceforge.net/
UnitTest++ 소스 받는 곳
http://www.gamesfromwithin.com
Noel Llopis - llopis@convexhull.com
GDC2006 발표자료
http://andstudy.com/andwiki/wiki.php/BackwardsIsFo
rward
위 자료를 번역해 놓은 PPT 및 노트