게임을 위한 테스트 주도 개발 : 무엇을 , 왜 그리고 어떻게 ?

Post on 26-Jan-2016

79 Views

Category:

Documents

6 Downloads

Preview:

Click to see full reader

DESCRIPTION

게임을 위한 테스트 주도 개발 : 무엇을 , 왜 그리고 어떻게 ?. Noel Llopis Senior Architect High Moon Studios 번역 : 박일 http://ParkPD.egloos.com 도움 : 김기웅 http://betterways.tistory.com/. 1. 테스트 주도 개발 ( TDD) 이란 ? 2. 우리는 TDD 를 어떻게 사용했는가 3. TDD 와 게임 4. 우리가 얻은 교훈들 5. 결론. 1. 테스트 주도 개발 (TDD) 이란 ? - PowerPoint PPT Presentation

TRANSCRIPT

게임을 위한 테스트 주도 개발 :

무엇을 , 왜 그리고 어떻게 ?Noel LlopisSenior ArchitectHigh Moon Studios

번역 : 박일 http://ParkPD.egloos.com도움 : 김기웅 http://betterways.tistory.com/

1. 1. 테스트 주도 개발테스트 주도 개발 ((TDD)TDD) 이란이란 ??2. 2. 우리는 우리는 TDDTDD 를 어떻게 사용했는가를 어떻게 사용했는가3. TDD3. TDD 와 게임와 게임4. 4. 우리가 얻은 교훈들우리가 얻은 교훈들5. 5. 결론결론

1. 1. 테스트 주도 개발테스트 주도 개발 (TDD)(TDD)이란이란 ?? (( 그리고 어쩌다 이것을 도입하게 되었는가에 대하여그리고 어쩌다 이것을 도입하게 되었는가에 대하여 ))

2. 2. 우리는 우리는 TDDTDD 를 어떻게 사용했는가를 어떻게 사용했는가3. TDD3. TDD 와 게임와 게임4. 4. 우리가 얻은 교훈들우리가 얻은 교훈들5. 5. 결론결론

define G(n) int n(int t, int q, int d)#define X(p,t,s) (p>=t&&p<(t+s)&&(p-(t)&1023)<(s&1023))#define U(m) *((signed char *)(m))#define F if(!--q){#define I(s) (int)main-(int)s#define P(s,c,k) for(h=0; h>>14==0; h+=129)Y(16*c+h/1024+Y(V+36))&128>>(h&7)?U(s+(h&15367))=k:kG (B){ Z; F D = E (Y (V), C = E (Y (V), Y (t + 4) + 3, 4, 0), 2, 0); Y (t + 12) = Y (t + 20) = i; Y (t + 24) = 1; Y (t + 28) = t; Y (t + 16) = 442890; Y (t + 28) = d = E (Y (V), s = D * 8 + 1664, 1, 0); for (p = 0; j < s; j++, p++) U (d + j) = i == D | j < p ? p--, 0 : (n = U (C + 512 + i++)) < ' ' ? p |= n * 56 - 497, 0 : n;}n = Y (Y (t + 4)) & 1;FU (Y (t + 28) + 1536) |=62 & -n;MU (d + D) =X (D, Y (t + 12) + 26628, 412162) ? X (D, Y (t + 12) + 27653, 410112) ? 31 : 0 : U (d + D);for (; j < 12800; j += 8) P (d + 27653 + Y (t + 12) + ' ' * (j & ~511) + j % 512, U (Y (t + 28) + j / 8 + 64 * Y (t + 20)), 0);}F if (n) { D = Y (t + 28); if (d - 10) U (++Y (t + 24) + D + 1535) = d; else { for (i = D; i < D + 1600; i++) U (i) = U (i + 64); Y (t + 24) = 1; E (Y (V), i - 127, 3, 0); } }else Y (t + 20) += ((d >> 4) ^ (d >> 5)) - 3;}}

TDD 는 앞서 언급된 문제들을 해결해주었다

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

테스트 실패테스트 실패

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

체크 인체크 인

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){}

테스트작성 코드 작성

리팩토링

장점 : 단순함과 모듈화

장점 : 안전망

장점 : 즉각적인 피드백

마일스톤마일스톤 : ~2: ~2 개월개월주기주기 : 2~4: 2~4 주주야간야간 구축구축 : 1: 1 일일자동 구축자동 구축 : ~1: ~1 시간시간TDDTDD : 30: 30 회 회 / / 3~43~4 분분

장점 : 문서화

