html5-img
1 / 53

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

게임을 위한 테스트 주도 개발 : 무엇을 , 왜 그리고 어떻게 ?. 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) 이란 ?

nikki
Download Presentation

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

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 게임을 위한 테스트 주도 개발:무엇을, 왜 그리고 어떻게? Noel Llopis Senior Architect High Moon Studios 번역 : 박일 http://ParkPD.egloos.com 도움 : 김기웅 http://betterways.tistory.com/

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

  3. 1. 테스트 주도 개발(TDD)이란? • (그리고 어쩌다 이것을 도입하게 되었는가에 대하여) • 2. 우리는 TDD를 어떻게 사용했는가 • 3. TDD와 게임 • 4. 우리가 얻은 교훈들 • 5. 결론

  4. 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:k G (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; F U (Y (t + 28) + 1536) |= 62 & -n; M U (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; } }

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

  6. TDD의 순환 과정 테스트 실패 테스트 통과 테스트 통과 불과 몇 분밖에 걸리지 않는다. 테스트 작성 코드 작성 TEST (ShieldLevelStartsFull) { Shield shield; CHECK_EQUAL (Shield::kMaxLevel, shield.GetLevel()); } Shield::Shield() : m_level (Shield::kMaxLevel) { } 체크 인 체크 인 리팩토링

  7. 장점: 단순함과모듈화

  8. 장점: 안전망

  9. 장점: 즉각적인 피드백 마일스톤: ~2개월 주기: 2~4주 야간구축: 1일 자동 구축: ~1시간 TDD : 30회 / 3~4분

  10. 장점: 문서화

  11. TDD != 단위 검사TDD != 테스팅 전략 TDD == 개발 기법

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

  13. 캐릭터+ 방패 Character Damage(x) Shield Damage(x) class Character { IShield* m_shield;public: Character(); void Damage(float amount); float GetHealth() const; };

  14. 3가지 검사 방법 • 반환되는 값을 검사하기 • 상태를 검사하기 • 객체간의 상호작용을 검사하기

  15. 반환되는 값 검사 방패 검사하기 방패 bool Damage() 데미지? TEST (ShieldCanBeDamagedIfFull) { } Shield shield; CHECK (shield.Damage()); “ShieldLevelStartsFull에서 오류 발생: 100을 예상했지만, 0이 나옴.”

  16. 상태 검사 방패 방패 검사하기 Damage(200) GetLevel() 0? TEST (LevelCannotBeDamagedBelowZero) { } Shield shield; shield.Damage(200); CHECK_EQUAL (0, shield.GetLevel());

  17. 어디에 검사를 삽입하는 게 좋을까? • TestGame.exe (Game.lib와 연결됨) • #ifdef UNIT_TESTS • GameTests.DLL • GameTests.upk

  18. 검사를 작성하기 • 새로운 검사를 추가하기 손쉽도록, 단위 검사 프레임워크를 사용하라 • UnitTest++는 게임에 꽤 적합하다

  19. 매 빌드마다 검사하기

  20. 상호작용 검사(초기에 문제가 될 수 있다.) 캐릭터 검사하기 캐릭터 Damage() 방패 화려한 방패 *m_shield GetHealth() 390? TEST(CharacterUsesShieldToAbsorbDamage){ Character character(400); character.Damage(100); CHECK_EQUAL(390, character.GetHealth());}

  21. class IShield { public: virtual float Damage(float amount) = 0; } 가짜 객체가, 검사 대상인 해당 단위(unit)의 외부에 위치한 객체를 대신한다. 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; } }

  22. Mock을 사용해서 검사하기 캐릭터 Damage() *m_shield 매개 변수들은 정확한가? 반환된 데미지가 정확하게 사용되는가? GetHealth() 캐릭터 검사하기 가짜 방패 TEST(CharacterUsesShieldToAbsorbDamage){ } MockShield mockShield = new MockShield; mockShield->damageToReturn = 10; Character character(400, mockShield); character.Damage(200); CHECK_EQUAL(200, mockShield->damagePassedIn); CHECK_EQUAL(390, character.GetHealth());

  23. 누가 알겠어 검사 검사중인 코드 검사 검사중인 코드 하위 시스템 C 하위 시스템 B 하위 시스템 A 부엌의 싱크대 고양이가 끄집어낸 어떤 것 최상의 관행: 근처의 코드만 검사하기

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

  25. 최상의 관행: 신속한 검사 Slow Test(24 > 20 ms): CheckSpotOverlapIsHandledCorrectly1Test Slow Test(25 > 20 ms): CheckSpotOverlapIsHandledCorrectly2Test Slow Test(24 > 20 ms): DeleteWaveEventFailsIfEventDoesntExistInCueTest Slow Test(22 > 20 ms): CanGetObjectsInBrowserListPackageTest Slow Test(48 > 20 ms): HmAddActorCallsCreateActorTest Slow Test(74 > 20 ms): HmReplaceActorDoesNothingIfEmptySelectionTest Slow Test(57 > 20 ms): HmReplaceActorWorksIfTwoActorsSelectedTest Slow Test(26 > 20 ms): ThrowExceptionWhenTrackIndexOutOfRangeTest 1923회 검사의 총소요 시간: 4.83초. 26회의 느린 검사에 소요된 시간: 2.54초.

  26. 최상의 관행: 신속한 검사 Debug 상태에서 TestDebugServer의 단위 검사 실행중.. 검사 116번 실행 실패한 검사 없음.소요 시간: 0.016초. Debug 상태에서 TestStreams의 단위 검사 실행중... 검사 138번 실행 실패한 검사 없음.소요 시간: 0.015초. Debug 상태에서 TestMath의 단위 검사 실행중... 검사 245번 실행 실패한 검사 없음.소요 시간: 0.001초. 단위 검사 실행중... 검사 184번 실행 실패한 검사 없음.소요 시간: 0.359초.

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

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

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

  30. API 전체를 감싸기(wrap)

  31. API 상태를 직접 검사하기

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

  33. 미들웨어까지 포함해서 검사하기 Havok RenderWare Unreal Novodex OpenGL DirectX

  34. 기존의 엔진에서 TDD하기

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

  36. 1. TDD 란? • 2. TDD 사용법 • 3. TDD와 게임 • 4. 우리가 얻은 교훈들 • 5. 결론

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

  38. 예시: 공격형 인공지능 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()); }

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

  40. 구조의 선택

  41. 교훈#2: TDD와 코드 설계

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

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

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

  45. 교훈#6: 개발 속도

  46. 교훈#7: TDD를도입하기

  47. 교훈#7: TDD 도입하기 위험할수록, 대가가 크다 (High risk – High reward)

More Related