tdd for games

24
TDD for games 박박

Upload: anika-combs

Post on 30-Dec-2015

52 views

Category:

Documents


1 download

DESCRIPTION

TDD for games. 박일. TDD 란 ?. Programmer Test 프로그래머가 직접 설치하는 자동화된 테스트 White Box Test QA 팀의 테스트는 Black Box Test. 테스트 실패. 테스트 통과. 테스트 통과. TDD 의 순환 과정. 불과 몇 분밖에 걸리지 않는다. 테스트 작성. 코드 작성. TEST (ShieldLevelStartsFull){ Shield shield; - PowerPoint PPT Presentation

TRANSCRIPT

TDD for games

박일

TDD 란 ?

Programmer Test 프로그래머가 직접 설치하는 자동화된 테스트White Box Test QA 팀의 테스트는 Black Box Test

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

테스트 실패테스트 실패

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

체크 인체크 인

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

테스트작성 코드 작성

리팩토링

왜 Test?

리니지 2 업데이트 일지 CHRONICLE 01 - 전란을 부르는 자들 CHRONICLE 02 - 풍요의 시대 CHRONICLE 03 - 눈뜨는 어둠 CHRONICLE 04 - 운명의 계승자들 CHRONICLE 05 - Death of Blood 혼돈의 왕좌 Interlude - 그 시작을 말하다 혼돈의 왕좌 - The kamael (2007)계속되는 업데이트 & 변경되는 기획리펙토링 ! 수정되었던 버그가 다시 출현 Code FreezeQA 팀의 업무 증가 현재 Lineage2 팀의 QA 팀 인원은 ? 최고의 QA 팀이 있어도 버그는 막을 수 없다 .

UnitTest++

개발자 Noel Llopis Senior Architect High Moon Studios

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()

UnitTest++ 기능FIXTURE TEST_FIXTURE JUnit 의 setUp, tearDown 과 같은 역할struct CharMaker {

Char tester;CharMaker() { tester = new Char(); }~CharMaker() { delete tester; }

TEST_FIXTURE(CharMaker, SomeThing...) {CHECK_EQUAL(true, tester.HasSomeThing());

TimeConstraint 실행 시간이 일정 이상 지나면 테스트 fail 로 간주 .TestResult r;TimeContraint t(10, result, TestDetails(“”, “”, “”, 0);TimeHelpers::SleepMs(20);CHECK_EQUAL(1, result.GetFailureCount());

피보나치 예제

예제 보기

Unit Test 예제World world;const initialHealth = 60;Player player(initialHealth);world.Add(&player, Transform(AxisY, 0, Vector3(10,0,10));HealthPowerup powerup;world.Add(&powerup, Transform(AxisY, 0, Vector3(-10,0,20);world.Update(0.1f);CHECK_EQUAL(initialHealth, player.GetHealth());

TEST (PlayersHealtDoesNotIncreaseWhileFarFromHealthPowerup) {World world;const initialHealth = 60;Player player(initialHealth);world.Add(&player, Transform(AxisY, 0, Vector3(10,0,10));HealthPowerup powerup;world.Add(&powerup, Transform(AxisY, 0, Vector3(-10,0,20);world.Update(0.1f);CHECK_EQUAL(initialHealth, player.GetHealth());

}

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

}

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

Working Effectively with Legacy Code

DebuggingRegression Test

Test Driven Debugging?

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

1. 최소의 셋팅으로도 5~10 분은 걸림 .4. 클라이언트 1 개 ~3 개 실행

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

1. 재현하기 힘든 경우라면 ? 2. 좋은 버프만 동시에 30 개를 받는 경우는 ?

6. 코드 수정7. 3 번으로 돌아가서 확인

Test Driven Debugging!!

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

1. 최소의 셋팅으로도 5~10 분은 걸림 .2. 스크립트 없이 테스트 할 수 있는 경우가 많음 .

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

5. 재현1. 재현하기 힘든 경우라면 ?2. 좋은 버프만 동시에 30 개를 받는 경우는 ?3. 직접 확률을 지정하거나 , 코드에서 loop 돌릴 수 있다 .

6. 코드 수정7. 3 번으로 돌아가서 확인

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

Regression Test

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

리펙토링을 하기 전 필수적인 작업버그가 생기면 수익 감소 웹진에 소식으로 올라올 수도 ?!

테스트 방법

리턴값CHECK_CLOSE(10.5248, CAttacker::GetCritical(p1, p

2, ...), 0.001);객체 상태pUser->GetSkill(1, 1);CHECK_EQUAL(1, pUser->GetSkillNum());객체 상호작용 Mock 객체 사용 .

TDD Tips

#if defined(USING_TDD) && defined(_DEBUG) 팀원들을 안심시켜라 . Release 빌드에서는

file 에서 오른쪽 버튼 -> general 탭 에서 exclude file from build테스트를 빠르게 유지 파일 I/O 를 최소화한다 . TDD 돌릴 것인지 여부를 ini 파일로 결정test 없는 private 보다 test 있는 public 이 안전멤버변수도 파라메타로 넘기면 test 만들기 쉬워진다 . 마찬가지로 전역변수도 파라메타로 넘겨주자 .이제 아예 static 멤버함수로 만들자 . 좀 더 쉽게 테스트를 만들 수 있다 .breakpoint -> trace 는 쓰지 말자 . 대신 모든 검사에 CHECK 를 이용한다 .임의성 테스트Windows 프로그램에서 콘솔 띄우기

임의성 테스트타격 크리티컬 같이 random 값이 들어가는 계산은 어떻게 테스트 할 수 있을까 ?

#if defined(_DEBUG) && defined(USING_TDD)if (IsSetRandomNumber()) {

return GetTDDRandomNumber();}

#endif

double GetTDDRandomNumber() {return MyTestUnit::Inst().m_Random;

}TEST_FIXTURE(FixtureUser2, CheckMagicCritical){

int userLevel = 60;const double bonus = 50.0;MyTestUnit ::Inst().m_Random = 100.0; // 무조건 성공시키겠다 .CHECK_EQUAL(true, GetMagicCritical(user, userLevel, bonus));MyTestUnit ::Inst().m_Random = 0.0; // 무조건 실패시키겠다 .CHECK_EQUAL(false, GetMagicCritical(...));

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() 이용

Two Stage Test

1단계 리소스 로딩 이전에 로직 테스트 , 순수한 의미의 UnitTest2단계 리소스 로딩 후에 월드 지형 버그 , 스킬 , 퀘스트 등 데이터 로딩이

필요한 테스트 지형의 이동 가능 여부 등Suite, TestList 와 CHECK_EX 를 이용하면 two stage test 도 가능하다 .

Mock 객체

소켓 통신을 어떻게 테스트할 것인가 ?파일 시스템이 꽉 차 있는 경우는 어떻게 테스트 할 것인가 ? 진짜 하드를 꽉 채운 후 테스트 ?

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

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);

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;}

}

참고자료

http://unittest-cpp.sourceforge.net/ UnitTest++ UnitTest++ 소스 받는 곳소스 받는 곳http://www.gamesfromwithin.com Noel LlopisNoel Llopis - - [email protected] GDC2006 GDC2006 발표자료발표자료

http://andstudy.com/andwiki/wiki.php/BackwardsIsForward 위 자료를 번역해 놓은 위 자료를 번역해 놓은 PPT PPT 및 노및 노트트