TDD != TDD != 단위 검사단위 검사TDD != TDD != 테스팅 전략테스팅 전략

TDD == TDD == 개발 기법개발 기법

1. 1. 테스트 주도 개발테스트 주도 개발 (TDD)(TDD) 이란이란 ??

2. 2. 우리는 우리는 TDDTDD 를 어떻게 를 어떻게 사용했는가사용했는가

3. TDD3. TDD 와 게임와 게임4. 4. 우리가 얻은 교훈들우리가 얻은 교훈들5. 5. 결론결론

캐릭터 + 방패

Character

Damage(x)

Shield

Damage(x)

class Character{

IShield* m_shield;public:

Character();void Damage(float amount);float GetHealth() const;

};

3 가지 검사 방법

• 반환되는 값을 검사하기반환되는 값을 검사하기• 상태를 검사하기상태를 검사하기• 객체간의 상호작용을 검사하기객체간의 상호작용을 검사하기

반환되는 값 검사

TEST (ShieldCanBeDamagedIfFull){

}

방패방패 검사하기

bool Damage()데미지 ?

Shield shield;CHECK (shield.Damage());

“ShieldLevelStartsFull 에서 오류 발생 : 100 을 예상했지만 , 0 이 나옴 .”

상태 검사

TEST (LevelCannotBeDamagedBelowZero){

}

방패방패 검사하기

Damage(200)

GetLevel()

Shield shield;shield.Damage(200);CHECK_EQUAL (0, shield.GetLevel());

0?

어디에 검사를 삽입하는 게 좋을까 ?

• TestGame.exe (Game.libTestGame.exe (Game.lib 와 연결됨와 연결됨 ))• #ifdef UNIT_TESTS• GameTests.DLLGameTests.DLL• GameTests.upkGameTests.upk

검사를 작성하기

• 새로운 검사를 추가하기 손쉽도록새로운 검사를 추가하기 손쉽도록 ,, 단위 단위 검사 프레임워크를 사용하라검사 프레임워크를 사용하라

• UnitTest++UnitTest++ 는 게임에 꽤 적합하다는 게임에 꽤 적합하다

매 빌드마다 검사하기

상호작용 검사( 초기에 문제가 될 수 있다 .)

캐릭터 검사하기 캐릭터

Damage()

*m_shield

TEST(CharacterUsesShieldToAbsorbDamage){

Character character(400);character.Damage(100);CHECK_EQUAL(390, character.GetHealth());

}

390?

방패

GetHealth()

화려한방패

class IShield{public:

virtual float Damage(float amount) = 0;}

class FancyShield : public IShield{public:

float Damage(float amount) { … };}

class MockShield : public IShield{public:

float damagePassedIn;float damageToReturn;float Damage(float amount){

damagePassedIn = amount;return damageToReturn;

}}

가짜 객체가 , 검사 대상인 해당 단위 (unit) 의외부에 위치한 객체를 대신한다 .

Mock 을 사용해서 검사하기캐릭터 검사하기

캐릭터Damage()

*m_shield

TEST(CharacterUsesShieldToAbsorbDamage){

}

매개 변수들은 정확한가 ? 가짜 방패

GetHealth()반환된 데미지가정확하게 사용되는가 ?

MockShield mockShield = new MockShield;mockShield->damageToReturn = 10;Character character(400, mockShield);

character.Damage(200);

CHECK_EQUAL(200, mockShield->damagePassedIn);CHECK_EQUAL(390, character.GetHealth());

최상의 관행 : 근처의 코드만 검사하기

검사 검사중인코드 검사 검사중인

코드

하위 시스템 A 하위 시스템 B하위 시스템 C

고양이가 끄집어낸어떤 것

부엌의 싱크대

누가 알겠어

최상의 관행 : 간결한 검사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);

}

최상의 관행 : 신속한 검사

Slow Test(24 > 20 ms): CheckSpotOverlapIsHandledCorrectly1TestSlow Test(25 > 20 ms): CheckSpotOverlapIsHandledCorrectly2TestSlow Test(24 > 20 ms): DeleteWaveEventFailsIfEventDoesntExistInCueTestSlow Test(22 > 20 ms): CanGetObjectsInBrowserListPackageTestSlow Test(48 > 20 ms): HmAddActorCallsCreateActorTestSlow Test(74 > 20 ms): HmReplaceActorDoesNothingIfEmptySelectionTestSlow Test(57 > 20 ms): HmReplaceActorWorksIfTwoActorsSelectedTestSlow Test(26 > 20 ms): ThrowExceptionWhenTrackIndexOutOfRangeTest

