unittest, tdd for games kgc2007 parkpd

53
TDD, UnitTest for games 박일(NcSoft. Lineage II) http:// http:// ParkPD.egloos.com ParkPD.egloos.com

Upload: ryan-park

Post on 19-May-2015

3.578 views

Category:

Documents


3 download

DESCRIPTION

KGC2007 presentation file about UnitTest, TDD applying in game programming

TRANSCRIPT

Page 1: UnitTest, Tdd For Games Kgc2007 ParkPD

TDD, UnitTest for games

박일(NcSoft. Lineage II)

http://http://ParkPD.egloos.comParkPD.egloos.com

Page 2: UnitTest, Tdd For Games Kgc2007 ParkPD

버그?

Page 3: UnitTest, Tdd For Games Kgc2007 ParkPD

TDD 란?

Test Driven Development

� 테스트가 개발을 운전(Driven)한다.

Programmer Test

� 프로그래머가 직접 설치하는 자동화된 테스트

White Box Test

� QA 팀의 테스트는 Black Box Test

Page 4: UnitTest, Tdd For Games Kgc2007 ParkPD

불과불과 몇몇 분밖에분밖에 걸걸리지리지 않는다않는다..

테스트테스트 실패실패

테스트테스트 통과통과테스트테스트 통과통과

체크 인

체크 인

TDD의 순환과정

TEST (ShieldLevelStartsFull){Shield shield;CHECK_EQUAL (Shield::kMaxLevel, shield.GetLevel());

}

TEST (ShieldLevelStartsFull){Shield shield;CHECK_EQUAL (Shield::kMaxLevel, shield.GetLevel());

}

Shield::Shield() : m_level (Shield::kMaxLevel){}

Shield::Shield() : m_level (Shield::kMaxLevel){}

테스트작성 코드 작성

리팩토링

Page 5: UnitTest, Tdd For Games Kgc2007 ParkPD

UnitTest++

개발자개발자개발자개발자 Noel Noel Noel Noel LlopisLlopisLlopisLlopis

� Senior ArchitectSenior ArchitectSenior ArchitectSenior Architect

� High Moon StudiosHigh Moon StudiosHigh Moon StudiosHigh Moon Studios

Page 6: UnitTest, Tdd For Games Kgc2007 ParkPD

피보나치 수열 시연

피보나치 수열

0, 1, 1, 2, 3, 5, 8, 13, 21......의 형태의 수열. 즉, 첫 번째 항의 값은 0 이고 두 번째 항의 값은 1일 때 이후의 항들은 이전의 두 항을 더한 값으로 만들어지는 수열을 말한다. 수열의 공식은 다음과 같다. fn = fn-1 + fn-2 (단, f0 = 0, f1 = 1, n = 2, 3, 4, ....)

Page 7: UnitTest, Tdd For Games Kgc2007 ParkPD

피보나치 수열 1

fn = fn-1 + fn-2 (단, f0 = 0, f1 = 1, n = 2, 3, 4, ....)

재귀호출을 이용

Page 8: UnitTest, Tdd For Games Kgc2007 ParkPD

피보나치 수열 2

Page 9: UnitTest, Tdd For Games Kgc2007 ParkPD

UnitTest++ 기능

