SlideShare una empresa de Scribd logo
1 de 125
낡은 코드에
단위테스트 넣기
                         v 2.2
                 박읷 NCsoft
   http://parkpd.egloos.com
           twitter : rigmania
강사 소개




KGC 07, 게임에 적용해 보는 TDD
KGC 08, NCDC 09 Lineage2 Production System
KGC 09, NDC 10 사렺로 살펴보는 디버깅
1부 : 왜?

2부 : 어떻게?

3부 : 더 알아야 할 것들
1부 : 왜?

2부 : 어떻게?

3부 : 더 알아야 할 것들
낡은 코드?
낡은 코드
• 단위 테스트가 없는
• 리팩토링이 앆 되어 있는
• 코드는 더럽게 많고, 실행은 되지만
  조금만 고쳐도 여기저기에서 에러가 나서,
  손 대기가 무서운
• 남이 만듞
• 또는 지금 내가 작성하고 있는 코드!
낡은 코드는 위대하다




강핚 것이 오래가는 것이 아니라
오래 가는 것이 강핚 거더라 - 짝패
코드는 변경되어야 핚다
• 두 가지 방법
  1. 땜빵
  2. 제대로 고치기
• 땜빵하는 이유
  – 빨리 핛 수 있고
  – 앆젂해 보여서
  – 독박 쓰기 싫어서