1923 회 검사의 총소요 시간 : 4.83 초 .

26 회의 느린 검사에 소요된 시간 : 2.54 초 .

최상의 관행 : 신속한 검사

Debug 상태에서 TestDebugServer 의 단위 검사 실행중 ..검사 116 번 실행실패한 검사 없음 . 소요 시간 : 0.016 초 .

Debug 상태에서 TestStreams 의 단위 검사 실행중 ...검사 138 번 실행실패한 검사 없음 . 소요 시간 : 0.015 초 .

Debug 상태에서 TestMath 의 단위 검사 실행중 ...검사 245 번 실행실패한 검사 없음 . 소요 시간 : 0.001 초 .

단위 검사 실행중 ...검사 184 번 실행실패한 검사 없음 . 소요 시간 : 0.359 초 .

최상의 관행 : 비의존적인 검사

g_CollisionWorldSingleton

1. 1. 테스트 주도 개발테스트 주도 개발 (TDD)(TDD) 이란이란 ??2. 2. 우리는 우리는 TDDTDD 를 어떻게 사용했는가를 어떻게 사용했는가

3. TDD3. TDD 와 게임와 게임4. 4. 우리가 얻은 교훈들우리가 얻은 교훈들5. 5. 결론결론

콘솔의 경우 , PC 보다는 덜 자주 검사했다 .

API 전체를 감싸기 (wrap)

API 상태를 직접 검사하기

API 함수 호출을 제외한 모든 코드를 검사하기

미들웨어까지 포함해서 검사하기

HavokRenderWare

UnrealNovodexOpenGLDirectX

기존의 엔진에서 TDD 하기

TDD 를 해보고는 싶지만 ...

1. TDD 1. TDD 란란 ??2. TDD 2. TDD 사용법사용법3. TDD3. TDD 와 게임와 게임

4. 4. 우리가 얻은 교훈들우리가 얻은 교훈들5. 5. 결론결론

교훈 #1: 고수준의 게임 코드에도 TDD 를 적용할 수 있

다 .

function TestEnemyChoosesLightAttack(){

FightingComp = new(self) class'FightingComponent';

FightingComp.AddAttack(LightAttack);FightingComp.AddAttack(HeavyAttack);

enemy.AttachComponent(FightingComp);enemy.FightingComponent = FightingComp;enemy.FindPlayerPawn = MockFindPlayerPawn;enemy.ShouldMeleeAttack = MockShouldAttack;ShouldMeleeAttackReturn = true;

enemy.Tick(0.666);

CheckObjectsEqual(LightAttack,FightingComp.GetCurrentAttack());

}

예시 : 공격형 인공지능

예시 : 캐릭터의 행동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());}

구조의 선택

교훈 #2: TDD 와 코드 설계

교훈 #3: 검사 횟수는 프로젝트 진행의 척도가 될 수 있다

교훈 #4: TDD 는 빌드의 안정성을 높여준다

교훈 #5: TDD 는 더 많은 코드를 만들어 낸다

교훈 #6: 개발 속도

교훈 #7: TDD 를 도입하기

교훈 #7: TDD 도입하기

위험할수록위험할수록 ,, 대가가 크다대가가 크다(High risk – High reward)(High risk – High reward)

1. 1. 테스트 주도 개발테스트 주도 개발 (TDD)(TDD) 이란이란 ??2. 2. 우리는 우리는 TDDTDD 를 어떻게 사용했는가를 어떻게 사용했는가3. TDD3. TDD 와 게임와 게임4. 4. 우리가 얻은 교훈들우리가 얻은 교훈들

5. 5. 결론결론

결론

질문 ?

자료자료Games from Within Games from Within http://www.gamesfromwithin.com 위 위 SiteSite 에는 이 강연의 보다 자세한 발표 자료와에는 이 강연의 보다 자세한 발표 자료와 TDD TDD 및 및

UnitTest++ UnitTest++ 프레임워크에 대한 자료가 있다프레임워크에 대한 자료가 있다 ..[[ 역자주역자주 ]]

http://andstudy.com/andwiki/wiki.php/BackwardsIsForward이 내용에 대한 이 내용에 대한 Note Note 가 번역된 페이지입니다가 번역된 페이지입니다 . .

참고하세요참고하세요 ..

Noel Llopis - Noel Llopis - llopis@convexhull.com

top related