TEST()� TEST(AfterUserConnectToServerOnline) {

CHECK()� CHECK(0 < a.GetHP())

CHECK_EQUAL()� CHECK_EQUAL(true, a.IsOnline());

CHECK_CLOSE()� CHECK_CLOSE(15.42, a.GetAttackFactor(), 0.01);

CHECK_ARRAY2D_CLOSE()

Page 10: UnitTest, Tdd For Games Kgc2007 ParkPD

UnitTest++ 기능 1/3

FIXTURE� TEST_FIXTURE� JUnit 의 setUp, tearDown 과 같은 역할� 예 : DB 테스트

� struct FixtureSQL {FixtureSQL() { sql.connect(); }~FixtureSQL() { sql.close() }SQL sql;

};TEST_FIXTURE (FixtureSQL, DBTest) {

// sql.xxx 실제 테스트

TEST(DBTest) {SQL sql;sql.connect();// 실제 테스트 코드sql.close();

}

TEST(DBTest) {SQL sql;sql.connect();// 실제 테스트 코드sql.close();

}

TEST(DBTest1) {SQL sql;sql.connect();// 실제 테스트 코드sql.close();

}

TEST(DBTest1) {SQL sql;sql.connect();// 실제 테스트 코드sql.close();

}

Page 11: UnitTest, Tdd For Games Kgc2007 ParkPD

UnitTest++ 기능 2/3

TimeConstraint� 실행 시간이 일정 이상 지나면

테스트 fail 로 간주.TestResult r;TimeConstraint t(10, result, TestDetails(“”, “”, “”, 0);TimeHelpers::SleepMs(20);CHECK_EQUAL(1, result.GetFailureCount());

Crash 검사

Page 12: UnitTest, Tdd For Games Kgc2007 ParkPD

UnitTest++ 기능 3/3

SuiteTwo Stage Test � 1단계

� 리소스 로딩 이전에� 로직 테스트, 순수한 의미의 UnitTest

� 2단계� 리소스 로딩 후에� 월드 지형 버그, 스킬, 퀘스트 등 데이터 로딩이 필요한 테스트� 지형의 이동 가능 여부 등

성능 테스트� 같은 함수를 100만번 부를 때 0.01초 내에 리턴되는지 검사� 매번 검사하기 부담스러우므로 command 명령으로 가끔씩 수동

으로 테스트하기.

Page 13: UnitTest, Tdd For Games Kgc2007 ParkPD

Unit Test 예제World World World World worldworldworldworld;;;;const const const const initialHealthinitialHealthinitialHealthinitialHealth = 60;= 60;= 60;= 60;Player Player Player Player player(initialHealthplayer(initialHealthplayer(initialHealthplayer(initialHealth););););world.Add(&playerworld.Add(&playerworld.Add(&playerworld.Add(&player, , , , Transform(AxisYTransform(AxisYTransform(AxisYTransform(AxisY, 0, Vector3(10,0,10));, 0, Vector3(10,0,10));, 0, Vector3(10,0,10));, 0, Vector3(10,0,10));HealthPowerupHealthPowerupHealthPowerupHealthPowerup poweruppoweruppoweruppowerup;;;;world.Add(&powerupworld.Add(&powerupworld.Add(&powerupworld.Add(&powerup, , , , Transform(AxisYTransform(AxisYTransform(AxisYTransform(AxisY, 0, Vector3(, 0, Vector3(, 0, Vector3(, 0, Vector3(----10,0,20);10,0,20);10,0,20);10,0,20);world.Update(0.1f);world.Update(0.1f);world.Update(0.1f);world.Update(0.1f);CHECK_EQUAL(initialHealthCHECK_EQUAL(initialHealthCHECK_EQUAL(initialHealthCHECK_EQUAL(initialHealth, , , , player.GetHealthplayer.GetHealthplayer.GetHealthplayer.GetHealth());());());());

TEST (TEST (TEST (TEST (PlayersHealtDoesNotIncreaseWhileFarFromHealthPowerupPlayersHealtDoesNotIncreaseWhileFarFromHealthPowerupPlayersHealtDoesNotIncreaseWhileFarFromHealthPowerupPlayersHealtDoesNotIncreaseWhileFarFromHealthPowerup) {) {) {) {World World World World worldworldworldworld;;;;const const const const initialHealthinitialHealthinitialHealthinitialHealth = 60;= 60;= 60;= 60;Player Player Player Player player(initialHealthplayer(initialHealthplayer(initialHealthplayer(initialHealth););););world.Add(&playerworld.Add(&playerworld.Add(&playerworld.Add(&player, , , , Transform(AxisYTransform(AxisYTransform(AxisYTransform(AxisY, 0, Vector3(10,0,10));, 0, Vector3(10,0,10));, 0, Vector3(10,0,10));, 0, Vector3(10,0,10));HealthPowerupHealthPowerupHealthPowerupHealthPowerup poweruppoweruppoweruppowerup;;;;world.Add(&powerupworld.Add(&powerupworld.Add(&powerupworld.Add(&powerup, , , , Transform(AxisYTransform(AxisYTransform(AxisYTransform(AxisY, 0, Vector3(, 0, Vector3(, 0, Vector3(, 0, Vector3(----10,0,20);10,0,20);10,0,20);10,0,20);world.Update(0.1f);world.Update(0.1f);world.Update(0.1f);world.Update(0.1f);CHECK_EQUAL(initialHealthCHECK_EQUAL(initialHealthCHECK_EQUAL(initialHealthCHECK_EQUAL(initialHealth, , , , player.GetHealthplayer.GetHealthplayer.GetHealthplayer.GetHealth());());());());

}}}}

Page 14: UnitTest, Tdd For Games Kgc2007 ParkPD

최상의 관행: 간결한 검사TEST (ShieldStartsAtInitialLevel){

ShieldComponent shield(100);CHECK_EQUAL (100, shield.GetLevel());

}

TEST (ShieldTakesDamage){

ShieldComponent shield(100);shield.Damage(30);CHECK_EQUAL (70, shield.GetLevel());

}

TEST (LevelCannotDropBelowZero){

ShieldComponent shield(100);shield.Damage(200);CHECK_EQUAL (0, shield.GetLevel());

}

TEST(ActorDoesntMoveIfPelvisBodyIsInSamePositionAsPelvisAnim){

component = ConstructObject<UAmpPhysicallyDrivableSkeletalComponent>();component->physicalPelvisHandle = NULL;component->SetOwner(owner);component->SkeletalMesh = skelMesh;component->Animations = CreateReadable2BoneAnimSequenceForAmpRagdollGetup(component, skelMesh,10.0f, 0.0f);component->PhysicsAsset = physicsAsset;component->SpaceBases.AddZeroed(2);component->InitComponentRBPhys(false);component->LocalToWorld = FMatrix::Identity;const FVector actorPos(100,200,300);const FVector pelvisBodyPositionWS(100,200,380);const FTranslationMatrix actorToWorld(actorPos);owner->Location = actorPos;component->ConditionalUpdateTransform(actorToWorld);INT pelvisIndex = physicsAsset->CreateNewBody(TEXT("Bone1"));URB_BodySetup* pelvisSetup = physicsAsset->BodySetup(pelvisIndex);FPhysAssetCreateParams params = GetGenericCreateParamsForAmpRagdollGetup();physicsAsset->CreateCollisionFromBone( pelvisSetup,

skelMesh,1,params,boneThings);

URB_BodyInstance* pelvisBody = component->PhysicsAssetInstance->Bodies(0);NxActor* pelvisNxActor = pelvisBody->GetNxActor();SetRigidBodyPositionWSForAmpRagdollGetup(*pelvisNxActor, pelvisBodyPositionWS);

component->UpdateSkelPose(0.016f);component->RetransformActorToMatchCurrrentRoot(TransformManipulator());

const float kTolerance(0.002f);

FMatrix expectedActorMatrix;expectedActorMatrix.SetIdentity();expectedActorMatrix.M[3][0] = actorPos.X;expectedActorMatrix.M[3][1] = actorPos.Y;expectedActorMatrix.M[3][2] = actorPos.Z;const FMatrix actorMatrix = owner->LocalToWorld();CHECK_ARRAY2D_CLOSE(expectedActorMatrix.M, actorMatrix.M, 4, 4, kTolerance);

}

Page 15: UnitTest, Tdd For Games Kgc2007 ParkPD

예시: 캐릭터의 행동TEST_F( CharacterFixture,

SupportedWhenLeapAnimationEndsTransitionsRunning ){

LandingState state(CharacterStateParameters(&character),AnimationIndex::LeapLanding);

state.Enter(input);input.deltaTime = character.GetAnimationDuration(

AnimationIndex::LeapLanding ) + kEpsilon;

character.supported = true;CharacterStateOutput output = state.Update( input );CHECK_EQUAL(std::string("TransitionState"),

output.nextState->GetClassInfo().GetName());const TransitionState& transition = *output.nextState;CHECK_EQUAL(std::string("RunningState"),

transition.endState->GetClassInfo().GetName());}

Page 16: UnitTest, Tdd For Games Kgc2007 ParkPD

Working Effectively with Legacy Code

필요한 이유

Debugging

Regression Test

Page 17: UnitTest, Tdd For Games Kgc2007 ParkPD

리니지2

리니지2 업데이트 일지� CHRONICLE 01 - 전란을 부르는 자들

� CHRONICLE 02 - 풍요의 시대

� CHRONICLE 03 - 눈뜨는 어둠

� CHRONICLE 04 - 운명의 계승자들

� CHRONICLE 05 - Death of Blood

� 혼돈의 왕좌 Interlude - 그 시작을 말하다

� 혼돈의 왕좌 - The kamael (2007)

계속되는 업데이트 & 변경되는 기획

Page 18: UnitTest, Tdd For Games Kgc2007 ParkPD

왜 개발자가 Test 까지?

QA 팀이 있으신가요?

� 없는 회사가 대부분

QA 팀이 있어도

� 최고의 QA 팀이 있어도 버그는 막을 수 없다.� Lineage2 팀의 QA 팀은 최고입니다.

� 마감직전에 발견되는 버그가 가장 큰 문제를 일으킨다.

결국 욕은 프로그래머가 먹고,

� 야근도 해야 한다. 미리 Test를 이용, 버그를 막아보자.

버그가 생기면

� 수익 감소

� 악플뿐 아니라 웹진기사가 뜨는 경우까지!

Page 19: UnitTest, Tdd For Games Kgc2007 ParkPD

QA 팀은 역시 필요합니다.

스토리스토리스토리스토리 테스트테스트테스트테스트비즈니스 의도(제품 설계)

사용성사용성사용성사용성 테스팅테스팅테스팅테스팅탐색적탐색적탐색적탐색적 테스팅테스팅테스팅테스팅

단위단위단위단위 테스트테스트테스트테스트개발자 의도(코드 설계)

특성특성특성특성 테스팅테스팅테스팅테스팅보안 테스팅부하 테스팅조합 테스팅

자동

자동

수동

도구

Page 20: UnitTest, Tdd For Games Kgc2007 ParkPD

Test Driven Debugging?

일반적인 디버깅 방법은?1. 버그 리포트 시스템에 새로운 버그 추가2. 게임 스크립트 데이타 받아서 컴파일3. 서버들 빌드 후 loading

1. 여기까지 5~10분은 걸림.4. 클라이언트 1개~3개 실행

1. 역시나 3분 이상 소모됨5. 재현

1. 재현하기 힘든 경우라면? 2. 혈맹 전쟁을 테스트하려면? 혈원 15명 이상이 접속

해야 테스트 가능6. 코드 수정7. 3번으로 돌아가서 확인

Page 21: UnitTest, Tdd For Games Kgc2007 ParkPD

Test Driven Debugging!!

TDD 를 이용할 때1. 디버그 관리자에 새로운 버그 추가2. 게임 스크립트 데이타 받아서 컴파일3. 서버들 빌드 후 loading

1. 여기까지 5~10분은 걸림.2. 스크립트 없이 테스트 할 수 있는 경우가 많음.

4. 클라이언트 1개~3개 실행1. 역시나 3분 이상 소모됨

2. 클라이언트 없이 실행 가능.5. 재현

1. 재현하기 힘든 경우라면?2. 혈맹 전쟁을 테스트하려면? 혈원 15명 이상이 접속해야 테스트 가능

3. 직접 확률을 지정하거나, 코드에서 loop 돌릴 수 있다.6. 코드 수정7. 3번으로 돌아가서 확인

8. 한 번 만들어진 테스트는 계속 남는다.

Page 22: UnitTest, Tdd For Games Kgc2007 ParkPD

Regression Test

변경되지 않은 기능은 ‘예전과 동일하게 동작함’을 보장하는 테스트� Characterization Test� 현재 상태를 그대로 테스트로 추가CPlayer* pMe = ...;CHECK_EQUAL(0, pMe->GetLife()); // Test FailedCHECK_EQUAL(644, pMe->GetLife()); // Test 성공

리펙토링을 하기 전 필수적인 작업일종의 TLP(Test Last Programming)

Page 23: UnitTest, Tdd For Games Kgc2007 ParkPD

Regression Test

2년 전의 전투 관련 서버 코드가 어떻게 돌아가는지 보고 싶다면

� 2년 전 Server 소스 snapshot 받아서 빌드

� 같은 날의 Client 소스 snapshot 받아서 빌드

� 같은 날의 게임 스크립트 데이타 로딩

� DB 스키마 셋팅

� 등등등...

Page 24: UnitTest, Tdd For Games Kgc2007 ParkPD

Regression Test in TDD

2년 전에 전투 관련 서버 코드가 어떻게 돌아가는지 보고 싶다면� 2년 전 Server 소스 snapshot 받아서 빌드

� 같은 날의 Client 소스 snapshot 받아서 빌드

� 같은 날의 게임 스크립트 데이타 로딩

� DB 스키마 셋팅

� 등등등...

심지어 예전 코드가 어떻게 실행되는지를 직접 Break Point 잡고 Trace 할 수 있다.

Page 25: UnitTest, Tdd For Games Kgc2007 ParkPD

Branch & Merge

Branch 후 Merge 작업

� Merge 하면서 다른 팀원이 바꾸어 놓은 코드때문에 버그 발생� 1차적으로는 지속적인 통합을 권장

� 2차적으로는 UnitTest 를 통해서 다른 팀원들에게지켜야 할 가이드라인을 제시

Page 26: UnitTest, Tdd For Games Kgc2007 ParkPD

Working Effectively with Legacy Code

Seams

Sprout Method / Class

Breaking Dependencies

Interception Points

Pinch Point Traps

Targeted Testing

Sensing Variable

Construction Test

Hack Points

Page 27: UnitTest, Tdd For Games Kgc2007 ParkPD

테스트 방법

리턴값

CHECK_CLOSE(10.5248, CAttacker::GetCritical(p1, p2, ...), 0.001);

객체 상태

pPlayer->GetSkill(1, 1);

CHECK_EQUAL(1, pPlayer->GetSkillsNum());

객체 상호작용

� Mock 객체 사용.

Page 28: UnitTest, Tdd For Games Kgc2007 ParkPD

TDD Tips 1

가장 쉽게 만들 수 있는 것부터 테스트에 추가한다.

Multithread 테스트는 포기한다.

#if defined(UnitTestDefined) && defined(_DEBUG)

� 팀원들을 안심시켜라.

� Release 빌드에서는file 에서 오른쪽 버튼 -> general 탭 에서 exclude file from build

테스트를 빠르게 유지� Disk I/O 를 최소화한다.

� 스크립트, Database dependency 를 최소화 할 수 있다.

Page 29: UnitTest, Tdd For Games Kgc2007 ParkPD

TDD Tips 2

기존 코드에 테스트 추가하기

� test 없는 private 보다 test 있는 public 이 안전

� 멤버변수도 parameter 로 넘기면 test 만들기 쉬워진다.

� 마찬가지로 전역변수도 parameter 로 넘겨주자.

� 이제 아예 static 멤버함수로 만들자.

� 좀 더 쉽게 테스트를 만들 수 있다.

Page 30: UnitTest, Tdd For Games Kgc2007 ParkPD

TDD Tips 3

breakpoint -> trace 는 대신

� 필요한 곳에 CHECK 테스트를 추가한다.

임의성 테스트

Windows 프로그램에서 콘솔 띄우기

TDD 돌릴 것인지 여부를 설정파일로 결정

주의!

� 직접 테스트도 병행해야 한다.

Page 31: UnitTest, Tdd For Games Kgc2007 ParkPD

임의성 테스트

타격 크리티컬 같이 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(...));

Page 32: UnitTest, Tdd For Games Kgc2007 ParkPD

Windows 프로그램에서 콘솔 띄우기

// http://dslweb.nwnexus.com/~ast/dload/guicon.htmstatic const WORD MAX_CONSOLE_LINES = 500;void RedirectIOToConsole() {

CONSOLE_SCREEN_BUFFER_INFO coninfo;AllocConsole();GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);coninfo.dwSize.Y = MAX_CONSOLE_LINES;SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);

// redirect unbuffered STDIN to the consolelStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);fp = _fdopen( hConHandle, "w" );*stderr = *fp;setvbuf( stderr, NULL, _IONBF, 0 );ios::sync_with_stdio();

}FreeConsole() 이용

Page 33: UnitTest, Tdd For Games Kgc2007 ParkPD

Mock 객체

소켓 통신을 어떻게 테스트할 것인가?

파일 시스템이 꽉 차 있는 경우는 어떻게 테스트 할 것인가?

� 진짜 하드를 꽉 채운 후 테스트?

DB 관련

원하는 환경을 가짜로 돌아가는 것처럼 만들어 주는 객체를 이용하자.

Page 34: UnitTest, Tdd For Games Kgc2007 ParkPD

Mock 객체

class SecretObject {protected:

int m_Age;virtual int GetMyAge() const { return m_Age; }

}class MockSecretObject : public SecretObject {

public:using SecretObject::m_Age;virtual int GetMyAge() const {

return SecretObject::GetMyAge(); }

}

MockSecretObject a;a.GrownUp();CHECK_EQUAL(1, a.GetMyAge());CHECK_EQUAL(1, a.m_Age);

Page 35: UnitTest, Tdd For Games Kgc2007 ParkPD

Mock 객체

class CMockPlayer : public CPlayer {

virtual CSocket* GetSocket() { return m_pSocket; }

CMockSocket* m_pSocket;

void Attack(double damage) {GetSocket()->SendMsg(“You got damage %d”,

damage);

}

class CMockSocket : public CSocket {

virtual void Send(...) {}virtual bool SendMsg(…) { return true;}

}

Page 36: UnitTest, Tdd For Games Kgc2007 ParkPD

Mock 시연 – FPS? ☺

Page 37: UnitTest, Tdd For Games Kgc2007 ParkPD

Mock 시연 – FPS? ☺

Page 38: UnitTest, Tdd For Games Kgc2007 ParkPD

테스트 - 일반원칙

망가질 가능성이 있는 모든 것을 테스트한다.

망가지는 모든 것을 테스트한다.

새 코드는 무죄가 증명되기 전까지는 유죄.

적어도 제품 코드만큼 테스트 코드를 작성한다.

컴파일을 할 때마다 지역 테스트를 실행한다.

저장소에 체크인하기 전에 모든 테스트를 실행해 본다.

Page 39: UnitTest, Tdd For Games Kgc2007 ParkPD

자문해 봐야 할 사항

이 코드가 옳게 동작한다면, 어떻게 그것을알 수 있는가?

이것을 어떻게 테스트할 것인가?

'그밖에' 어떤 것이 잘못될 수 있는가?

이와 똑같은 종류의 문제가 다른 곳에서도 일어날 수 있을까?

Page 40: UnitTest, Tdd For Games Kgc2007 ParkPD

무엇을 테스트해야 하는가RIGHT-BICEP

Right : 결과가 옳은가?

Boundary : 모든 경계 조건이 CORRECT한가?

Inverse : 역관계를 확인할 수 있는가?

Cross-check : 다른 수단을 사용해서 결과를 교차확인 할 수 있는가?

Error condition : 에러 조건을 강제로 만들어낼 수있는가?

Performance : 성능 특성이 한도내에 있는가?

Page 41: UnitTest, Tdd For Games Kgc2007 ParkPD

좋은 테스트는 A-TRIP해야 한다.

Automatic(자동적)

Through(철저함)

Repeatable(반복 가능)

Independent(독립적)

Professional(전문적)

Page 42: UnitTest, Tdd For Games Kgc2007 ParkPD

CORRECT 경계 조건

Conformance(형식 일치) : 값의 형식이 예상한 형식과 일치하는가?

Ordering(순서) : 적절히 순서대로 되어 있거나 그렇지 않은 값인가?

Range(범위) : 적당한 최소값과 최대값 사이에 있는 값인가?

Reference(참조) : 코드가 자기가 직접 제어하지 않는 외부 코드를참조하는가?

Existence(존재성) : 값이 존재하는가?

Cardinality(개체 수) : 확실히 충분한 값이 존재하는가?

Time(시간) : 모든 것이 순서대로 일어나는가? 제시간에? 때맞추어?

출처 : 실용주의 프로그래머를 위한 단위 테스트 with JUnit

Page 43: UnitTest, Tdd For Games Kgc2007 ParkPD

테스트 기피를 위한 변명

시간이 오래 걸린다.

개발 초기에는 기획 변경이 잦아서 테스트를만들어 봐야 소용없다.

Page 44: UnitTest, Tdd For Games Kgc2007 ParkPD

시간이 오래 걸린다 -> 맞습니다

2개월에서 1년까지는 시간이 더 걸립니다.

모 게임사의 XP 실패담.

� 테스트 코드가 2 만 라인이 안 되는 Product Code 보다 8배 정도 많음.

� 사람들이 #ifdef 로 테스트 코드를 무시하기 시작함.

� 다른 사람이 망가뜨린 테스트를 대신 고치는 일이 계속되면서 짜증 증가

CODECODECODECODE

Page 45: UnitTest, Tdd For Games Kgc2007 ParkPD

그러나!서비스를 오래 하려면?

기획 경화 현상� 이거 고쳤다가

잘못 되면 어쩔려고 그래요?

예전 구현을 손 대지않으려고땜빵식 구현/기획을추가하면서점점 더 고치기 힘들어짐.

버그/핵 에 대처 능력이떨어지게 된다.

Page 46: UnitTest, Tdd For Games Kgc2007 ParkPD

기획이 자주 변경된다.

Fragile Test

지금 아니면 할 수 없습니다.

� 초반부터테스트 코드를추가하면,더욱 더 단단한코드를얻을 수 있고,신뢰할 수 있는테스트 집합을구축할 수 있다.

Page 47: UnitTest, Tdd For Games Kgc2007 ParkPD

TDD 적용하기

스스로 먼저 확신을 가질 수 있도록 먼저 해보기

Page 48: UnitTest, Tdd For Games Kgc2007 ParkPD

UnitTest 의 어려운 점

팀원들에게 같이 하자고 꼬시는 게 가장 어려움

� 왜 일을 더 해야 하는지(테스트 코딩)를 설득하기가 어려움

일부만 UnitTest 를 한다면

� 다른 팀원이 수정한 내용이 Test 를 실패시키는 바람에 갈등 유발

Mock 객체를 부주의하게 사용해서

� UnitTest define 을 끈 채로 빌드하면 에러 발생!

� 비정상적인 로직이 동작하게 할 수 있음

기존 가정을 깨는 Seam Code 를 추가하는 도중에� 없던 문제를 발생시킬 수 있음 �

테스트 코드는 제품코드가 아니라는 생각 때문에 막 코딩해 버림

� 테스트 코드 자체가 주체할 수 없게 됨

그럼에도 불구하고 지켜봐 주고 도와준 팀원들에게 감사!!!

Page 49: UnitTest, Tdd For Games Kgc2007 ParkPD

결론

테스트 프레임워크 구축은 쉽지 않다.

그러나 노력한 만큼 복리로 돌려받을 수 있다.

테스터의 입장에서 코드를 바라보게 된다.(코드 품질이 향상되고, 좋은 버릇이 생긴다.)

모든 방법을 동원해서 테스트하라.

� 상상력이 필요합니다.

� TDD 는 도구이지 목표가 아니다.

� 1900 년 초부터 UnitTest 는 시작되었습니다.

Page 50: UnitTest, Tdd For Games Kgc2007 ParkPD

참고자료

http://unittesthttp://unittest--cpp.sourceforge.net/cpp.sourceforge.net/�� UnitTestUnitTest++ ++ 소스소스 받는받는 곳곳

http://www.gamesfromwithin.comhttp://www.gamesfromwithin.com�� Noel Noel LlopisLlopis -- [email protected]@convexhull.com

�� GDC2006 GDC2006 발표자료발표자료

http://andstudy.com/andwiki/wiki.php/BackwardsIsFohttp://andstudy.com/andwiki/wiki.php/BackwardsIsForwardrward

�� 위위 자료를자료를 번역해번역해 놓은놓은 PPT PPT 및및 노트노트

Page 51: UnitTest, Tdd For Games Kgc2007 ParkPD

테스트 주도 개발

단위 테스트 with JUnit

Page 52: UnitTest, Tdd For Games Kgc2007 ParkPD

Working Effectively with Legacy Code

xUnit Test Patterns

Page 53: UnitTest, Tdd For Games Kgc2007 ParkPD

Q & A