(birdkr@gmail.com)
 birdkr@gmail.com)
?
CruiseControl.NET
CruiseControl.NET
CruiseControl.NET
•
• Asset
•
•
•
•
•       (doxygen)
•
?
1.           .

2.   .

3.       ,
         .
테스트 결과 로그
<?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>
테스트 결과 로그
• 테스트 결과를 XML로 만들고, XSL을 이용
  하여 CruseControl.NET에 출력한다.
Feedback
Unit Test
•
•
    .
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));
}
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);
}
Unit Test
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));
}
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 { }
        …
};
,
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
        }
};
,
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;
       }
};
,
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());
}
,
template <class Type>
class GTestMgrWrapper : public MInstanceChanger<Type>
{
public:
        GTestMgrWrapper() : MInstanceChanger()
        {
                m_pOriginal = gmgr.Change(m_pTester);
        }
        ~GTestMgrWrapper()
        {
                gmgr.Change(m_pOriginal);
        }
};
,
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());
}
Refactoring Test Code
• Mock Object
 • override
 • Google C++ Mocking Framework!
Refactoring Test Code
• UnitTestHelper
 –                            Helper
                  .
 –   ) GUTHelper_NPC::SpawnNPC()
Refactoring Test Code
•                         Fixture
                      .
    class FBasePlayer;
    class FBaseItem;
    class FBaseNPC;
    class FBaseMap;
    class FBaseNetClient;
Refactoring Test Code
•                           Fixture
                        .
class FForCombatTest : public FBaseMockLink,
                      public FBaseNetClient,
                      public FBasePlayer,
                      public FBaseMap,
                      public FBaseMapMgr,
                      public FBasePlayer
{
…
};
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());
    }
}
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();
}
Database Unit Test
• 저장 프로시저, 트리거 등에 대한 유닛 테스
  트
• xDBUnit 프레임워크가 있지만 자체적으로
  작성했다.
 – UnitTest++ 사용
Database Unit Test
• 테스트 단계
 1. SandBox에 데이터베이스, Table, SP 등 생성
 2. 테스트에 필요한 데이터 집합(Seed Dataset)
    을 생성
 3. 테스트 케이스 실행
 4. 데이터 변경 검증
Seed DataSet
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());
}
• 특정 씬을 렌더링하여 원본 이미지와 같은
  이미지인지 픽셀별로 비교하여 같은 픽셀
  값인지 테스트
• 렌더링에 대한 UnitTest를 만들지 못하여
  나온 대안
• 랜덤 요소 제거 등의 추가 작업이 필요함
•
•
•
•
•
Recv
 Replay                              Send
 Packet
 Queue         Command Queue        Packet

 Local
                                    Local
 Event
                               복사
                                    Replay
                                    Queue




커맨드 구조    ID         Data
Resource Validator
• 기획자나 아티스트가 작업한 게임 데이터
  (Assets)에 논리적으로 잘못된 값이 입력되
  었는지 검증
• 예시
 –   상점 인터랙션이 설정된 NPC는 비전투형인가?
 –   아이템 판매 가격이 구매 가격보다 높은가?
 –   몬스터에 설정된 스킬 애니메이션 파일이 존재하는가?
 –   맵의 포탈에 연결된 맵이 실재로 존재하는가?
Resource Validator
• XML             Schema
•

•
Resource Validator
Runtime Validator
•

•
    – DB
    –
    – AI
    – Assertion
Runtime Validator
•                       XML
                .
    –
        •   ,       ,     ,
    –
•
• Crash
AI
•

•
    – Crash
    –
Crash Dump Reporter
Crash Dump Analyzer
•

•
•
Crash Dump Analyzer
•
•
• WinDbg   Command-Line

•
Crash Dump Analyzer
Crash Dump Analyzer
?
1.

2.

3.
Q/A
             birdkr@gmail.com
      http://mypage.sarang.net

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

  • 1.
  • 2.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
    1. . 2. . 3. , .
  • 10.
    테스트 결과 로그 <?xmlversion="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>
  • 11.
    테스트 결과 로그 •테스트 결과를 XML로 만들고, XSL을 이용 하여 CruseControl.NET에 출력한다.
  • 12.
  • 14.
  • 15.
    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)); }
  • 16.
    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); }
  • 17.
  • 18.
    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)); }
  • 19.
    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 { } … };
  • 20.
    , 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 } };
  • 21.
    , 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; } };
  • 22.
    , 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()); }
  • 23.
    , template <class Type> classGTestMgrWrapper : public MInstanceChanger<Type> { public: GTestMgrWrapper() : MInstanceChanger() { m_pOriginal = gmgr.Change(m_pTester); } ~GTestMgrWrapper() { gmgr.Change(m_pOriginal); } };
  • 24.
    , 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()); }
  • 25.
    Refactoring Test Code •Mock Object • override • Google C++ Mocking Framework!
  • 26.
    Refactoring Test Code •UnitTestHelper – Helper . – ) GUTHelper_NPC::SpawnNPC()
  • 27.
    Refactoring Test Code • Fixture . class FBasePlayer; class FBaseItem; class FBaseNPC; class FBaseMap; class FBaseNetClient;
  • 28.
    Refactoring Test Code • Fixture . class FForCombatTest : public FBaseMockLink, public FBaseNetClient, public FBasePlayer, public FBaseMap, public FBaseMapMgr, public FBasePlayer { … };
  • 29.
    Refactoring Test Code ClassFduel // 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()); } }
  • 30.
    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(); }
  • 31.
    Database Unit Test •저장 프로시저, 트리거 등에 대한 유닛 테스 트 • xDBUnit 프레임워크가 있지만 자체적으로 작성했다. – UnitTest++ 사용
  • 32.
    Database Unit Test •테스트 단계 1. SandBox에 데이터베이스, Table, SP 등 생성 2. 테스트에 필요한 데이터 집합(Seed Dataset) 을 생성 3. 테스트 케이스 실행 4. 데이터 변경 검증
  • 33.
  • 34.
    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()); }
  • 35.
    • 특정 씬을렌더링하여 원본 이미지와 같은 이미지인지 픽셀별로 비교하여 같은 픽셀 값인지 테스트 • 렌더링에 대한 UnitTest를 만들지 못하여 나온 대안 • 랜덤 요소 제거 등의 추가 작업이 필요함
  • 36.
  • 37.
    Recv Replay Send Packet Queue Command Queue Packet Local Local Event 복사 Replay Queue 커맨드 구조 ID Data
  • 38.
    Resource Validator • 기획자나아티스트가 작업한 게임 데이터 (Assets)에 논리적으로 잘못된 값이 입력되 었는지 검증 • 예시 – 상점 인터랙션이 설정된 NPC는 비전투형인가? – 아이템 판매 가격이 구매 가격보다 높은가? – 몬스터에 설정된 스킬 애니메이션 파일이 존재하는가? – 맵의 포탈에 연결된 맵이 실재로 존재하는가?
  • 39.
  • 40.
  • 41.
    Runtime Validator • • – DB – – AI – Assertion
  • 42.
  • 43.
    XML . – • , , , – • • Crash
  • 44.
    AI • • – Crash –
  • 45.
  • 46.
  • 47.
    Crash Dump Analyzer • • •WinDbg Command-Line •
  • 48.
  • 49.
  • 50.
  • 51.
  • 53.
    Q/A birdkr@gmail.com http://mypage.sarang.net