생산적인 개발을 위한 지속적인 테스트

2,688 views

Published on

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
2,688
On SlideShare
0
From Embeds
0
Number of Embeds
952
Actions
Shares
0
Downloads
34
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

생산적인 개발을 위한 지속적인 테스트

  1. 1. (birdkr@gmail.com) birdkr@gmail.com)
  2. 2. ?
  3. 3. CruiseControl.NET
  4. 4. CruiseControl.NET
  5. 5. CruiseControl.NET • • Asset • • • • • (doxygen) •
  6. 6. ?
  7. 7. 1. . 2. . 3. , .
  8. 8. 테스트 결과 로그 <?xml version="1.0"?> <maiettest-results tests=“2" failedtests=“1" failures="1" time="0.137"> <report text="time : 680 sec" /> <report text=“총 클라이언트 개수 : 4650" /> <test name=“로그인 반복" time="0.062" > <success message="success" /> </test> <test name=“캐릭터 생성 반복" time="0.062" > <failure message="Crash!" /> </test> </maiettest-results>
  9. 9. 테스트 결과 로그 • 테스트 결과를 XML로 만들고, XSL을 이용 하여 CruseControl.NET에 출력한다.
  10. 10. Feedback
  11. 11. Unit Test • • .
  12. 12. Unit Test TEST(TestMathFunctionTruncateToInt) { CHECK_EQUAL(0, GMath::TruncateToInt(0.0)); CHECK_EQUAL(5, GMath::TruncateToInt(5.6)); CHECK_EQUAL(13, GMath::TruncateToInt(13.2)); CHECK_EQUAL(13, GMath::TruncateToInt(13.2)); CHECK_EQUAL(-6, GMath::TruncateToInt(-5.6)); CHECK_EQUAL(-3, GMath::TruncateToInt(-2.1)); }
  13. 13. Unit Test TEST(ShieldCanBeDamaged) { World world; world.Create(); Player player; player.Create(world, vec3(1000,1000,0)); player.SetHealth(1000); Shield shield; shield.SetHealth(100); player.Equip(shield); player.Damage(200); CHECK(shield.GetHealth() == 0); CHECK(player.GetHealth() == 900); }
  14. 14. Unit Test
  15. 15. Unit Test TEST_FIXTURE(FLogin, TestLogin_MC_COMM_REQUEST_LOGIN_SERVER_Success) { MakeParam_TD_LOGIN_INFO(); TD_LOGIN_INFO tdLoginInfo = MakeParam_TD_LOGIN_INFO(); OnRecv_MMC_COMM_REQUEST_LOGIN_SERVER(m_nRequestID, m_nConnectionKey, &tdLoginInfo tdLoginInfo); OnRecv_MMC_COMM_REQUEST_LOGIN_SERVER(m_nRequestID, m_nConnectionKey, &tdLoginInfo); // 로그인 하기 전의 값 체크 CHECK_EQUAL(0, gmgr.pPlayerObjectManager->GetPlayersCount()); // 클라이언트로부터 존 입장 패킷 받음 OnRecv_MC_COMM_REQUEST_LOGIN_SERVER(m_nConnectionKey); OnRecv_MC_COMM_REQUEST_LOGIN_SERVER(m_nConnectionKey); // 마스터 서버로 인증키 확인 패킷 보냈는지 체크 CHECK_EQUAL(MC_COMM_RESPONSE_LOGIN_SERVER, m_pLink- GetCommandID(0)); CHECK_EQUAL(MC_COMM_RESPONSE_LOGIN_SERVER, m_pLink->GetCommandID(0)); CHECK_EQUAL(RESULT_SUCCESS, m_pLink- GetParam<int>(0, CHECK_EQUAL(RESULT_SUCCESS, m_pLink->GetParam<int>(0, 0)); CHECK_EQUAL(m_pLink->GetUID(), m_pLink->GetParam<MUID>(0, 1)); CHECK_EQUAL(m_pLink- GetUID(), m_pLink- GetParam<MUID>(0, // 로그인 후 값 체크 CHECK_EQUAL(1, gmgr.pPlayerObjectManager->GetPlayersCount()); GPlayerObject* pPlayerObject = gmgr.pPlayerObjectManager->GetPlayer(m_pLink->GetUID()); CHECK(pPlayerObject != NULL); CHECK_EQUAL(m_nGUID, pPlayerObject->GetAccountInfo().nGUID); CHECK_EQUAL(string(“birdkr”), string(pPlayerObject->GetAccountInfo().strID)); }
  16. 16. Mock Object class MockPlayer : public GPlayer { public: MockPlayer() {}; virtual ~ MockPlayer() {}; … virtual void SendToThisSector (MPacket* pPacket) override { } virtual void SendToMe(MPacket * pPacket) override { } virtual void SendToGuild(MPacket* pPacket) override { } … };
  17. 17. , class XSystem { public: virtual unsigned int GetNowTime() { return timeGetTime() timeGetTime()(); } virtual int RandomNumber(int nMin, int nMax) { return (rand() % (nMax - nMin + 1)) + nMin; rand() rand } };
  18. 18. , class MockSystem : public XSystem { protected: unsigned int m_nExpectedNowTime; public: virtual unsigned int GetNowTime() { if (m_nExpectedNowTime != 0) return m_nExpectedNowTime; return XSystem::GetNowTime(); } void ExpectNowTime(unsigned int nNowTime) { m_nExpectedNowTime = nNowTime; } };
  19. 19. , TEST_FIXTURE(FPlayerInOut2, TestObjectCacheDelete) { vec3 vNewPos = vec3(100.0f, 100.0f, 0.0f); CHECK_EQUAL(2, gg.omgr->GetCount()); m_pNet->OnRecv( MC_ENTITY_WARP, 3, NEW_ID(m_pMyPlayer->GetID Update(0.1f); CHECK_CLOSE(100.0f, m_pMyPlayer->GetPosition().x, 0.001f); CHECK_CLOSE(100.0f, m_pMyPlayer->GetPosition().y, 0.001f); XExpectNowTime(XGetNowTime() + 10000 ); Update(10.0f); // 멀리 있는 다른 플레이어가 지워졌다. CHECK_EQUAL(1, gg.omgr->GetCount()); }
  20. 20. , template <class Type> class GTestMgrWrapper : public MInstanceChanger<Type> { public: GTestMgrWrapper() : MInstanceChanger() { m_pOriginal = gmgr.Change(m_pTester); } ~GTestMgrWrapper() { gmgr.Change(m_pOriginal); } };
  21. 21. , TEST_FIXTURE(FChangeMode, TestNPC_SightRange) { GTestMgrWrapper<GNPCInfoMgr> m_NPCInfoMgrWrapper; m_NPCInfo.nSightRange = 1000; GNPC* pNPC = m_pMap->SpawnTestNPC(&m_NPCInfo); CHECK_EQUAL(1000, pNPC->GetSightRange()); pNPC->ChangeMode(NPC_MODE_1); CHECK_EQUAL(500, pNPC->GetSightRange()); }
  22. 22. Refactoring Test Code • Mock Object • override • Google C++ Mocking Framework!
  23. 23. Refactoring Test Code • UnitTestHelper – Helper . – ) GUTHelper_NPC::SpawnNPC()
  24. 24. Refactoring Test Code • Fixture . class FBasePlayer; class FBaseItem; class FBaseNPC; class FBaseMap; class FBaseNetClient;
  25. 25. Refactoring Test Code • Fixture . class FForCombatTest : public FBaseMockLink, public FBaseNetClient, public FBasePlayer, public FBaseMap, public FBaseMapMgr, public FBasePlayer { … };
  26. 26. Refactoring Test Code Class Fduel // Fixture { … void CHECK_DuelCancel() { CHECK_EQUAL(m_pLinkRequester->GetCommand(0).GetID(), MC_DUEL_CANCEL); CHECK_EQUAL(m_pLinkTarget->GetCommand(0).GetID(), MC_DUEL_CANCEL); } void CHECK_DuelFinished(CPlayer* pWinner, CPlayer* pLoser) { MockLink* pWinnerLink = (pWinner==m_pPlayerRequester) ? M_pLinkRequester : m_pLinkT const Mcommand& Command = pWinnerLink->GetCommand(); CHECK_EQUAL(Command.GetID(), MC_DUEL_FINISHED); int nWinnerID, nLoserID; Command.GetParam(&nWinnerID, 0, MPT_INT); Command.GetParam(&nLoserID, 0, MPT_INT); CHECK_EQUAL(nWinnerID, pWinner->GetID()); CHECK_EQUAL(nLoserID, pLoser->GetID()); } }
  27. 27. Refactoring Test Code TEST_FIXTURE(FDuel, DuelQuestionRefuse) { CHECK_EQUAL(gmgr.pDuelMgr->GetCount(), 0); DuelRequest(); CHECK_EQUAL(gmgr.pDuelMgr->GetCount(), 1); BeginCommandRecord(); DuelResponse(false); CHECK_DuelCancel(); }
  28. 28. Database Unit Test • 저장 프로시저, 트리거 등에 대한 유닛 테스 트 • xDBUnit 프레임워크가 있지만 자체적으로 작성했다. – UnitTest++ 사용
  29. 29. Database Unit Test • 테스트 단계 1. SandBox에 데이터베이스, Table, SP 등 생성 2. 테스트에 필요한 데이터 집합(Seed Dataset) 을 생성 3. 테스트 케이스 실행 4. 데이터 변경 검증
  30. 30. Seed DataSet
  31. 31. Unit Test Code DBTEST(FGuildDB, CreateGuild) { UTestDB.Seed(“GuildTestSeedData.xml”); uint32 nMasterCID = DBTestHelper::GetCID(“Acc5Char1”); uint32 nMem1 = DBTestHelper::GetCID(“Acc5Char2”); uint32 nMem2 = DBTestHelper::GetCID(“Acc5Char3”); CHECK((0 != nMasterCID) && (0 != nMem1) && (0 != nMem2)); // 길드 생성 TDBRecordSet rs1; UTestDB.Execute(rs1, “{CALL spGuildCreate (‘%S’, %d)}”, “TGuild4”, nMasterCID); int nGID = rs1.Field(“GID”).AsInt(); CHECK(0 != nGID); // 길드가 추가되었는지 레코드 개수 확인 TDBRecordSet rs2; UTestDB.Execute(rs2, “SELECT COUNT(*) AS cnt FROM dbo.Guild;”); CHECK_EQUAL(1, rs2.GetFetchedCount()); CHECK_EQUAL(1, rs2.Field(“cnt”).AsInt()); }
  32. 32. • 특정 씬을 렌더링하여 원본 이미지와 같은 이미지인지 픽셀별로 비교하여 같은 픽셀 값인지 테스트 • 렌더링에 대한 UnitTest를 만들지 못하여 나온 대안 • 랜덤 요소 제거 등의 추가 작업이 필요함
  33. 33. • • • • •
  34. 34. Recv Replay Send Packet Queue Command Queue Packet Local Local Event 복사 Replay Queue 커맨드 구조 ID Data
  35. 35. Resource Validator • 기획자나 아티스트가 작업한 게임 데이터 (Assets)에 논리적으로 잘못된 값이 입력되 었는지 검증 • 예시 – 상점 인터랙션이 설정된 NPC는 비전투형인가? – 아이템 판매 가격이 구매 가격보다 높은가? – 몬스터에 설정된 스킬 애니메이션 파일이 존재하는가? – 맵의 포탈에 연결된 맵이 실재로 존재하는가?
  36. 36. Resource Validator • XML Schema • •
  37. 37. Resource Validator
  38. 38. Runtime Validator • • – DB – – AI – Assertion
  39. 39. Runtime Validator
  40. 40. • XML . – • , , , – • • Crash
  41. 41. AI • • – Crash –
  42. 42. Crash Dump Reporter
  43. 43. Crash Dump Analyzer • • •
  44. 44. Crash Dump Analyzer • • • WinDbg Command-Line •
  45. 45. Crash Dump Analyzer
  46. 46. Crash Dump Analyzer
  47. 47. ?
  48. 48. 1. 2. 3.
  49. 49. Q/A birdkr@gmail.com http://mypage.sarang.net

×