Upload
ryan-park
View
1.804
Download
5
Embed Size (px)
Citation preview
테스트 주도 개발 : 무엇을 왜 , 어떻게 ?
Noel LlopisSenior ArchitectHigh Moon Studios
번역 : 박일http://[email protected]
1. TDD1. TDD 란란 ? ?
2. TDD 2. TDD 쓰는 법쓰는 법 ??
3. TDD 3. TDD 와 게임와 게임4. 4. 배운 점배운 점5. 5. 결론결론
1. TDD1. TDD 란란 ? ? (( 왜 이걸 쓰고 싶어할까왜 이걸 쓰고 싶어할까 ??))
2. TDD2. TDD 쓰는 법쓰는 법3. TDD 3. 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 는 이런 문제점에 초점이 맞춰져 있다 .
몇 분 안에 진행몇 분 안에 진행
테스트 실패테스트 실패
테스트 통과테스트 통과테스트 통과테스트 통과
체크인Check in
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){}
테스트작성 코드 작성
리펙토링
좋은 점 : 단순함 , 모듈화
좋은 점 : 안전망
좋은 점 : Instant feedback
마일스톤마일스톤 : ~2 : ~2 개월개월주기주기 : 2-4 : 2-4 주주Nightly Nightly 빌드빌드 : 1 : 1 일일자동 빌드자동 빌드 : ~1 : ~1 시간시간TDD: 3-4 TDD: 3-4 분간에 분간에 30 30 여번여번
좋은 점 : 문서화
TDD != TDD != 단위 테스트단위 테스트TDD != TDD != 테스트 방법테스트 방법
TDD == TDD == 개발 방법론개발 방법론
1. TDD1. TDD 란란 ??
2. TDD2. TDD 쓰는 법쓰는 법3. TDD 3. 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){
}
ShieldTest Shield
bool Damage()Damage?
Shield shield;CHECK (shield.Damage());
“Failure in ShieldLevelStartsFull: Expected 100 but got 0”
상태 검사
TEST (LevelCannotBeDamagedBelowZero){
}
ShieldTest Shield
Damage(200)
GetLevel()
Shield shield;shield.Damage(200);CHECK_EQUAL (0, shield.GetLevel());
0?
어디에 테스트를 둘까 ?
• TestGame.exe (links with Game.lib)TestGame.exe (links with Game.lib)• #ifdef UNIT_TESTS#ifdef UNIT_TESTS• GameTests.DLLGameTests.DLL• GameTests.upkGameTests.upk
테스트 작성
• 테스트를 쉽게 추가하기 위해 단위 테스트 테스트를 쉽게 추가하기 위해 단위 테스트 프레임워크를 사용프레임워크를 사용
• UnitTest++ UnitTest++ 는 게임에 잘 맞아떨어짐는 게임에 잘 맞아떨어짐 ..
매 빌드마다 테스트 돌리기
테스트 상호작용( 문제가 될 수 있는 부분 )
Test Character Character
Damage()
*m_shield
TEST(CharacterUsesShieldToAbsorbDamage){
Character character(400);character.Damage(100);CHECK_EQUAL(390, character.GetHealth());
}
390?
Shield
GetHealth()
FancyShield
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;
}}
A mock object stands in for an objectoutside the unit you're testing
테스트에 Mock 쓰기
Test Character Character
Damage()
*m_shield
TEST(CharacterUsesShieldToAbsorbDamage){
}
MockShield Parameters correct?
GetHealth()Returned damagecorrectly used?
MockShield mockShield = new MockShield;mockShield->damageToReturn = 10;Character character(400, mockShield);
character.Damage(200);
CHECK_EQUAL(200, mockShield->damagePassedIn);CHECK_EQUAL(390, character.GetHealth());
실천법 : 가까이 있는 코드만 테스트하기
TestCodeundertest
TestCodeundertest
Subsystem A Subsystem BSubsystem C
Something the catdragged in
The kitchen sink
Who knows
Best Practices: Keep Tests SimpleTEST (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
Total time spent in 1923 tests: 4.83 seconds.
Time spent in 26 slow tests: 2.54 seconds.
실천법 : 테스트를 빠르게 유지하기
Running unit tests TestDebugServer in Debug...116 tests runThere were no test failures. Test time: 0.016 seconds.
Running unit tests for TestStreams in Debug...138 tests runThere were no test failures. Test time: 0.015 seconds.
Running unit tests TestMath in Debug...245 tests runThere were no test failures. Test time: 0.001 seconds.
Running unit tests...184 tests runThere were no test failures. Test time: 0.359 seconds.
실천법 : 테스트를 독립적으로 유지하기
g_CollisionWorldSingleton
1. TDD1. TDD 란란 ??
2. TDD 2. TDD 쓰는 법쓰는 법
3. TDD 3. TDD 와 게임와 게임4. 4. 배운 점배운 점5. 5. 결론결론
콘솔에서는 테스트를 덜 자주 돌렸습니다 .
전체 API 를 wrap 하기
API 상태를 직접 테스트하기
API 함수 호출 뺀 나머지 코드를 테스트하기
미들웨어를 끼고 테스트하기
HavokRenderWare
UnrealNovodexOpenGLDirectX
만들어진 엔진을 끼고 TDD 하기
TDD 를 해 보고 싶지만 ...
1. TDD 1. TDD 란란 ??
2. TDD 2. TDD 사용법사용법3. TDD 3. 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 rewardHigh risk – High reward
1. TDD1. TDD 란란 ??
2. TDD 2. TDD 쓰는 법쓰는 법3. TDD 3. TDD 와 게임와 게임4. 4. 배운 점배운 점
5. 5. 결론결론
결론
질문 ?
ResourcesResourcesGames from Within Games from Within http://www.gamesfromwithin.comhttp://www.gamesfromwithin.com
Includes paper for this presentation with more details and Includes paper for this presentation with more details and links to other TDD resources and the UnitTest++ links to other TDD resources and the UnitTest++ framework.framework.
Noel Llopis - Noel Llopis - [email protected]