SlideShare a Scribd company logo
1 of 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

More Related Content

What's hot

테스터가 말하는 테스트코드 작성 팁과 사례
테스터가 말하는 테스트코드 작성 팁과 사례테스터가 말하는 테스트코드 작성 팁과 사례
테스터가 말하는 테스트코드 작성 팁과 사례SangIn Choung
 
C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기OnGameServer
 
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기OKKY
 
카카오스토리 웹팀의 코드리뷰 경험
카카오스토리 웹팀의 코드리뷰 경험카카오스토리 웹팀의 코드리뷰 경험
카카오스토리 웹팀의 코드리뷰 경험Ohgyun Ahn
 
Test Driven Development With Python
Test Driven Development With PythonTest Driven Development With Python
Test Driven Development With PythonSiddhi
 
MySQL 5.7 トラブルシューティング 性能解析入門編
MySQL 5.7 トラブルシューティング 性能解析入門編MySQL 5.7 トラブルシューティング 性能解析入門編
MySQL 5.7 トラブルシューティング 性能解析入門編Mikiya Okuno
 
코딩 테스트 및 알고리즘 문제해결 공부 방법 (고려대학교 KUCC, 2022년 4월)
코딩 테스트 및 알고리즘 문제해결 공부 방법 (고려대학교 KUCC, 2022년 4월)코딩 테스트 및 알고리즘 문제해결 공부 방법 (고려대학교 KUCC, 2022년 4월)
코딩 테스트 및 알고리즘 문제해결 공부 방법 (고려대학교 KUCC, 2022년 4월)Suhyun Park
 
A Brief Introduction to React.js
A Brief Introduction to React.jsA Brief Introduction to React.js
A Brief Introduction to React.jsDoug Neiner
 
(알도개) GraalVM – 자바를 넘어선 새로운 시작의 서막
(알도개) GraalVM – 자바를 넘어선 새로운 시작의 서막(알도개) GraalVM – 자바를 넘어선 새로운 시작의 서막
(알도개) GraalVM – 자바를 넘어선 새로운 시작의 서막Jay Park
 
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)SangIn Choung
 
Amazon product advertising apiで遊んでみた
Amazon product advertising apiで遊んでみたAmazon product advertising apiで遊んでみた
Amazon product advertising apiで遊んでみたYoichi Toyota
 
Jenkinsfileのlintで救える命がある
Jenkinsfileのlintで救える命があるJenkinsfileのlintで救える命がある
Jenkinsfileのlintで救える命があるJumpei Miyata
 
Spring bootでweb バリデート編
Spring bootでweb バリデート編Spring bootでweb バリデート編
Spring bootでweb バリデート編なべ
 
훌륭한 개발자로 성장하기
훌륭한 개발자로 성장하기훌륭한 개발자로 성장하기
훌륭한 개발자로 성장하기Changyol BAEK
 
katalon studio 툴을 이용한 GUI 테스트 자동화 가이드
katalon studio 툴을 이용한 GUI 테스트 자동화 가이드katalon studio 툴을 이용한 GUI 테스트 자동화 가이드
katalon studio 툴을 이용한 GUI 테스트 자동화 가이드SangIn Choung
 
윤석주, 인하우스 웹 프레임워크 Jul8 제작기, NDC2018
윤석주, 인하우스 웹 프레임워크 Jul8 제작기, NDC2018윤석주, 인하우스 웹 프레임워크 Jul8 제작기, NDC2018
윤석주, 인하우스 웹 프레임워크 Jul8 제작기, NDC2018devCAT Studio, NEXON
 
LogbackからLog4j 2への移行によるアプリケーションのスループット改善 ( JJUG CCC 2021 Fall )
LogbackからLog4j 2への移行によるアプリケーションのスループット改善 ( JJUG CCC 2021 Fall ) LogbackからLog4j 2への移行によるアプリケーションのスループット改善 ( JJUG CCC 2021 Fall )
LogbackからLog4j 2への移行によるアプリケーションのスループット改善 ( JJUG CCC 2021 Fall ) Hironobu Isoda
 
日本語テストメソッドについて
日本語テストメソッドについて日本語テストメソッドについて
日本語テストメソッドについてkumake
 
[NDC18] 나는 테스트 정책대로 살기로 했다.
[NDC18] 나는 테스트 정책대로 살기로 했다.[NDC18] 나는 테스트 정책대로 살기로 했다.
[NDC18] 나는 테스트 정책대로 살기로 했다.Wooram Hwang
 

What's hot (20)