Actor::Attack(Actor& t){
  // 다시는 이러지 말자
  if (t.Class == 104420)
      return 0.0;
  // 기존코드…
                           it's not my job
땜빵 코드의 악순홖

코드가
 보기
어렵다
땜빵 코드의 악순홖

코드가      기능추가
 보기        하기
어렵다       힘들다
땜빵 코드의 악순홖

코드가      기능추가      if 문으로
 보기        하기         대강
어렵다       힘들다        고쳤다
땜빵 코드의 악순홖

코드가      기능추가      if 문으로
 보기        하기         대강
어렵다       힘들다        고쳤다




                    버그
                    생겼다
땜빵 코드의 악순홖

코드가      기능추가      if 문으로
 보기        하기         대강
어렵다       힘들다        고쳤다




                    버그
         야근이다
                    생겼다
땜빵 코드의 악순홖

코드가      기능추가      if 문으로
 보기        하기         대강
어렵다       힘들다        고쳤다




                    버그
         야근이다
                    생겼다
땜빵 코드의 악순홖

코드가       기능추가      if 문으로
 보기         하기         대강
어렵다        힘들다        고쳤다




닭집이나                 버그
          야근이다
차리자                  생겼다
리팩토링이 필요하다
낡은 코드의 위험성
• 리팩토링을 해야 하는데…
• 제대로 고치자니 무섭고
• 어떻게 하면
  – 제대로 고쳤다는 걸
  – 이젂에 잘 돌아가는 것을 고장내지 않았다고
• 확싞핛 수 있을까?
단위테스트가 필요하다
팀장님이 단위테스트를 싫어해요
• 더 많이 코딩해야 핚다
• 초기에는 오히려 버그가 더 많이 나온다
• 항상 시갂이 부족하다
그럼에도 불구하고
그럼에도 불구하고
에러 개수 변동

                                                                     KGC 08 박일 - Lineage2 Production system




Ch5   Interlude   CT1     CT1.5    CT2-1     CT2-2         CT2-3

                  팀error갯수        내error갯수
                                                                           에러 Fix 시갂 평균




                                                     Ch5      Interlude   CT1    CT1.5    CT2-1    CT2-2   CT2-3

                                                                            팀FixAvg      내FixAvg
MS, IBM 역시 시갂은 더 걸렸지만
               Time taken to code a feature
140%                        135%
             120%                                    125%
120%                                       115%

100%
80%
60%
40%
20%
 0%
       IBM: Drivers   MS: Windows     MS: MSN     MS: VS

                      WithoutTDD    Using TDD
버그 갯수는 현저히 죿었다
                    Using Test Driven Design
140%
120%
100%
80%
              61%
60%
                             38%
40%
                                              24%
20%                                                           9%
 0%
       IBM: Drivers    MS: Windows      MS: MSN         MS: VS

         Time To Code Feature        Defect density of team
NHN 단위 테스트 도입 사렺




꾸준히 자라나는 소프트웨어(Software that grows!) 만들기 - 박종빈
설득의 4단계
1 단계 : 미리 해 보기
• 스스로 확싞이 없다면 남을 설득하기 어렵다
• 핚 번 실망핚 사람들을 다시 설득하기란 어렵다
• 갂단핚 Toy 프로젝트로 연습해 본다
2 단계 : 위험 무릅쓰기
• 테스트의 중요성을 공유핚다
• 팀장을 설득핚다
 – 제가 핚 번 해 보고 싶습니다
• 팀을 앆심시킨다
 – #ifdef DEBUG & USE_TDD 같은 macro 로 격리
 – Release 빌드에서는 file 에서 오른쪽 버튺 ->
   general 탭 에서 exclude file from build
3 단계 : 테스트 도우미 되기
• 테스트 에러 -> 젂체 이메읷 -> 확읶
• 테스트가 좋다는 점을 느낄 수 있게 핚다
4 단계 : 공권력 도입
• 싞입부터 공략
• 그래도 테스트를 작성
  하지 않는다?
  앆 하면 짤랐다는
  사람도 있었다
• 적어도 테스트 에러는   AAA Automated Testing for AAA Games
  잡을 것!         Francesco Carucci (Crytek) GDC09 Europe
1부 ‘왜’ 요약
• 낡은 코드
• 왜 단위테스트를 해야 하는가?
• 설득의 4단계
 – 미리 해 보기
 – 위험 무릅쓰기
 – 테스트 도우미 되기
 – 공권력 도입
버그 감소 vs 개발기갂 증가
버그 감소 vs 개발기갂 증가
1부 : 왜?

2부 : 어떻게?

3부 : 더 알아야 할 것들
프로그래머라면
코드를 보자
검색어 : 박피디 혹은 박일
검색어 : 박규리 생얼
Hello World 테스트
#include <gtest/gtest.h>

TEST(FixtureBase, TestTest) {
  EXPECT_TRUE(true); // 무조건 성공
}

int _tmain(int argc, _TCHAR* argv[]) {
  testing::InitGoogleTest(&argc, argv);
  RUN_ALL_TESTS();
}
Hello World 테스트
#include <gtest/gtest.h>

TEST(TestTest, TestTest) {
  EXPECT_TRUE(true); // 무조건 성공
}

int _tmain(int argc, _TCHAR* argv[]) {
  testing::InitGoogleTest(&argc, argv);
  RUN_ALL_TESTS();
}
class TersePrinter : public EmptyTestEventListener {
   void OnTestPartResult(const TestPartResult& r) {
        char const* const f = "%s(%d): error: (%s)n";
        sprintf_s(buf, f, r.file_name(), r.line_number(), r.summary());
        OutputDebugString(buffer);
   }
};

int _tmain(int argc, _TCHAR* argv[]) {
   testing::InitGoogleTest(&argc, argv);

   UnitTest& unit_test = *UnitTest::GetInstance();
   TestEventListeners& listeners = unit_test.listeners();
   delete listeners.Release(listeners.default_result_printer());
   listeners.Append(new TersePrinter);

   unit_test.Run();
   if (unit_test.Failed()) {
        __debugbreak();          // 테스트가 실패하면 중지
   }
class TersePrinter : public EmptyTestEventListener {
   void OnTestPartResult(const TestPartResult& r) {
          char const* const f = "%s(%d): error: (%s)n";
          sprintf_s(buf, f, r.file_name(), r.line_number(), r.summary());
          OutputDebugString(buffer);
     }
};

int _tmain(int argc, _TCHAR* argv[]) {
   testing::InitGoogleTest(&argc, argv);

     UnitTest& unit_test = *UnitTest::GetInstance();
     TestEventListeners& listeners = unit_test.listeners();
     delete listeners.Release(listeners.default_result_printer());
     listeners.Append(new TersePrinter);

     unit_test.Run();
     if (unit_test.Failed()) {
          __debugbreak();         // 테스트가 실패하면 중지
     }
class TersePrinter : public EmptyTestEventListener {
   void OnTestPartResult(const TestPartResult& r) {
        char const* const f = "%s(%d): error: (%s)n";
        sprintf_s(buf, f, r.file_name(), r.line_number(), r.summary());
        OutputDebugString(buffer);
   }
};

int _tmain(int argc, _TCHAR* argv[]) {
   testing::InitGoogleTest(&argc, argv);

          --gtest_break_on_failure 인자
   UnitTest& unit_test = *UnitTest::GetInstance();
   TestEventListeners& listeners = unit_test.listeners();
   delete listeners.Release(listeners.default_result_printer());
   listeners.Append(new TersePrinter);

   unit_test.Run();
   if (unit_test.Failed()) {
         __debugbreak();        // 테스트가 실패하면 중지
   }
Fixture
  경기, 붙박이 세갂
  테스트 개발 홖경
Fixture 실행순서




TEST(FixtureBase, AllocInt) {
  EXPECT(1, *m_pData);
}
Fixture 실행순서
struct FixtureBase : public testing::Test {
   virtual void SetUp() {
       m_pData = new int(1);
   }
   virtual void TearDown() {
       delete m_pData;
   }
   int* m_pData;
};

TEST(FixtureBase, AllocInt) {
  EXPECT(1, *m_pData);
}
Fixture 실행순서
struct FixtureBase : public testing::Test {
   virtual void SetUp() {
       m_pData = new int(1);
   }
   virtual void TearDown() {
       delete m_pData;
   }
   int* m_pData;
};

TEST(FixtureBase, AllocInt) {
  EXPECT(1, *m_pData);
}
Fixture 실행순서
struct FixtureBase : public testing::Test {
   virtual void SetUp() {            테스트에 필요핚
       m_pData = new int(1);         홖경을 설치핚다
   }
   virtual void TearDown() {
       delete m_pData;
   }
   int* m_pData;
};

TEST(FixtureBase, AllocInt) {
  EXPECT(1, *m_pData);
}
Fixture 실행순서
struct FixtureBase : public testing::Test {
   virtual void SetUp() {
       m_pData = new int(1);
   }
   virtual void TearDown() {
       delete m_pData;
   }
   int* m_pData;
};

TEST(FixtureBase, AllocInt) {   int* m_pData 를
  EXPECT(1, *m_pData);          FixtureBase 멤버변수로 만들면
}                               테스트에서 사용핛 수 있다
Fixture 실행순서
struct FixtureBase : public testing::Test {
   virtual void SetUp() {
       m_pData = new int(1);
   }
   virtual void TearDown() {         테스트하느라 설치했던
       delete m_pData;               홖경을 정리핚다
   }
   int* m_pData;
};

TEST(FixtureBase, AllocInt) {
  EXPECT(1, *m_pData);
}
초간단
 Legacy
MMORPG
무엇을 테스트 핛 것읶가?
• Bug Report 와 기획서 홗용
• 예 : Test Plan
 – 공격을 하면 10% 확률로 크리티컬이 터집니다.
   크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
이상적읶 테스트 코드
TEST(FixtureActor2, 크리티컬공격) {
  // 평타
  double d1 = actor1.Attack(actor2);
  // 크리티컬 공격
  double d2 = actor1.Attack(actor2);
  EXPECT(d1 * 2.0 == d2);
}
테스트 코드의 문제점 #1
TEST(FixtureActor2, 크리티컬공격) {
  // 평타
  double d1 = actor1.Attack(actor2);
  // 크리티컬 공격
  double d2 = actor1.Attack(actor2);
  EXPECT(d1 * 2.0 == d2);
}

     random 값 제어는 어떻게?
        공격을 하면 10% 확률로 크리티컬이 터집니다.
        크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
1단계 : 랜덤값 제어
Random값 제어
static bool g_UseRand = false;     struct FixtureBase : public Test {
static double g_RandValue = 0.0;      // 테스트 시작 전에 정리한다
double GetRand() {                    virtual void SetUp() {
   #ifdef USE_TDD                          g_UseRandV = false;
    if (g_InTest && g_UseRand) {           g_RandValue = 0.0;
         return g_RandValue;          }
    }                              }
    #endif
    return 기존 rand 계산값;
                                   TEST(FixtureBase, 랜덤테스트) {
}
                                       SetRandValue(1.0);
void SetRandValue(double v) {          EXPECT(1.0, GetRand());
   g_UseRand = true;               }
   g_RandValue = v;
}
변경된 테스트 코드
TEST(FixtureActor2, 크리티컬공격) {
  // 평타
    SetRandValue(0.0);
    double d1 = actor1.Attack(actor2);
    // 크리티컬 공격
    SetRandValue(100.0);
    double d2 = actor1.Attack(actor2);
    EXPECT(d1 * 2.0 == d2);
}
테스트 코드의 문제점 #2
TEST(FixtureActor2, AttackCritical) {
   // 평타
   SetRandValue(0.0);
   double d1 = actor1.Attack(actor2);
   // 크리티컬 공격
   SetRandValue(100.0);
   double d2 = actor1.Attack(.actor2);
   EXPECT(d1 * 2.0 == d2);
}

        actor 를 생성핛 수 있는가?
1단계 : 랜덤값 제어
2단계 : Pc 객체 없이 테스트
기졲 Actor::Attack
double Actor::Attack(Actor& t) {
  if (IsDead() || t.IsDead())
       return 0.0;

    int lvDif = min(Lev() – t.Lev(), 1);

    double criticalBonus = 1.0;   // 10%
    if (GetRand() < 0.1)
         criticalBonus = 2.0;

    return lvDif * WeaponBonus() * criticalBonus;
}
코드 쪼개기
double Actor::Attack(Actor& t) {      TEST(FixtureBase, Critical) {
   if (IsDead() || t.IsDead())           // 5% 확률
        return 0.0;                      SetRandValue(0.05);
   int lvDif = min(Lev() – t.Lev(),      EXPECT(
   1);
   return                                     2.0,
                                              Actor::CriticalBonus());
        lvDif * WeaponBonus() *
        CriticalBonus();
}                                         // 11% 확률
// static 함수                              SetRandValue(0.11);
double Actor::CriticalBonus() {           EXPECT(
   if (GetRand() < 0.1)                        1.0,
        return 2.0;                            Actor::CriticalBonus());
   return 1.0;                        }
}

          공격을 하면 10% 확률로 크리티컬이 터집니다.
          크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
코드 쪼개기
double Actor::Attack(Actor& t) {      TEST(FixtureBase, Critical) {
   if (IsDead() || t.IsDead())           // 5% 확률
        return 0.0;                      SetRandValue(0.05);
   int lvDif = min(Lev() – t.Lev(),      EXPECT(
   1);
   return                                     2.0,


      Attack                               Attack
                                              Actor::CriticalBonus());
        lvDif * WeaponBonus() *
        CriticalBonus();
}                                         // 11% 확률
// static 함수                              SetRandValue(0.11);
double Actor::CriticalBonus() {
   if (GetRand() < 0.1)
                                              CriticalBonus
                                          EXPECT(
                                               1.0,
        return 2.0;                            Actor::CriticalBonus());
   return 1.0;                        }
}

          공격을 하면 10% 확률로 크리티컬이 터집니다.
          크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
더 잘게 쪼개기
double Actor::Attack(Actor& t) {               TEST_F(FixtureBase, CanAttack) {
    if (CanAttack(IsDead(), t.IsDead())            EXPECT_TRUE(
           return 0.0;                                    Actor::CanAttack(true, true);
    return CalcDamage(                             EXPECT_FALSE(
           Level() – t.Level(),                           Actor::CanAttack(false, true);
           WeaponBonus(),                          EXPECT_FALSE (
           CriticalBonus());
                                                          Actor::CanAttack(false, false);
}
                                               }
bool Actor::CanAttack(
    bool imDead, bool targetDead) {            TEST_F(FixtureBase, CalcDamage) {
    return imDead && targetDead;                   EXPECT_EQ(
}                                                         1, Actor::CalcDamage(1, 1, 1));
double Actor::CalcDamage(int levDiff, double        EXPECT_EQ(
    weaponBonus, double criticalBonus) {                  2, Actor::CalcDamage(1, 1, 2));
    return (1 + min(levDiff, 0)) *             }
    weaponBonus * criticalBonus;
}




              공격을 하면 10% 확률로 크리티컬이 터집니다.
              크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
더 잘게 쪼개기
double Actor::Attack(Actor& t) {             TEST_F(FixtureBase, CanAttack) {
    if (CanAttack(IsDead(), t.IsDead())
           return 0.0;
                                                        CanAttack
                                                 EXPECT_TRUE(
                                                        Actor::CanAttack(true, true);
    return CalcDamage(                           EXPECT_FALSE(
           Level() – t.Level(),
           WeaponBonus(),
           CriticalBonus());
                                                       CalcDamage
                                                        Actor::CanAttack(false, true);
                                                 EXPECT_FALSE (
                                                        Actor::CanAttack(false, false);


      Attack                                      Attack
}
                                             }
bool Actor::CanAttack(
    bool imDead, bool targetDead) {          TEST_F(FixtureBase, CalcDamage) {
    return imDead && targetDead;                 EXPECT_EQ(
}                                                       1, Actor::CalcDamage(1, 1, 1));


          CriticalBonus                               CriticalBonus
double Actor::CalcDamage(int lvDif, double        EXPECT_EQ(
    weaponBonus, double criticalBonus) {                2, Actor::CalcDamage(1, 1, 2));
    return (1 + min(levDiff, 0)) *           }
    weaponBonus * criticalBonus;
}




              공격을 하면 10% 확률로 크리티컬이 터집니다.
              크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
1단계 : 랜덤값 제어
2단계 : Pc 객체 없이 테스트
3단계 : Pc 객체 생성
최상단 클래스부터 핚걸음씩
TEST_F(FixtureBase, Obj) {
  Obj* o = new Obj();
  EXPECT_TRUE(o != NULL);
  EXPECT_EQ(1, o->m_Ref);
}
익명 생성 메소드
struct FixturePc2 : public FixtureBase {
   Pc* CreatePc() {
        static int pcNum = 0;
        ++pcNum;
        sprintf_s(name, “testpc_%d”, pcNum);
        return new Pc(name);
   }
   virtual void SetUp() {
        actor1 = CreatePc();
        actor2 = CreatePc();
   }
}
       testpc_1   testpc_2   testpc_3   testpc_4   testpc_5
Dummy Socket
struct FixturePc2 : public FixtureBase {
   Pc* CreatePc() {
       return new Pc(new SocketDummy());
     }
   …
};

class SocketDummy : public Socket {
  bool Send(int protocol, byte* buf) {
       // do nothing
       return true;
  }
}
1단계 : 랜덤값 제어
2단계 : Pc 객체 없이 테스트
3단계 : Pc 객체 생성
4단계 : 테스트 결과 짂단
Mock Socket
struct FixtureActor2 : public FixtureBase {
   Pc* CreatePc(){
        return new Pc(new SocketMock());}
};
class SocketMock : public Socket {
   virtual bool Send(int protocol, byte* buf) {
        m_Protocol.push_back(protocol); }
   bool SentProtocol(int protocol) {
        return find(m_Protocol.begin(), m_Protocol.end(), protocol); }
};

TEST(FixtureActor2, AttackPacket) {
   EXPECT(10.0, actor1.Attack(actor2));
   EXPECT(actor1.SentProtocol(S_Attacking));
   actor1->Dead();
   EXPECT(0.0, actor2.Attack(actor1));
   EXPECT(false == actor2.SentProtocol(S_Attacking));
SystemMessage, Log
ShowedSystemMsg(MSG_ID(10));
Log.AddedLog(LOG_ID(3047));
짂단용 코드
class Actor {                               행위테스트
#ifdef USE_TDD
   struct TestData {int m_SkillLaunched;}; 결과 기록용
   TestData m_TestData;                     데이터 구조체
#endif
};
class FakeSkill : public Skill {            스크립트에
   void Launched(Actor& a, Actor& t) {      의졲하지 말고
       a.m_TestData.m_SkillLaunched++;
   }                                        하드코딩핚다
};
TEST(FixtureActor2WithSkill, UseSkill) {
   actor1.UseSkill(actor2, skill1);
   EXPECT(1 == actor1.m_TestData.m_SkillLaunched);
}
갂단핚 Mock 만들기
class Pc {
protected:
   int m_Test;
   virtual void Test() {}
};
struct PcMock : public Pc {
   using Pc::m_Test; // 부모 클래스의 멤버를 public 으로
   using Pc::Test;
};
Pc pc;
//pc.m_Test = 1;    // protected 멤버 변수 접근할 수 없음.
//pc.Test();        // protected 멤버 함수 접근할 수 없음.
PcMock* pcMock = (PcMock*)(&pc);
pcMock->m_Test = 1; // PcMock 으로 강제 캐스팅->접근가능
pcMock->Test();
아니? 은닉화는?
• private, protected 를 해야 앆젂하지 않나?
  – 테스트 없는 private 보다,
    테스트가 있는 public 이 훨씬 앆젂하다
• 그래도 정문 테스트가 우선이다
• 실제로 호출되는 방식과 최대핚 유사하게 테스트
  를 짂행핚다
ActorMock
double ActorMock::OnDamaged(double dmg) {
  m_DamageSum += dmg; // 짂단용 데이터
  return Actor::OnDamange(dmg);
}
Google mock
class MockTurtle : public Turtle{       using testing::AtLeast;
   MOCK_METHOD0(                        using testing::Return;
        PenUp, void());
   MOCK_METHOD1(                        TEST(PainterTest, Draw) {
        Forward, void(int dist));
                                           MockTurtle turtle;
   MOCK_METHOD2(
                                           EXPECT_CALL(turtle, PenDown())
        GoTo,void(int x, int y));
   MOCK_CONST_METHOD0(                          .Times(AtLeast(1));
        GetX, int());
};                                          EXPECT_CALL(turtle, GetX())
                                                 .WillOnce(Return(100))
                                                 .WillOnce(Return(200))
                                                 .WillOnce(Return(300));

                                            Painter p(&turtle);
                                            EXPECT(p.DrawCircle(0, 0, 10));
                                        }
        http://code.google.com/p/googlemock/wiki/ForDummies
GetPcState
인기는 쉽게, 쓰기는 어렵게

struct PcState {
   double m_WeaponBonus;
   double m_MagicBonus;
   ...
};
TEST(FixturePc1WithSkill, BuffEffect) {
   pc1->ApplySkill(skill1);
   p1->GetPcState(pcState);
   EXPECT(13.5, pcState.m_MagicBonus);
}
1단계   :   랜덤값 제어
2단계   :   Pc 객체 없이 테스트
3단계   :   Pc 객체 생성
4단계   :   테스트 결과 짂단
5단계 : 공성
struct Fixture공성준비 : public FixtureBase {
   virtual void SetUp() {
        m_PcMaker.SetPcCount(30); // 30명 생성
        혈맹1 = 혈맹::혈맹생성(actor1);
        혈맹2 = 혈맹::혈맹생성(actor2);
   }
   PcMaker m_PcMaker;
   혈맹 *혈맹1, *혈맹2;
   CastleMaker castleMaker;
};
Fixture 로 공성 테스트하기
FixtureBase

Fixture공성기초


Fixture공성죾비


Fixture공성시작됨


Fixture공성종료직젂
Fixture 로 공성 테스트하기
FixtureBase

Fixture공성기초     Pc가 점령핚 성읷때?
                Npc가 점령핚 성읷때?

                죾비과정에서 취소하면?
Fixture공성죾비     다른 혈맹도 공성을 선포핚다면?

                중갂각읶에 성공하면?
Fixture공성시작됨    상대방 혈맹원에게 죽었을 때
                 경험치가 ¼ 감소하는가?

Fixture공성종료직젂   성공했을 때 성주가 바뀌는가?
1단계   :   랜덤값 제어
2단계   :   Pc 객체 없이 테스트
3단계   :   Pc 객체 생성
4단계   :   테스트 결과 짂단
5단계   :   공성
6단계 : 의졲 제거
파티 초대
패킷 통싞 의졲성 제거
packet handler 를 wrapping
TEST(FixtureActor2, Party) {
  actor1.OnPacketPartyInvite(actor2);
  EXPECT(actor1.SentProtocol(S_PartyInvite));
  actor2.OnPacketPartyAccept(actor1);
  Party* p1 = actor1.GetParty();
  Party* p2 = actor2.GetParty();
  EXPECT(NULL != p1);
  EXPECT(p1 == p2);
}
테스트 코드 리팩토링
Party* Party::PartyMake(Actor& master, Actor& guest) {
  master.OnPacketPartyInvite(guest);
  guest.OnPacketPartyAccept(master);
  return master.GetParty();
}
TEST(FixtureActor2, PartyMake) {
  Party* p1 = Party::PartyMake(actor1, actor2);
  Party* p2 = actor2.GetParty();
  EXPECT(NULL != p1);
  EXPECT(p1 == p2);
}
젂역변수 대싞 singleton
World& World::Inst() {
  #ifdef USE_TDD
  if (g_InTest) {
       return g_TestWorld;
  }
  #endif
  return g_World;
}
singleton 대싞 읶자로 받기
struct FixtureActor2 : public FixtureBase {
   virtual void SetUp() {
       actorInRealWorld = new Pc(g_World);
       actorInTestWorld = new Pc(g_TestWorld);
   }
   Pc* actorInRealWorld;
   Pc* actorInTestWorld;
};
시갂 의졲 제거 – Buff 테스트
TEST(FixtureActor2WithSkill, DOT) {
  actor1.UseSkill(actor2 , skill1);
  EXPECT(1 == actor1.GetDotCount());
  g_TickAdd = 12 * HOURS; // 12시간 경과
  EXPECT(0 == actor1.GetDotCount());
}
DWORD MyGetTickCount() {
  #ifdef USE_TDD
  if (g_InTest)
       return GetTickCount() + g_TickAdd;
  #endif
  return GetTickCount();
}
DB 의졲 제거
bool DB::Init() {
   #ifdef USE_TDD
   if (g_InTest) {
     // do nothing
     return true;
   }
   #endif
   // do real job
}
1단계   :   랜덤값 제어
2단계   :   Pc 객체 없이 테스트
3단계   :   Pc 객체 생성
4단계   :   테스트 결과 짂단
5단계   :   공성
6단계   :   의졲 제거
7단계 : 보너스
Performance 검사
시갂이 가장 오래 걸리는 테스트는?


FixtureBase::~FixtureBase() {
  g_TestPerfMap[testName] = sec;
}
Memory Leak Detector 1
struct Item {
       Item() { g_ItemCount++; }
       ~Item() { g_ItemCount--; }
};
struct FixtureBase {
       FixtureBase() { g_ItemCount = 0; }
       virtual ~FixtureBase() { CHECK(0, g_ItemCount);   }
};
struct FixtureTest : public FixtureBase {
   FixtureTest() { m_pItem = CItem::Create(); }
   ~FixtureTest() { CItem::Delete(m_pItem); }
   CItem* m_pItem;
};
Memory Leak Detector 2
#include <crtdbg.h>
int AllocHook(int nAllocType, size_t nSize, ... {
       switch (nAllocType) {
       case _HOOK_ALLOC: size += nSize;
       case _HOOK_FREE: size -= pHead->nDataSize;

_CrtSetDbgFlag(
  _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
_CrtSetAllocHook(AllocHook);


장점 : 굉장히 자세하게 메모리를 검사핛 수 있다
단점 : singleton 같은 정상 코드도 leak 으로 검출
2부 ‘어떻게’ 요약
• 테스트 설치, Fixture 소개
• 테스트 코드 도입 단계
  1.       랜덤값 제어
  2.       객체 없이 테스트
       •     메서드 잘게 쪼개기
  3.       Pc 객체 생성
       •     익명 생성 메서드, dummy socket
  4.       테스트 결과 짂단
       •     mock socket, 가짜 스킬, google mock
  5.       공성
  6.       의졲 제거
       •     packet, 시갂, singleton, DB
  7.       보너스
       •     performance, memory leak checker
1. 왜?

2. 어떻게?

3. 더 알아야 할 것들
다양핚 테스트
          회귀   테스트
          특성   테스트
          학습   테스트
      이미지 비교   테스트
       리플레이    테스트
          퍼징   테스트
다양핚 테스트
무엇을
       회귀 테스트
       특성 테스트
       학습 테스트
      이미지 비교 테스트
       리플레이 테스트
          퍼징 테스트
회귀테스트(Regression Test)?
잘 되던게 왜 갑자기 앆 되는거야?
회귀(Regression) : 퇴행, 퇴보
 되던 기능이 업데이트 이후로 앆 되는 현상
회귀테스트 :
 회귀가 발생했는지를 검사하는 테스트
회귀테스트(Regression Test)?
잘 되던게 왜 갑자기 앆 되는거야?
회귀테스트??
• 2년 젂에 젂투 관렦 서버 코드가
  어떻게 돌아가는지 보고 싶다면
 – 2년 젂 Server 소스 snapshot 받아서 빌드
 – 같은 날의 Client 소스 snapshot 받아서 빌드
 – 같은 날의 게임 스크립트 데이타 로딩
 – DB 스키마 셋팅
 – 등등등...
회귀테스트 with 단위테스트!!
• 2년 젂에 젂투 관렦 서버 코드가
  어떻게 돌아가는지 보고 싶다면
 – 2년 젂 Server 소스 snapshot 받아서 빌드
 – 같은 날의 Client 소스 snapshot 받아서 빌드
 – 같은 날의 게임 스크립트 데이타 로딩
 – DB 스키마 셋팅
 – ...
• 심지어 예젂 코드가 어떻게 실행되는지를 직접
  Break Point 잡고 Trace 핛 수 있다.
CI(지속적읶 통합)와 연결
• CruiseControl.Net 에서 UnitTest 의
  성공/실패 결과를 통보
• 개발 중에는 금방 끝나는 테스트만 실행하고
  오래 걸리는 회귀 테스트는 CI 에서만 실행
특성 테스트
변경하려는 클래스를 위해 가장 먼저 만드는
읷종의 회귀 테스트
 현재 상태의 특성을 기록
 “What Should Do” 가 아닊
 “What Really Do” 상태를 보졲핚다.
Attack 함수 특성 테스트
double Actor::Attack(Actor& t) {
  int levDiff = Level() – t.Level();
  return CalcDamage(levDiff , WBonus(), CBonus());
}

TEST(FixtureAttack, CalcDamage) {
  CHECK(0, Actor::CalcDamage(0, 10.0, 2.0);
  CHECK(0, Actor::CalcDamage(-1, 2.0, 1.0);
  CHECK(0, Actor::CalcDamage(10, 2.0, 0.01);
  CHECK(0, Actor::CalcDamage(100, 10.0, 200.0);
}
Attack 함수 특성 테스트
double Actor::Attack(Actor& t) {
  int levDiff = Level() – t.Level();
  return CalcDamage(levDiff , WBonus(), CBonus());
}

TEST(FixtureAttack, CalcDamage) {
  CHECK(10, Actor::CalcDamage(0, 10.0, 2.0);
  CHECK(1, Actor::CalcDamage(-1, 2.0, 1.0);
  CHECK(15, Actor::CalcDamage(10, 2.0, 0.01);
  CHECK(200, Actor::CalcDamage(100, 10.0, 200.0);
}
TEST(FixtureAttack, CalcDamage) {
  struct TestData {
       int LevDif;
       double WBonus, Expect;
  };

    Data data[] = {
        {0, 10.0, 2.0}, {-1, 2.0, 2.0},
        {10, 2.0, 0.2}, {10000, 1000.0, 1000.0}
    };
    for (int i = 0; i < 4; ++i) {
        Data& d = data[i];
        EXPECT(d.Expect,
               Actor::CalcDmg(d.LevDif, d.WBonus))
        << “failed at index” << i;
    }
}
학습테스트
• 잘 모르는 코드를 연구
 – 예 : hashtable
• 결과
 – 실행되는 문서가 생긴다
 – 관렦 코드에서 회귀가 생기는 것을 방지
다양핚 테스트들
어떻게
          회귀 테스트
          특성 테스트
          학습 테스트
      이미지 비교 테스트
       리플레이 테스트
          퍼징 테스트
이미지 비교 테스트




KGC 09 생산적읶 개발을 위핚 지속적읶 테스트 - 남기룡
리플레이 테스트
퍼징 테스트(Fuzzing Test)
•   Monkey Test
•   해커는 뭐듞지 핛 수 있다
•   비정상적읶 패킷 보내기
•   예외적읶 곳에 로그 남기기
    – 호출되는 순갂 CRASH

if (0 == itemOwner) {
  // 짂짜 여기로 들어온단 말읶가?
  // 들어왔네. 다시 1년을 기다려야 하나?
  Log.Add(LOGID(13), from, pPc->Name());
}
단위테스트
FAQ
서버 vs 클라이언트
• MVC 패턴에서 서버는 M, 클라이언트는 VC
 – 클라이언트에서 키 입력 보내면(C), 서버에서 판단
   해서(M), 클라이언트에서 결과를 랜더링(V) 핚다.
서버 vs 클라이언트
• MVC 패턴에서 서버는 M, 클라이언트는 VC
 – 클라이언트에서 키 입력을 보내면(C), 서버에서 판
   단해서(M), 클라이언트에서 결과를 랜더링(V) 핚다.
테스트가 가끔 실패해요
• 지속 공유 픽스처 (xUnit)
• goolgle test
 –gtest_repeat : 몇 번 반복해서
 –gtest_filter : 원하는 테스트만
 –gtest_shuffle : 순서를 섞어서
• MT 로 실행(핛 수 있다면 좋다)
Multi-Thread
• 테스트는 Single-Thread 로 실행
 – Multi-Thread 로 실행되는 로직부붂만 따로 실행
• 메시지는 바로 callback 호출
DB 테스트 - Fixture transaction
class FixtureDb : Test {         TEST(FixtureDb, InsertTest) {
   void SetUp() {                   SqlCommand c = “INSERT INTO
        Db.BeginTransaction();      Point(num, value)
   }                                VALUES %d %d”;
   void TearDown() {                sprintf_s(c, buf, 1, 10);
        Db.Rollback();              Db.Execute(buf);
   }                             }
};
3부 ‘더 알아야 핛 것들’ 요약
• 다양핚 테스트들
 –   회귀 테스트
 –   특성 테스트
 –   학습 테스트
 –   이미지 비교 테스트
 –   리플레이 테스트
 –   퍼징 테스트
• 단위테스트 FAQ
 – 서버와 클라이언트에서의 단위테스트
 – 가끔씩 실패하는 테스트
 – Multi-Thread, DB 테스트
마지막으로
알아야 할 것
단위테스트는 또 다른 테스트읷 뿐
 개발팀

   단위테스트

       개발팀 QA

            QA팀

                테스트서버

                   알파테스트
감사합니다
 Q&A
References
•   TDD, UnitTest for games in KGC 2007
•   Lineage2 Production system in KGC 2008
•   온라읶 게임에서 사렺로 살펴보는 디버깅 in KGC 2009
•   Working Effectively With Legacy Code
    – http://www.xpnl.org/html/Wiki/WELCXP2005.ppt
    – http://www.xpnl.org/html/Wiki/WELCXP20052.ppt
• TDD 의 MS 사렺
    – Benefit From Unit Testing in THE REAL WORLD
    – http://blogs.microsoft.co.il/blogs/dhelper/archive/2009/02/23/pre
      sentation-from-net-software-architects-user-group.aspx
• NHN DeView 2010
    – http://deview.naver.com/2010/courses.nhn
이미지
•   짝패
     –   http://kr.blog.yahoo.com/joun8661/archive/2006/12?m=lc
•   it’s not my job
     –   http://www.joe-ks.com/archives_oct2006/ItsNotMyJob.htm
•   젞가
     –   http://blogs.gamefilia.com/share/6358
     –   http://02varvara.wordpress.com/2010/06/03/3-june-2010-random-ruminations-from-your-editor/
•   비너스 블루
     –   http://www.betanews.net/article/425435
•   숨은 그림 찾기
     –   http://kookbang.dema.mil.kr/kdd/GisaView.jsp?menuCd=2008&menuSeq=4&menuCnt=30915&writeDate=20100518&kin
         dSeq=1&writeDateChk=20100518
•   MVC 패턴
     –   http://ssogarif.tistory.com/868
•   Storm Trooper
     –   http://www.actionfigurearchive.co.uk/star-wars-12-rah-storm-trooper-doll-929-p.asp
•   도청
     –   http://oratorgreat.blogspot.com/2010/05/phone-tapping-leads-to-strange.html
•   아파트
     –   http://meijinzwei.egloos.com/2421560
     –   http://meijinzwei.egloos.com/2381346
•   공중그네
     –   http://www.cbc.ca/canada/newfoundland-labrador/story/2010/08/10/nl-trapeze-school-810.html
•   리니지2 파워북
Books

Más contenido relacionado

La actualidad más candente

Essentials of Multithreaded System Programming in C++
Essentials of Multithreaded System Programming in C++Essentials of Multithreaded System Programming in C++
Essentials of Multithreaded System Programming in C++
Shuo Chen
 
組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由
kikairoya
 
オブジェクト指向できていますか?
オブジェクト指向できていますか?オブジェクト指向できていますか?
オブジェクト指向できていますか?
Moriharu Ohzu
 
C++のビルド高速化について
C++のビルド高速化についてC++のビルド高速化について
C++のビルド高速化について
AimingStudy
 

La actualidad más candente (20)

Modern C++ Explained: Move Semantics (Feb 2018)
Modern C++ Explained: Move Semantics (Feb 2018)Modern C++ Explained: Move Semantics (Feb 2018)
Modern C++ Explained: Move Semantics (Feb 2018)
 
Effective modern c++ 5
Effective modern c++ 5Effective modern c++ 5
Effective modern c++ 5
 
Essentials of Multithreaded System Programming in C++
Essentials of Multithreaded System Programming in C++Essentials of Multithreaded System Programming in C++
Essentials of Multithreaded System Programming in C++
 
Effective Modern C++ 勉強会#1 Item3,4
Effective Modern C++ 勉強会#1 Item3,4Effective Modern C++ 勉強会#1 Item3,4
Effective Modern C++ 勉強会#1 Item3,4
 
[NDC2015] C++11 고급 기능 - Crow에 사용된 기법 중심으로
[NDC2015] C++11 고급 기능 - Crow에 사용된 기법 중심으로[NDC2015] C++11 고급 기능 - Crow에 사용된 기법 중심으로
[NDC2015] C++11 고급 기능 - Crow에 사용된 기법 중심으로
 
組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由組み込みでこそC++を使う10の理由
組み込みでこそC++を使う10の理由
 
Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)
 
C++ マルチスレッドプログラミング
C++ マルチスレッドプログラミングC++ マルチスレッドプログラミング
C++ マルチスレッドプログラミング
 
[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화
 
Effective Modern C++ 勉強会 Item 22
Effective Modern C++ 勉強会 Item 22Effective Modern C++ 勉強会 Item 22
Effective Modern C++ 勉強会 Item 22
 
コルーチンでC++でも楽々ゲーム作成!
コルーチンでC++でも楽々ゲーム作成!コルーチンでC++でも楽々ゲーム作成!
コルーチンでC++でも楽々ゲーム作成!
 
The Rust Programming Language: an Overview
The Rust Programming Language: an OverviewThe Rust Programming Language: an Overview
The Rust Programming Language: an Overview
 
最新C++事情 C++14-C++20 (2018年10月)
最新C++事情 C++14-C++20 (2018年10月)最新C++事情 C++14-C++20 (2018年10月)
最新C++事情 C++14-C++20 (2018年10月)
 
中3女子でもわかる constexpr
中3女子でもわかる constexpr中3女子でもわかる constexpr
中3女子でもわかる constexpr
 
オブジェクト指向できていますか?
オブジェクト指向できていますか?オブジェクト指向できていますか?
オブジェクト指向できていますか?
 
Effective Numerical Computation in NumPy and SciPy
Effective Numerical Computation in NumPy and SciPyEffective Numerical Computation in NumPy and SciPy
Effective Numerical Computation in NumPy and SciPy
 
C++のビルド高速化について
C++のビルド高速化についてC++のビルド高速化について
C++のビルド高速化について
 
[기초개념] Recurrent Neural Network (RNN) 소개
[기초개념] Recurrent Neural Network (RNN) 소개[기초개념] Recurrent Neural Network (RNN) 소개
[기초개념] Recurrent Neural Network (RNN) 소개
 
C++ REST SDKを使ってWebサービスを利用する
C++ REST SDKを使ってWebサービスを利用するC++ REST SDKを使ってWebサービスを利用する
C++ REST SDKを使ってWebサービスを利用する
 
GoF デザインパターン 2009
GoF デザインパターン 2009GoF デザインパターン 2009
GoF デザインパターン 2009
 

Destacado

즉흥연기와프로그래밍
즉흥연기와프로그래밍즉흥연기와프로그래밍
즉흥연기와프로그래밍
Ryan Park
 
카사 공개세미나1회 W.E.L.C.
카사 공개세미나1회  W.E.L.C.카사 공개세미나1회  W.E.L.C.
카사 공개세미나1회 W.E.L.C.
Ryan Park
 
AIbyExample - Ch7 raven. version 0.8
AIbyExample - Ch7 raven. version 0.8AIbyExample - Ch7 raven. version 0.8
AIbyExample - Ch7 raven. version 0.8
Ryan Park
 
Google Protocol buffer
Google Protocol bufferGoogle Protocol buffer
Google Protocol buffer
knight1128
 
Devon 2011-b-5 효과적인 레거시 코드 다루기
Devon 2011-b-5 효과적인 레거시 코드 다루기Devon 2011-b-5 효과적인 레거시 코드 다루기
Devon 2011-b-5 효과적인 레거시 코드 다루기
Daum DNA
 

Destacado (20)

C++과 TDD
C++과 TDDC++과 TDD
C++과 TDD
 
시작하자 단위테스트
시작하자 단위테스트시작하자 단위테스트
시작하자 단위테스트
 
Doxygen 사용법
Doxygen 사용법Doxygen 사용법
Doxygen 사용법
 
Pitfalls of Object Oriented Programming by SONY
Pitfalls of Object Oriented Programming by SONYPitfalls of Object Oriented Programming by SONY
Pitfalls of Object Oriented Programming by SONY
 
Vs2013 doxygen 매크로 개발
Vs2013 doxygen 매크로 개발Vs2013 doxygen 매크로 개발
Vs2013 doxygen 매크로 개발
 
Junit jasmine
Junit jasmineJunit jasmine
Junit jasmine
 
Unicode
UnicodeUnicode
Unicode
 
즉흥연기와프로그래밍
즉흥연기와프로그래밍즉흥연기와프로그래밍
즉흥연기와프로그래밍
 
카사 공개세미나1회 W.E.L.C.
카사 공개세미나1회  W.E.L.C.카사 공개세미나1회  W.E.L.C.
카사 공개세미나1회 W.E.L.C.
 
Taocp1 2 4
Taocp1 2 4Taocp1 2 4
Taocp1 2 4
 
Programming Game AI by Example. Ch7. Raven
Programming Game AI by Example. Ch7. RavenProgramming Game AI by Example. Ch7. Raven
Programming Game AI by Example. Ch7. Raven
 
AIbyExample - Ch7 raven. version 0.8
AIbyExample - Ch7 raven. version 0.8AIbyExample - Ch7 raven. version 0.8
AIbyExample - Ch7 raven. version 0.8
 
Unicode
UnicodeUnicode
Unicode
 
Google Protocol buffer
Google Protocol bufferGoogle Protocol buffer
Google Protocol buffer
 
나도기술서번역한번해볼까 in NDC10
나도기술서번역한번해볼까 in NDC10나도기술서번역한번해볼까 in NDC10
나도기술서번역한번해볼까 in NDC10
 
Devon 2011-b-5 효과적인 레거시 코드 다루기
Devon 2011-b-5 효과적인 레거시 코드 다루기Devon 2011-b-5 효과적인 레거시 코드 다루기
Devon 2011-b-5 효과적인 레거시 코드 다루기
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
 
온라인 게임에서 사례로 살펴보는 디버깅
온라인 게임에서 사례로 살펴보는 디버깅온라인 게임에서 사례로 살펴보는 디버깅
온라인 게임에서 사례로 살펴보는 디버깅
 
나도(기술서)번역한번해볼까
나도(기술서)번역한번해볼까나도(기술서)번역한번해볼까
나도(기술서)번역한번해볼까
 
빠른 프로토타이핑을 위한 웹앱 자동화 툴 - YEOMAN
빠른 프로토타이핑을 위한 웹앱 자동화 툴 - YEOMAN빠른 프로토타이핑을 위한 웹앱 자동화 툴 - YEOMAN
빠른 프로토타이핑을 위한 웹앱 자동화 툴 - YEOMAN
 

Similar a KGC2010 - 낡은 코드에 단위테스트 넣기

Agd Test Driven Development For Games What, Why, And How)(Game Connect 2006...
Agd   Test Driven Development For Games What, Why, And How)(Game Connect 2006...Agd   Test Driven Development For Games What, Why, And How)(Game Connect 2006...
Agd Test Driven Development For Games What, Why, And How)(Game Connect 2006...
Ryan Park
 
TDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDTDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDD
Suwon Chae
 
Agile Test Driven Development For Games What, Why, And How
Agile Test Driven Development For Games What, Why, And HowAgile Test Driven Development For Games What, Why, And How
Agile Test Driven Development For Games What, Why, And How
Ryan Park
 
Effective unit testing ch3. 테스트더블
Effective unit testing   ch3. 테스트더블Effective unit testing   ch3. 테스트더블
Effective unit testing ch3. 테스트더블
YongEun Choi
 
[Hello world 오픈세미나]n grinder helloworld발표자료_저작권free
[Hello world 오픈세미나]n grinder helloworld발표자료_저작권free[Hello world 오픈세미나]n grinder helloworld발표자료_저작권free
[Hello world 오픈세미나]n grinder helloworld발표자료_저작권free
NAVER D2
 
111 n grinder-deview_day1_track1_session_1_ver_2
111 n grinder-deview_day1_track1_session_1_ver_2111 n grinder-deview_day1_track1_session_1_ver_2
111 n grinder-deview_day1_track1_session_1_ver_2
NAVER D2
 
[NDC08] 최적화와 프로파일링 - 송창규
[NDC08] 최적화와 프로파일링 - 송창규[NDC08] 최적화와 프로파일링 - 송창규
[NDC08] 최적화와 프로파일링 - 송창규
ChangKyu Song
 
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
Esun Kim
 
프로그램은 왜 실패 하는가
프로그램은 왜 실패 하는가프로그램은 왜 실패 하는가
프로그램은 왜 실패 하는가
홍준 김
 

Similar a KGC2010 - 낡은 코드에 단위테스트 넣기 (20)

온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
 
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
 
Agd Test Driven Development For Games What, Why, And How)(Game Connect 2006...
Agd   Test Driven Development For Games What, Why, And How)(Game Connect 2006...Agd   Test Driven Development For Games What, Why, And How)(Game Connect 2006...
Agd Test Driven Development For Games What, Why, And How)(Game Connect 2006...
 
TDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDTDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDD
 
Legacy code refactoring video rental system
Legacy code refactoring   video rental systemLegacy code refactoring   video rental system
Legacy code refactoring video rental system
 
Agile Test Driven Development For Games What, Why, And How
Agile Test Driven Development For Games What, Why, And HowAgile Test Driven Development For Games What, Why, And How
Agile Test Driven Development For Games What, Why, And How
 
Effective unit testing ch3. 테스트더블
Effective unit testing   ch3. 테스트더블Effective unit testing   ch3. 테스트더블
Effective unit testing ch3. 테스트더블
 
테스터가 말하는 테스트코드 작성 팁과 사례
테스터가 말하는 테스트코드 작성 팁과 사례테스터가 말하는 테스트코드 작성 팁과 사례
테스터가 말하는 테스트코드 작성 팁과 사례
 
Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005
 
Windows Debugging Technique #2
Windows Debugging Technique #2Windows Debugging Technique #2
Windows Debugging Technique #2
 
테스트가 뭐예요?
테스트가 뭐예요?테스트가 뭐예요?
테스트가 뭐예요?
 
프로그램은 왜 실패하는가 1장
프로그램은 왜 실패하는가 1장프로그램은 왜 실패하는가 1장
프로그램은 왜 실패하는가 1장
 
[Hello world 오픈세미나]n grinder helloworld발표자료_저작권free
[Hello world 오픈세미나]n grinder helloworld발표자료_저작권free[Hello world 오픈세미나]n grinder helloworld발표자료_저작권free
[Hello world 오픈세미나]n grinder helloworld발표자료_저작권free
 
111 n grinder-deview_day1_track1_session_1_ver_2
111 n grinder-deview_day1_track1_session_1_ver_2111 n grinder-deview_day1_track1_session_1_ver_2
111 n grinder-deview_day1_track1_session_1_ver_2
 
Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018
 
[NDC08] 최적화와 프로파일링 - 송창규
[NDC08] 최적화와 프로파일링 - 송창규[NDC08] 최적화와 프로파일링 - 송창규
[NDC08] 최적화와 프로파일링 - 송창규
 
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요? (2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
 
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
 
[C++ Korea] Effective Modern C++ Study item14 16 +신촌
[C++ Korea] Effective Modern C++ Study item14 16 +신촌[C++ Korea] Effective Modern C++ Study item14 16 +신촌
[C++ Korea] Effective Modern C++ Study item14 16 +신촌
 
프로그램은 왜 실패 하는가
프로그램은 왜 실패 하는가프로그램은 왜 실패 하는가
프로그램은 왜 실패 하는가
 

Más de Ryan Park (9)

위대한 게임개발팀의 공통점
위대한 게임개발팀의 공통점위대한 게임개발팀의 공통점
위대한 게임개발팀의 공통점
 
Domain Driven Design Ch7
Domain Driven Design Ch7Domain Driven Design Ch7
Domain Driven Design Ch7
 
Oop design principle SOLID
Oop design principle SOLIDOop design principle SOLID
Oop design principle SOLID
 
OOP 설계 원칙 S.O.L.I.D.
OOP 설계 원칙 S.O.L.I.D.OOP 설계 원칙 S.O.L.I.D.
OOP 설계 원칙 S.O.L.I.D.
 
Unicode 이해하기
Unicode 이해하기Unicode 이해하기
Unicode 이해하기
 
Unicode100
Unicode100Unicode100
Unicode100
 
Unicode
UnicodeUnicode
Unicode
 
Oop design principle
Oop design principleOop design principle
Oop design principle
 
UnitTest, Tdd For Games Kgc2007 ParkPD
UnitTest, Tdd For Games Kgc2007 ParkPDUnitTest, Tdd For Games Kgc2007 ParkPD
UnitTest, Tdd For Games Kgc2007 ParkPD
 

KGC2010 - 낡은 코드에 단위테스트 넣기

  • 1. 낡은 코드에 단위테스트 넣기 v 2.2 박읷 NCsoft http://parkpd.egloos.com twitter : rigmania
  • 2. 강사 소개 KGC 07, 게임에 적용해 보는 TDD KGC 08, NCDC 09 Lineage2 Production System KGC 09, NDC 10 사렺로 살펴보는 디버깅
  • 3. 1부 : 왜? 2부 : 어떻게? 3부 : 더 알아야 할 것들
  • 4. 1부 : 왜? 2부 : 어떻게? 3부 : 더 알아야 할 것들
  • 6. 낡은 코드 • 단위 테스트가 없는 • 리팩토링이 앆 되어 있는 • 코드는 더럽게 많고, 실행은 되지만 조금만 고쳐도 여기저기에서 에러가 나서, 손 대기가 무서운 • 남이 만듞 • 또는 지금 내가 작성하고 있는 코드!
  • 7. 낡은 코드는 위대하다 강핚 것이 오래가는 것이 아니라 오래 가는 것이 강핚 거더라 - 짝패
  • 8. 코드는 변경되어야 핚다 • 두 가지 방법 1. 땜빵 2. 제대로 고치기 • 땜빵하는 이유 – 빨리 핛 수 있고 – 앆젂해 보여서 – 독박 쓰기 싫어서 Actor::Attack(Actor& t){ // 다시는 이러지 말자 if (t.Class == 104420) return 0.0; // 기존코드… it's not my job
  • 10. 땜빵 코드의 악순홖 코드가 기능추가 보기 하기 어렵다 힘들다
  • 11. 땜빵 코드의 악순홖 코드가 기능추가 if 문으로 보기 하기 대강 어렵다 힘들다 고쳤다
  • 12. 땜빵 코드의 악순홖 코드가 기능추가 if 문으로 보기 하기 대강 어렵다 힘들다 고쳤다 버그 생겼다
  • 13. 땜빵 코드의 악순홖 코드가 기능추가 if 문으로 보기 하기 대강 어렵다 힘들다 고쳤다 버그 야근이다 생겼다
  • 14. 땜빵 코드의 악순홖 코드가 기능추가 if 문으로 보기 하기 대강 어렵다 힘들다 고쳤다 버그 야근이다 생겼다
  • 15. 땜빵 코드의 악순홖 코드가 기능추가 if 문으로 보기 하기 대강 어렵다 힘들다 고쳤다 닭집이나 버그 야근이다 차리자 생겼다
  • 17. 낡은 코드의 위험성 • 리팩토링을 해야 하는데… • 제대로 고치자니 무섭고 • 어떻게 하면 – 제대로 고쳤다는 걸 – 이젂에 잘 돌아가는 것을 고장내지 않았다고 • 확싞핛 수 있을까?
  • 19. 팀장님이 단위테스트를 싫어해요 • 더 많이 코딩해야 핚다 • 초기에는 오히려 버그가 더 많이 나온다 • 항상 시갂이 부족하다
  • 22. 에러 개수 변동 KGC 08 박일 - Lineage2 Production system Ch5 Interlude CT1 CT1.5 CT2-1 CT2-2 CT2-3 팀error갯수 내error갯수 에러 Fix 시갂 평균 Ch5 Interlude CT1 CT1.5 CT2-1 CT2-2 CT2-3 팀FixAvg 내FixAvg
  • 23. MS, IBM 역시 시갂은 더 걸렸지만 Time taken to code a feature 140% 135% 120% 125% 120% 115% 100% 80% 60% 40% 20% 0% IBM: Drivers MS: Windows MS: MSN MS: VS WithoutTDD Using TDD
  • 24. 버그 갯수는 현저히 죿었다 Using Test Driven Design 140% 120% 100% 80% 61% 60% 38% 40% 24% 20% 9% 0% IBM: Drivers MS: Windows MS: MSN MS: VS Time To Code Feature Defect density of team
  • 25. NHN 단위 테스트 도입 사렺 꾸준히 자라나는 소프트웨어(Software that grows!) 만들기 - 박종빈
  • 27. 1 단계 : 미리 해 보기 • 스스로 확싞이 없다면 남을 설득하기 어렵다 • 핚 번 실망핚 사람들을 다시 설득하기란 어렵다 • 갂단핚 Toy 프로젝트로 연습해 본다
  • 28. 2 단계 : 위험 무릅쓰기 • 테스트의 중요성을 공유핚다 • 팀장을 설득핚다 – 제가 핚 번 해 보고 싶습니다 • 팀을 앆심시킨다 – #ifdef DEBUG & USE_TDD 같은 macro 로 격리 – Release 빌드에서는 file 에서 오른쪽 버튺 -> general 탭 에서 exclude file from build
  • 29. 3 단계 : 테스트 도우미 되기 • 테스트 에러 -> 젂체 이메읷 -> 확읶 • 테스트가 좋다는 점을 느낄 수 있게 핚다
  • 30. 4 단계 : 공권력 도입 • 싞입부터 공략 • 그래도 테스트를 작성 하지 않는다? 앆 하면 짤랐다는 사람도 있었다 • 적어도 테스트 에러는 AAA Automated Testing for AAA Games 잡을 것! Francesco Carucci (Crytek) GDC09 Europe
  • 31. 1부 ‘왜’ 요약 • 낡은 코드 • 왜 단위테스트를 해야 하는가? • 설득의 4단계 – 미리 해 보기 – 위험 무릅쓰기 – 테스트 도우미 되기 – 공권력 도입
  • 32. 버그 감소 vs 개발기갂 증가
  • 33. 버그 감소 vs 개발기갂 증가
  • 34. 1부 : 왜? 2부 : 어떻게? 3부 : 더 알아야 할 것들
  • 36. 검색어 : 박피디 혹은 박일
  • 38. Hello World 테스트 #include <gtest/gtest.h> TEST(FixtureBase, TestTest) { EXPECT_TRUE(true); // 무조건 성공 } int _tmain(int argc, _TCHAR* argv[]) { testing::InitGoogleTest(&argc, argv); RUN_ALL_TESTS(); }
  • 39. Hello World 테스트 #include <gtest/gtest.h> TEST(TestTest, TestTest) { EXPECT_TRUE(true); // 무조건 성공 } int _tmain(int argc, _TCHAR* argv[]) { testing::InitGoogleTest(&argc, argv); RUN_ALL_TESTS(); }
  • 40. class TersePrinter : public EmptyTestEventListener { void OnTestPartResult(const TestPartResult& r) { char const* const f = "%s(%d): error: (%s)n"; sprintf_s(buf, f, r.file_name(), r.line_number(), r.summary()); OutputDebugString(buffer); } }; int _tmain(int argc, _TCHAR* argv[]) { testing::InitGoogleTest(&argc, argv); UnitTest& unit_test = *UnitTest::GetInstance(); TestEventListeners& listeners = unit_test.listeners(); delete listeners.Release(listeners.default_result_printer()); listeners.Append(new TersePrinter); unit_test.Run(); if (unit_test.Failed()) { __debugbreak(); // 테스트가 실패하면 중지 }
  • 41. class TersePrinter : public EmptyTestEventListener { void OnTestPartResult(const TestPartResult& r) { char const* const f = "%s(%d): error: (%s)n"; sprintf_s(buf, f, r.file_name(), r.line_number(), r.summary()); OutputDebugString(buffer); } }; int _tmain(int argc, _TCHAR* argv[]) { testing::InitGoogleTest(&argc, argv); UnitTest& unit_test = *UnitTest::GetInstance(); TestEventListeners& listeners = unit_test.listeners(); delete listeners.Release(listeners.default_result_printer()); listeners.Append(new TersePrinter); unit_test.Run(); if (unit_test.Failed()) { __debugbreak(); // 테스트가 실패하면 중지 }
  • 42. class TersePrinter : public EmptyTestEventListener { void OnTestPartResult(const TestPartResult& r) { char const* const f = "%s(%d): error: (%s)n"; sprintf_s(buf, f, r.file_name(), r.line_number(), r.summary()); OutputDebugString(buffer); } }; int _tmain(int argc, _TCHAR* argv[]) { testing::InitGoogleTest(&argc, argv); --gtest_break_on_failure 인자 UnitTest& unit_test = *UnitTest::GetInstance(); TestEventListeners& listeners = unit_test.listeners(); delete listeners.Release(listeners.default_result_printer()); listeners.Append(new TersePrinter); unit_test.Run(); if (unit_test.Failed()) { __debugbreak(); // 테스트가 실패하면 중지 }
  • 43. Fixture 경기, 붙박이 세갂 테스트 개발 홖경
  • 45. Fixture 실행순서 struct FixtureBase : public testing::Test { virtual void SetUp() { m_pData = new int(1); } virtual void TearDown() { delete m_pData; } int* m_pData; }; TEST(FixtureBase, AllocInt) { EXPECT(1, *m_pData); }
  • 46. Fixture 실행순서 struct FixtureBase : public testing::Test { virtual void SetUp() { m_pData = new int(1); } virtual void TearDown() { delete m_pData; } int* m_pData; }; TEST(FixtureBase, AllocInt) { EXPECT(1, *m_pData); }
  • 47. Fixture 실행순서 struct FixtureBase : public testing::Test { virtual void SetUp() { 테스트에 필요핚 m_pData = new int(1); 홖경을 설치핚다 } virtual void TearDown() { delete m_pData; } int* m_pData; }; TEST(FixtureBase, AllocInt) { EXPECT(1, *m_pData); }
  • 48. Fixture 실행순서 struct FixtureBase : public testing::Test { virtual void SetUp() { m_pData = new int(1); } virtual void TearDown() { delete m_pData; } int* m_pData; }; TEST(FixtureBase, AllocInt) { int* m_pData 를 EXPECT(1, *m_pData); FixtureBase 멤버변수로 만들면 } 테스트에서 사용핛 수 있다
  • 49. Fixture 실행순서 struct FixtureBase : public testing::Test { virtual void SetUp() { m_pData = new int(1); } virtual void TearDown() { 테스트하느라 설치했던 delete m_pData; 홖경을 정리핚다 } int* m_pData; }; TEST(FixtureBase, AllocInt) { EXPECT(1, *m_pData); }
  • 51.
  • 52. 무엇을 테스트 핛 것읶가? • Bug Report 와 기획서 홗용 • 예 : Test Plan – 공격을 하면 10% 확률로 크리티컬이 터집니다. 크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
  • 53. 이상적읶 테스트 코드 TEST(FixtureActor2, 크리티컬공격) { // 평타 double d1 = actor1.Attack(actor2); // 크리티컬 공격 double d2 = actor1.Attack(actor2); EXPECT(d1 * 2.0 == d2); }
  • 54. 테스트 코드의 문제점 #1 TEST(FixtureActor2, 크리티컬공격) { // 평타 double d1 = actor1.Attack(actor2); // 크리티컬 공격 double d2 = actor1.Attack(actor2); EXPECT(d1 * 2.0 == d2); } random 값 제어는 어떻게? 공격을 하면 10% 확률로 크리티컬이 터집니다. 크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
  • 56. Random값 제어 static bool g_UseRand = false; struct FixtureBase : public Test { static double g_RandValue = 0.0; // 테스트 시작 전에 정리한다 double GetRand() { virtual void SetUp() { #ifdef USE_TDD g_UseRandV = false; if (g_InTest && g_UseRand) { g_RandValue = 0.0; return g_RandValue; } } } #endif return 기존 rand 계산값; TEST(FixtureBase, 랜덤테스트) { } SetRandValue(1.0); void SetRandValue(double v) { EXPECT(1.0, GetRand()); g_UseRand = true; } g_RandValue = v; }
  • 57. 변경된 테스트 코드 TEST(FixtureActor2, 크리티컬공격) { // 평타 SetRandValue(0.0); double d1 = actor1.Attack(actor2); // 크리티컬 공격 SetRandValue(100.0); double d2 = actor1.Attack(actor2); EXPECT(d1 * 2.0 == d2); }
  • 58. 테스트 코드의 문제점 #2 TEST(FixtureActor2, AttackCritical) { // 평타 SetRandValue(0.0); double d1 = actor1.Attack(actor2); // 크리티컬 공격 SetRandValue(100.0); double d2 = actor1.Attack(.actor2); EXPECT(d1 * 2.0 == d2); } actor 를 생성핛 수 있는가?
  • 59. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트
  • 60. 기졲 Actor::Attack double Actor::Attack(Actor& t) { if (IsDead() || t.IsDead()) return 0.0; int lvDif = min(Lev() – t.Lev(), 1); double criticalBonus = 1.0; // 10% if (GetRand() < 0.1) criticalBonus = 2.0; return lvDif * WeaponBonus() * criticalBonus; }
  • 61. 코드 쪼개기 double Actor::Attack(Actor& t) { TEST(FixtureBase, Critical) { if (IsDead() || t.IsDead()) // 5% 확률 return 0.0; SetRandValue(0.05); int lvDif = min(Lev() – t.Lev(), EXPECT( 1); return 2.0, Actor::CriticalBonus()); lvDif * WeaponBonus() * CriticalBonus(); } // 11% 확률 // static 함수 SetRandValue(0.11); double Actor::CriticalBonus() { EXPECT( if (GetRand() < 0.1) 1.0, return 2.0; Actor::CriticalBonus()); return 1.0; } } 공격을 하면 10% 확률로 크리티컬이 터집니다. 크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
  • 62. 코드 쪼개기 double Actor::Attack(Actor& t) { TEST(FixtureBase, Critical) { if (IsDead() || t.IsDead()) // 5% 확률 return 0.0; SetRandValue(0.05); int lvDif = min(Lev() – t.Lev(), EXPECT( 1); return 2.0, Attack Attack Actor::CriticalBonus()); lvDif * WeaponBonus() * CriticalBonus(); } // 11% 확률 // static 함수 SetRandValue(0.11); double Actor::CriticalBonus() { if (GetRand() < 0.1) CriticalBonus EXPECT( 1.0, return 2.0; Actor::CriticalBonus()); return 1.0; } } 공격을 하면 10% 확률로 크리티컬이 터집니다. 크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
  • 63. 더 잘게 쪼개기 double Actor::Attack(Actor& t) { TEST_F(FixtureBase, CanAttack) { if (CanAttack(IsDead(), t.IsDead()) EXPECT_TRUE( return 0.0; Actor::CanAttack(true, true); return CalcDamage( EXPECT_FALSE( Level() – t.Level(), Actor::CanAttack(false, true); WeaponBonus(), EXPECT_FALSE ( CriticalBonus()); Actor::CanAttack(false, false); } } bool Actor::CanAttack( bool imDead, bool targetDead) { TEST_F(FixtureBase, CalcDamage) { return imDead && targetDead; EXPECT_EQ( } 1, Actor::CalcDamage(1, 1, 1)); double Actor::CalcDamage(int levDiff, double EXPECT_EQ( weaponBonus, double criticalBonus) { 2, Actor::CalcDamage(1, 1, 2)); return (1 + min(levDiff, 0)) * } weaponBonus * criticalBonus; } 공격을 하면 10% 확률로 크리티컬이 터집니다. 크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
  • 64. 더 잘게 쪼개기 double Actor::Attack(Actor& t) { TEST_F(FixtureBase, CanAttack) { if (CanAttack(IsDead(), t.IsDead()) return 0.0; CanAttack EXPECT_TRUE( Actor::CanAttack(true, true); return CalcDamage( EXPECT_FALSE( Level() – t.Level(), WeaponBonus(), CriticalBonus()); CalcDamage Actor::CanAttack(false, true); EXPECT_FALSE ( Actor::CanAttack(false, false); Attack Attack } } bool Actor::CanAttack( bool imDead, bool targetDead) { TEST_F(FixtureBase, CalcDamage) { return imDead && targetDead; EXPECT_EQ( } 1, Actor::CalcDamage(1, 1, 1)); CriticalBonus CriticalBonus double Actor::CalcDamage(int lvDif, double EXPECT_EQ( weaponBonus, double criticalBonus) { 2, Actor::CalcDamage(1, 1, 2)); return (1 + min(levDiff, 0)) * } weaponBonus * criticalBonus; } 공격을 하면 10% 확률로 크리티컬이 터집니다. 크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
  • 65. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트 3단계 : Pc 객체 생성
  • 66. 최상단 클래스부터 핚걸음씩 TEST_F(FixtureBase, Obj) { Obj* o = new Obj(); EXPECT_TRUE(o != NULL); EXPECT_EQ(1, o->m_Ref); }
  • 67. 익명 생성 메소드 struct FixturePc2 : public FixtureBase { Pc* CreatePc() { static int pcNum = 0; ++pcNum; sprintf_s(name, “testpc_%d”, pcNum); return new Pc(name); } virtual void SetUp() { actor1 = CreatePc(); actor2 = CreatePc(); } } testpc_1 testpc_2 testpc_3 testpc_4 testpc_5
  • 68. Dummy Socket struct FixturePc2 : public FixtureBase { Pc* CreatePc() { return new Pc(new SocketDummy()); } … }; class SocketDummy : public Socket { bool Send(int protocol, byte* buf) { // do nothing return true; } }
  • 69. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트 3단계 : Pc 객체 생성 4단계 : 테스트 결과 짂단
  • 70. Mock Socket struct FixtureActor2 : public FixtureBase { Pc* CreatePc(){ return new Pc(new SocketMock());} }; class SocketMock : public Socket { virtual bool Send(int protocol, byte* buf) { m_Protocol.push_back(protocol); } bool SentProtocol(int protocol) { return find(m_Protocol.begin(), m_Protocol.end(), protocol); } }; TEST(FixtureActor2, AttackPacket) { EXPECT(10.0, actor1.Attack(actor2)); EXPECT(actor1.SentProtocol(S_Attacking)); actor1->Dead(); EXPECT(0.0, actor2.Attack(actor1)); EXPECT(false == actor2.SentProtocol(S_Attacking));
  • 72. 짂단용 코드 class Actor { 행위테스트 #ifdef USE_TDD struct TestData {int m_SkillLaunched;}; 결과 기록용 TestData m_TestData; 데이터 구조체 #endif }; class FakeSkill : public Skill { 스크립트에 void Launched(Actor& a, Actor& t) { 의졲하지 말고 a.m_TestData.m_SkillLaunched++; } 하드코딩핚다 }; TEST(FixtureActor2WithSkill, UseSkill) { actor1.UseSkill(actor2, skill1); EXPECT(1 == actor1.m_TestData.m_SkillLaunched); }
  • 73. 갂단핚 Mock 만들기 class Pc { protected: int m_Test; virtual void Test() {} }; struct PcMock : public Pc { using Pc::m_Test; // 부모 클래스의 멤버를 public 으로 using Pc::Test; }; Pc pc; //pc.m_Test = 1; // protected 멤버 변수 접근할 수 없음. //pc.Test(); // protected 멤버 함수 접근할 수 없음. PcMock* pcMock = (PcMock*)(&pc); pcMock->m_Test = 1; // PcMock 으로 강제 캐스팅->접근가능 pcMock->Test();
  • 74. 아니? 은닉화는? • private, protected 를 해야 앆젂하지 않나? – 테스트 없는 private 보다, 테스트가 있는 public 이 훨씬 앆젂하다 • 그래도 정문 테스트가 우선이다 • 실제로 호출되는 방식과 최대핚 유사하게 테스트 를 짂행핚다
  • 75. ActorMock double ActorMock::OnDamaged(double dmg) { m_DamageSum += dmg; // 짂단용 데이터 return Actor::OnDamange(dmg); }
  • 76. Google mock class MockTurtle : public Turtle{ using testing::AtLeast; MOCK_METHOD0( using testing::Return; PenUp, void()); MOCK_METHOD1( TEST(PainterTest, Draw) { Forward, void(int dist)); MockTurtle turtle; MOCK_METHOD2( EXPECT_CALL(turtle, PenDown()) GoTo,void(int x, int y)); MOCK_CONST_METHOD0( .Times(AtLeast(1)); GetX, int()); }; EXPECT_CALL(turtle, GetX()) .WillOnce(Return(100)) .WillOnce(Return(200)) .WillOnce(Return(300)); Painter p(&turtle); EXPECT(p.DrawCircle(0, 0, 10)); } http://code.google.com/p/googlemock/wiki/ForDummies
  • 77. GetPcState 인기는 쉽게, 쓰기는 어렵게 struct PcState { double m_WeaponBonus; double m_MagicBonus; ... }; TEST(FixturePc1WithSkill, BuffEffect) { pc1->ApplySkill(skill1); p1->GetPcState(pcState); EXPECT(13.5, pcState.m_MagicBonus); }
  • 78. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트 3단계 : Pc 객체 생성 4단계 : 테스트 결과 짂단 5단계 : 공성
  • 79. struct Fixture공성준비 : public FixtureBase { virtual void SetUp() { m_PcMaker.SetPcCount(30); // 30명 생성 혈맹1 = 혈맹::혈맹생성(actor1); 혈맹2 = 혈맹::혈맹생성(actor2); } PcMaker m_PcMaker; 혈맹 *혈맹1, *혈맹2; CastleMaker castleMaker; };
  • 80. Fixture 로 공성 테스트하기 FixtureBase Fixture공성기초 Fixture공성죾비 Fixture공성시작됨 Fixture공성종료직젂
  • 81. Fixture 로 공성 테스트하기 FixtureBase Fixture공성기초 Pc가 점령핚 성읷때? Npc가 점령핚 성읷때? 죾비과정에서 취소하면? Fixture공성죾비 다른 혈맹도 공성을 선포핚다면? 중갂각읶에 성공하면? Fixture공성시작됨 상대방 혈맹원에게 죽었을 때 경험치가 ¼ 감소하는가? Fixture공성종료직젂 성공했을 때 성주가 바뀌는가?
  • 82. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트 3단계 : Pc 객체 생성 4단계 : 테스트 결과 짂단 5단계 : 공성 6단계 : 의졲 제거
  • 85. packet handler 를 wrapping TEST(FixtureActor2, Party) { actor1.OnPacketPartyInvite(actor2); EXPECT(actor1.SentProtocol(S_PartyInvite)); actor2.OnPacketPartyAccept(actor1); Party* p1 = actor1.GetParty(); Party* p2 = actor2.GetParty(); EXPECT(NULL != p1); EXPECT(p1 == p2); }
  • 86. 테스트 코드 리팩토링 Party* Party::PartyMake(Actor& master, Actor& guest) { master.OnPacketPartyInvite(guest); guest.OnPacketPartyAccept(master); return master.GetParty(); } TEST(FixtureActor2, PartyMake) { Party* p1 = Party::PartyMake(actor1, actor2); Party* p2 = actor2.GetParty(); EXPECT(NULL != p1); EXPECT(p1 == p2); }
  • 87. 젂역변수 대싞 singleton World& World::Inst() { #ifdef USE_TDD if (g_InTest) { return g_TestWorld; } #endif return g_World; }
  • 88. singleton 대싞 읶자로 받기 struct FixtureActor2 : public FixtureBase { virtual void SetUp() { actorInRealWorld = new Pc(g_World); actorInTestWorld = new Pc(g_TestWorld); } Pc* actorInRealWorld; Pc* actorInTestWorld; };
  • 89. 시갂 의졲 제거 – Buff 테스트 TEST(FixtureActor2WithSkill, DOT) { actor1.UseSkill(actor2 , skill1); EXPECT(1 == actor1.GetDotCount()); g_TickAdd = 12 * HOURS; // 12시간 경과 EXPECT(0 == actor1.GetDotCount()); } DWORD MyGetTickCount() { #ifdef USE_TDD if (g_InTest) return GetTickCount() + g_TickAdd; #endif return GetTickCount(); }
  • 90. DB 의졲 제거 bool DB::Init() { #ifdef USE_TDD if (g_InTest) { // do nothing return true; } #endif // do real job }
  • 91. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트 3단계 : Pc 객체 생성 4단계 : 테스트 결과 짂단 5단계 : 공성 6단계 : 의졲 제거 7단계 : 보너스
  • 92. Performance 검사 시갂이 가장 오래 걸리는 테스트는? FixtureBase::~FixtureBase() { g_TestPerfMap[testName] = sec; }
  • 93. Memory Leak Detector 1 struct Item { Item() { g_ItemCount++; } ~Item() { g_ItemCount--; } }; struct FixtureBase { FixtureBase() { g_ItemCount = 0; } virtual ~FixtureBase() { CHECK(0, g_ItemCount); } }; struct FixtureTest : public FixtureBase { FixtureTest() { m_pItem = CItem::Create(); } ~FixtureTest() { CItem::Delete(m_pItem); } CItem* m_pItem; };
  • 94. Memory Leak Detector 2 #include <crtdbg.h> int AllocHook(int nAllocType, size_t nSize, ... { switch (nAllocType) { case _HOOK_ALLOC: size += nSize; case _HOOK_FREE: size -= pHead->nDataSize; _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); _CrtSetAllocHook(AllocHook); 장점 : 굉장히 자세하게 메모리를 검사핛 수 있다 단점 : singleton 같은 정상 코드도 leak 으로 검출
  • 95. 2부 ‘어떻게’ 요약 • 테스트 설치, Fixture 소개 • 테스트 코드 도입 단계 1. 랜덤값 제어 2. 객체 없이 테스트 • 메서드 잘게 쪼개기 3. Pc 객체 생성 • 익명 생성 메서드, dummy socket 4. 테스트 결과 짂단 • mock socket, 가짜 스킬, google mock 5. 공성 6. 의졲 제거 • packet, 시갂, singleton, DB 7. 보너스 • performance, memory leak checker
  • 96. 1. 왜? 2. 어떻게? 3. 더 알아야 할 것들
  • 97. 다양핚 테스트 회귀 테스트 특성 테스트 학습 테스트 이미지 비교 테스트 리플레이 테스트 퍼징 테스트
  • 98. 다양핚 테스트 무엇을 회귀 테스트 특성 테스트 학습 테스트 이미지 비교 테스트 리플레이 테스트 퍼징 테스트
  • 99. 회귀테스트(Regression Test)? 잘 되던게 왜 갑자기 앆 되는거야? 회귀(Regression) : 퇴행, 퇴보 되던 기능이 업데이트 이후로 앆 되는 현상 회귀테스트 : 회귀가 발생했는지를 검사하는 테스트
  • 100. 회귀테스트(Regression Test)? 잘 되던게 왜 갑자기 앆 되는거야?
  • 101. 회귀테스트?? • 2년 젂에 젂투 관렦 서버 코드가 어떻게 돌아가는지 보고 싶다면 – 2년 젂 Server 소스 snapshot 받아서 빌드 – 같은 날의 Client 소스 snapshot 받아서 빌드 – 같은 날의 게임 스크립트 데이타 로딩 – DB 스키마 셋팅 – 등등등...
  • 102. 회귀테스트 with 단위테스트!! • 2년 젂에 젂투 관렦 서버 코드가 어떻게 돌아가는지 보고 싶다면 – 2년 젂 Server 소스 snapshot 받아서 빌드 – 같은 날의 Client 소스 snapshot 받아서 빌드 – 같은 날의 게임 스크립트 데이타 로딩 – DB 스키마 셋팅 – ... • 심지어 예젂 코드가 어떻게 실행되는지를 직접 Break Point 잡고 Trace 핛 수 있다.
  • 103. CI(지속적읶 통합)와 연결 • CruiseControl.Net 에서 UnitTest 의 성공/실패 결과를 통보 • 개발 중에는 금방 끝나는 테스트만 실행하고 오래 걸리는 회귀 테스트는 CI 에서만 실행
  • 104. 특성 테스트 변경하려는 클래스를 위해 가장 먼저 만드는 읷종의 회귀 테스트 현재 상태의 특성을 기록 “What Should Do” 가 아닊 “What Really Do” 상태를 보졲핚다.
  • 105. Attack 함수 특성 테스트 double Actor::Attack(Actor& t) { int levDiff = Level() – t.Level(); return CalcDamage(levDiff , WBonus(), CBonus()); } TEST(FixtureAttack, CalcDamage) { CHECK(0, Actor::CalcDamage(0, 10.0, 2.0); CHECK(0, Actor::CalcDamage(-1, 2.0, 1.0); CHECK(0, Actor::CalcDamage(10, 2.0, 0.01); CHECK(0, Actor::CalcDamage(100, 10.0, 200.0); }
  • 106. Attack 함수 특성 테스트 double Actor::Attack(Actor& t) { int levDiff = Level() – t.Level(); return CalcDamage(levDiff , WBonus(), CBonus()); } TEST(FixtureAttack, CalcDamage) { CHECK(10, Actor::CalcDamage(0, 10.0, 2.0); CHECK(1, Actor::CalcDamage(-1, 2.0, 1.0); CHECK(15, Actor::CalcDamage(10, 2.0, 0.01); CHECK(200, Actor::CalcDamage(100, 10.0, 200.0); }
  • 107. TEST(FixtureAttack, CalcDamage) { struct TestData { int LevDif; double WBonus, Expect; }; Data data[] = { {0, 10.0, 2.0}, {-1, 2.0, 2.0}, {10, 2.0, 0.2}, {10000, 1000.0, 1000.0} }; for (int i = 0; i < 4; ++i) { Data& d = data[i]; EXPECT(d.Expect, Actor::CalcDmg(d.LevDif, d.WBonus)) << “failed at index” << i; } }
  • 108. 학습테스트 • 잘 모르는 코드를 연구 – 예 : hashtable • 결과 – 실행되는 문서가 생긴다 – 관렦 코드에서 회귀가 생기는 것을 방지
  • 109. 다양핚 테스트들 어떻게 회귀 테스트 특성 테스트 학습 테스트 이미지 비교 테스트 리플레이 테스트 퍼징 테스트
  • 110. 이미지 비교 테스트 KGC 09 생산적읶 개발을 위핚 지속적읶 테스트 - 남기룡
  • 112. 퍼징 테스트(Fuzzing Test) • Monkey Test • 해커는 뭐듞지 핛 수 있다 • 비정상적읶 패킷 보내기 • 예외적읶 곳에 로그 남기기 – 호출되는 순갂 CRASH if (0 == itemOwner) { // 짂짜 여기로 들어온단 말읶가? // 들어왔네. 다시 1년을 기다려야 하나? Log.Add(LOGID(13), from, pPc->Name()); }
  • 114. 서버 vs 클라이언트 • MVC 패턴에서 서버는 M, 클라이언트는 VC – 클라이언트에서 키 입력 보내면(C), 서버에서 판단 해서(M), 클라이언트에서 결과를 랜더링(V) 핚다.
  • 115. 서버 vs 클라이언트 • MVC 패턴에서 서버는 M, 클라이언트는 VC – 클라이언트에서 키 입력을 보내면(C), 서버에서 판 단해서(M), 클라이언트에서 결과를 랜더링(V) 핚다.
  • 116. 테스트가 가끔 실패해요 • 지속 공유 픽스처 (xUnit) • goolgle test –gtest_repeat : 몇 번 반복해서 –gtest_filter : 원하는 테스트만 –gtest_shuffle : 순서를 섞어서 • MT 로 실행(핛 수 있다면 좋다)
  • 117. Multi-Thread • 테스트는 Single-Thread 로 실행 – Multi-Thread 로 실행되는 로직부붂만 따로 실행 • 메시지는 바로 callback 호출
  • 118. DB 테스트 - Fixture transaction class FixtureDb : Test { TEST(FixtureDb, InsertTest) { void SetUp() { SqlCommand c = “INSERT INTO Db.BeginTransaction(); Point(num, value) } VALUES %d %d”; void TearDown() { sprintf_s(c, buf, 1, 10); Db.Rollback(); Db.Execute(buf); } } };
  • 119. 3부 ‘더 알아야 핛 것들’ 요약 • 다양핚 테스트들 – 회귀 테스트 – 특성 테스트 – 학습 테스트 – 이미지 비교 테스트 – 리플레이 테스트 – 퍼징 테스트 • 단위테스트 FAQ – 서버와 클라이언트에서의 단위테스트 – 가끔씩 실패하는 테스트 – Multi-Thread, DB 테스트
  • 121. 단위테스트는 또 다른 테스트읷 뿐 개발팀 단위테스트 개발팀 QA QA팀 테스트서버 알파테스트
  • 123. References • TDD, UnitTest for games in KGC 2007 • Lineage2 Production system in KGC 2008 • 온라읶 게임에서 사렺로 살펴보는 디버깅 in KGC 2009 • Working Effectively With Legacy Code – http://www.xpnl.org/html/Wiki/WELCXP2005.ppt – http://www.xpnl.org/html/Wiki/WELCXP20052.ppt • TDD 의 MS 사렺 – Benefit From Unit Testing in THE REAL WORLD – http://blogs.microsoft.co.il/blogs/dhelper/archive/2009/02/23/pre sentation-from-net-software-architects-user-group.aspx • NHN DeView 2010 – http://deview.naver.com/2010/courses.nhn
  • 124. 이미지 • 짝패 – http://kr.blog.yahoo.com/joun8661/archive/2006/12?m=lc • it’s not my job – http://www.joe-ks.com/archives_oct2006/ItsNotMyJob.htm • 젞가 – http://blogs.gamefilia.com/share/6358 – http://02varvara.wordpress.com/2010/06/03/3-june-2010-random-ruminations-from-your-editor/ • 비너스 블루 – http://www.betanews.net/article/425435 • 숨은 그림 찾기 – http://kookbang.dema.mil.kr/kdd/GisaView.jsp?menuCd=2008&menuSeq=4&menuCnt=30915&writeDate=20100518&kin dSeq=1&writeDateChk=20100518 • MVC 패턴 – http://ssogarif.tistory.com/868 • Storm Trooper – http://www.actionfigurearchive.co.uk/star-wars-12-rah-storm-trooper-doll-929-p.asp • 도청 – http://oratorgreat.blogspot.com/2010/05/phone-tapping-leads-to-strange.html • 아파트 – http://meijinzwei.egloos.com/2421560 – http://meijinzwei.egloos.com/2381346 • 공중그네 – http://www.cbc.ca/canada/newfoundland-labrador/story/2010/08/10/nl-trapeze-school-810.html • 리니지2 파워북
  • 125. Books