테스터가 말하는 테스트코드 작성 팁과 사례
테스터가 말하는 테스트코드 작성 팁과 사례테스터가 말하는 테스트코드 작성 팁과 사례
테스터가 말하는 테스트코드 작성 팁과 사례
 
C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기
 
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기
[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기
 
카카오스토리 웹팀의 코드리뷰 경험
카카오스토리 웹팀의 코드리뷰 경험카카오스토리 웹팀의 코드리뷰 경험
카카오스토리 웹팀의 코드리뷰 경험
 
Test Driven Development With Python
Test Driven Development With PythonTest Driven Development With Python
Test Driven Development With Python
 
MySQL 5.7 トラブルシューティング 性能解析入門編
MySQL 5.7 トラブルシューティング 性能解析入門編MySQL 5.7 トラブルシューティング 性能解析入門編
MySQL 5.7 トラブルシューティング 性能解析入門編
 
코딩 테스트 및 알고리즘 문제해결 공부 방법 (고려대학교 KUCC, 2022년 4월)
코딩 테스트 및 알고리즘 문제해결 공부 방법 (고려대학교 KUCC, 2022년 4월)코딩 테스트 및 알고리즘 문제해결 공부 방법 (고려대학교 KUCC, 2022년 4월)
코딩 테스트 및 알고리즘 문제해결 공부 방법 (고려대학교 KUCC, 2022년 4월)
 
A Brief Introduction to React.js
A Brief Introduction to React.jsA Brief Introduction to React.js
A Brief Introduction to React.js
 
(알도개) GraalVM – 자바를 넘어선 새로운 시작의 서막
(알도개) GraalVM – 자바를 넘어선 새로운 시작의 서막(알도개) GraalVM – 자바를 넘어선 새로운 시작의 서막
(알도개) GraalVM – 자바를 넘어선 새로운 시작의 서막
 
NodeJS for Beginner
NodeJS for BeginnerNodeJS for Beginner
NodeJS for Beginner
 
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
 
Amazon product advertising apiで遊んでみた
Amazon product advertising apiで遊んでみたAmazon product advertising apiで遊んでみた
Amazon product advertising apiで遊んでみた
 
Jenkinsfileのlintで救える命がある
Jenkinsfileのlintで救える命があるJenkinsfileのlintで救える命がある
Jenkinsfileのlintで救える命がある
 
Spring bootでweb バリデート編
Spring bootでweb バリデート編Spring bootでweb バリデート編
Spring bootでweb バリデート編
 
훌륭한 개발자로 성장하기
훌륭한 개발자로 성장하기훌륭한 개발자로 성장하기
훌륭한 개발자로 성장하기
 
katalon studio 툴을 이용한 GUI 테스트 자동화 가이드
katalon studio 툴을 이용한 GUI 테스트 자동화 가이드katalon studio 툴을 이용한 GUI 테스트 자동화 가이드
katalon studio 툴을 이용한 GUI 테스트 자동화 가이드
 
윤석주, 인하우스 웹 프레임워크 Jul8 제작기, NDC2018
윤석주, 인하우스 웹 프레임워크 Jul8 제작기, NDC2018윤석주, 인하우스 웹 프레임워크 Jul8 제작기, NDC2018
윤석주, 인하우스 웹 프레임워크 Jul8 제작기, NDC2018
 
LogbackからLog4j 2への移行によるアプリケーションのスループット改善 ( JJUG CCC 2021 Fall )
LogbackからLog4j 2への移行によるアプリケーションのスループット改善 ( JJUG CCC 2021 Fall ) LogbackからLog4j 2への移行によるアプリケーションのスループット改善 ( JJUG CCC 2021 Fall )
LogbackからLog4j 2への移行によるアプリケーションのスループット改善 ( JJUG CCC 2021 Fall )
 
日本語テストメソッドについて
日本語テストメソッドについて日本語テストメソッドについて
日本語テストメソッドについて
 
[NDC18] 나는 테스트 정책대로 살기로 했다.
[NDC18] 나는 테스트 정책대로 살기로 했다.[NDC18] 나는 테스트 정책대로 살기로 했다.
[NDC18] 나는 테스트 정책대로 살기로 했다.
 

Viewers also liked

시작하자 단위테스트
시작하자 단위테스트시작하자 단위테스트
시작하자 단위테스트YongEun Choi
 
Doxygen 사용법
Doxygen 사용법Doxygen 사용법
Doxygen 사용법YoungSu Son
 
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 SONYAnaya Medias Swiss
 
Vs2013 doxygen 매크로 개발
Vs2013 doxygen 매크로 개발Vs2013 doxygen 매크로 개발
Vs2013 doxygen 매크로 개발민석 강
 
즉흥연기와프로그래밍
즉흥연기와프로그래밍즉흥연기와프로그래밍
즉흥연기와프로그래밍Ryan Park
 
카사 공개세미나1회 W.E.L.C.
카사 공개세미나1회  W.E.L.C.카사 공개세미나1회  W.E.L.C.
카사 공개세미나1회 W.E.L.C.Ryan Park
 
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. RavenRyan Park
 
AIbyExample - Ch7 raven. version 0.8
AIbyExample - Ch7 raven. version 0.8AIbyExample - Ch7 raven. version 0.8
AIbyExample - Ch7 raven. version 0.8Ryan Park
 
Google Protocol buffer
Google Protocol bufferGoogle Protocol buffer
Google Protocol bufferknight1128
 
나도기술서번역한번해볼까 in NDC10
나도기술서번역한번해볼까 in NDC10나도기술서번역한번해볼까 in NDC10
나도기술서번역한번해볼까 in NDC10Ryan Park
 
Devon 2011-b-5 효과적인 레거시 코드 다루기
Devon 2011-b-5 효과적인 레거시 코드 다루기Devon 2011-b-5 효과적인 레거시 코드 다루기
Devon 2011-b-5 효과적인 레거시 코드 다루기Daum DNA
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010Ryan Park
 
온라인 게임에서 사례로 살펴보는 디버깅
온라인 게임에서 사례로 살펴보는 디버깅온라인 게임에서 사례로 살펴보는 디버깅
온라인 게임에서 사례로 살펴보는 디버깅Ryan Park
 
나도(기술서)번역한번해볼까
나도(기술서)번역한번해볼까나도(기술서)번역한번해볼까
나도(기술서)번역한번해볼까Ryan Park
 
빠른 프로토타이핑을 위한 웹앱 자동화 툴 - YEOMAN
빠른 프로토타이핑을 위한 웹앱 자동화 툴 - YEOMAN빠른 프로토타이핑을 위한 웹앱 자동화 툴 - YEOMAN
빠른 프로토타이핑을 위한 웹앱 자동화 툴 - YEOMAN정호 전
 

Viewers also liked (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 to KGC2010 - 낡은 코드에 단위테스트 넣기

온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10Ryan Park
 
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018Kenneth Ceyer
 
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: TDDSuwon Chae
 
Legacy code refactoring video rental system
Legacy code refactoring   video rental systemLegacy code refactoring   video rental system
Legacy code refactoring video rental systemJaehoon Oh
 
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 HowRyan Park
 
Effective unit testing ch3. 테스트더블
Effective unit testing   ch3. 테스트더블Effective unit testing   ch3. 테스트더블
Effective unit testing ch3. 테스트더블YongEun Choi
 
Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005Ryan Park
 
Windows Debugging Technique #2
Windows Debugging Technique #2Windows Debugging Technique #2
Windows Debugging Technique #2Wooseok Seo
 
테스트가 뭐예요?
테스트가 뭐예요?테스트가 뭐예요?
테스트가 뭐예요?Kyoung Up Jung
 
프로그램은 왜 실패하는가 1장
프로그램은 왜 실패하는가 1장프로그램은 왜 실패하는가 1장
프로그램은 왜 실패하는가 1장Ryan Park
 
[Hello world 오픈세미나]n grinder helloworld발표자료_저작권free
[Hello world 오픈세미나]n grinder helloworld발표자료_저작권free[Hello world 오픈세미나]n grinder helloworld발표자료_저작권free
[Hello world 오픈세미나]n grinder helloworld발표자료_저작권freeNAVER 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_2NAVER D2
 
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 2018KyungHo Jung
 
[NDC08] 최적화와 프로파일링 - 송창규
[NDC08] 최적화와 프로파일링 - 송창규[NDC08] 최적화와 프로파일링 - 송창규
[NDC08] 최적화와 프로파일링 - 송창규ChangKyu Song
 
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요? (2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요? 내훈 정
 
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013Esun Kim
 
[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 +신촌Seok-joon Yun
 
프로그램은 왜 실패 하는가
프로그램은 왜 실패 하는가프로그램은 왜 실패 하는가
프로그램은 왜 실패 하는가홍준 김
 
242 naver-2
242 naver-2242 naver-2
242 naver-2NAVER D2
 

Similar to 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 +신촌
 
프로그램은 왜 실패 하는가
프로그램은 왜 실패 하는가프로그램은 왜 실패 하는가
프로그램은 왜 실패 하는가
 
242 naver-2
242 naver-2242 naver-2
242 naver-2
 

More from Ryan Park

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

More from 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