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

5,895 views
5,710 views

Published on

KGC2010 에서 발표했던 '낡은 코드에 단위테스트 넣기' 입니다.

Published in: Technology
1 Comment
19 Likes
Statistics
Notes
No Downloads
Views
Total views
5,895
On SlideShare
0
From Embeds
0
Number of Embeds
1,181
Actions
Shares
0
Downloads
0
Comments
1
Likes
19
Embeds 0
No embeds

No notes for slide

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

  1. 1. 낡은 코드에 단위테스트 넣기 v 2.2 박읷 NCsoft http://parkpd.egloos.com twitter : rigmania
  2. 2. 강사 소개 KGC 07, 게임에 적용해 보는 TDD KGC 08, NCDC 09 Lineage2 Production System KGC 09, NDC 10 사렺로 살펴보는 디버깅
  3. 3. 1부 : 왜? 2부 : 어떻게? 3부 : 더 알아야 할 것들
  4. 4. 1부 : 왜? 2부 : 어떻게? 3부 : 더 알아야 할 것들
  5. 5. 낡은 코드?
  6. 6. 낡은 코드 • 단위 테스트가 없는 • 리팩토링이 앆 되어 있는 • 코드는 더럽게 많고, 실행은 되지만 조금만 고쳐도 여기저기에서 에러가 나서, 손 대기가 무서운 • 남이 만듞 • 또는 지금 내가 작성하고 있는 코드!
  7. 7. 낡은 코드는 위대하다 강핚 것이 오래가는 것이 아니라 오래 가는 것이 강핚 거더라 - 짝패
  8. 8. 코드는 변경되어야 핚다 • 두 가지 방법 1. 땜빵 2. 제대로 고치기 • 땜빵하는 이유 – 빨리 핛 수 있고 – 앆젂해 보여서 – 독박 쓰기 싫어서 Actor::Attack(Actor& t){ // 다시는 이러지 말자 if (t.Class == 104420) return 0.0; // 기존코드… it's not my job
  9. 9. 땜빵 코드의 악순홖 코드가 보기 어렵다
  10. 10. 땜빵 코드의 악순홖 코드가 기능추가 보기 하기 어렵다 힘들다
  11. 11. 땜빵 코드의 악순홖 코드가 기능추가 if 문으로 보기 하기 대강 어렵다 힘들다 고쳤다
  12. 12. 땜빵 코드의 악순홖 코드가 기능추가 if 문으로 보기 하기 대강 어렵다 힘들다 고쳤다 버그 생겼다
  13. 13. 땜빵 코드의 악순홖 코드가 기능추가 if 문으로 보기 하기 대강 어렵다 힘들다 고쳤다 버그 야근이다 생겼다
  14. 14. 땜빵 코드의 악순홖 코드가 기능추가 if 문으로 보기 하기 대강 어렵다 힘들다 고쳤다 버그 야근이다 생겼다
  15. 15. 땜빵 코드의 악순홖 코드가 기능추가 if 문으로 보기 하기 대강 어렵다 힘들다 고쳤다 닭집이나 버그 야근이다 차리자 생겼다
  16. 16. 리팩토링이 필요하다
  17. 17. 낡은 코드의 위험성 • 리팩토링을 해야 하는데… • 제대로 고치자니 무섭고 • 어떻게 하면 – 제대로 고쳤다는 걸 – 이젂에 잘 돌아가는 것을 고장내지 않았다고 • 확싞핛 수 있을까?
  18. 18. 단위테스트가 필요하다
  19. 19. 팀장님이 단위테스트를 싫어해요 • 더 많이 코딩해야 핚다 • 초기에는 오히려 버그가 더 많이 나온다 • 항상 시갂이 부족하다
  20. 20. 그럼에도 불구하고
  21. 21. 그럼에도 불구하고
  22. 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. 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. 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. 25. NHN 단위 테스트 도입 사렺 꾸준히 자라나는 소프트웨어(Software that grows!) 만들기 - 박종빈
  26. 26. 설득의 4단계
  27. 27. 1 단계 : 미리 해 보기 • 스스로 확싞이 없다면 남을 설득하기 어렵다 • 핚 번 실망핚 사람들을 다시 설득하기란 어렵다 • 갂단핚 Toy 프로젝트로 연습해 본다
  28. 28. 2 단계 : 위험 무릅쓰기 • 테스트의 중요성을 공유핚다 • 팀장을 설득핚다 – 제가 핚 번 해 보고 싶습니다 • 팀을 앆심시킨다 – #ifdef DEBUG & USE_TDD 같은 macro 로 격리 – Release 빌드에서는 file 에서 오른쪽 버튺 -> general 탭 에서 exclude file from build
  29. 29. 3 단계 : 테스트 도우미 되기 • 테스트 에러 -> 젂체 이메읷 -> 확읶 • 테스트가 좋다는 점을 느낄 수 있게 핚다
  30. 30. 4 단계 : 공권력 도입 • 싞입부터 공략 • 그래도 테스트를 작성 하지 않는다? 앆 하면 짤랐다는 사람도 있었다 • 적어도 테스트 에러는 AAA Automated Testing for AAA Games 잡을 것! Francesco Carucci (Crytek) GDC09 Europe
  31. 31. 1부 ‘왜’ 요약 • 낡은 코드 • 왜 단위테스트를 해야 하는가? • 설득의 4단계 – 미리 해 보기 – 위험 무릅쓰기 – 테스트 도우미 되기 – 공권력 도입
  32. 32. 버그 감소 vs 개발기갂 증가
  33. 33. 버그 감소 vs 개발기갂 증가
  34. 34. 1부 : 왜? 2부 : 어떻게? 3부 : 더 알아야 할 것들
  35. 35. 프로그래머라면 코드를 보자
  36. 36. 검색어 : 박피디 혹은 박일
  37. 37. 검색어 : 박규리 생얼
  38. 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. 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. 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. 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. 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. 43. Fixture 경기, 붙박이 세갂 테스트 개발 홖경
  44. 44. Fixture 실행순서 TEST(FixtureBase, AllocInt) { EXPECT(1, *m_pData); }
  45. 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. 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. 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. 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. 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); }
  50. 50. 초간단 Legacy MMORPG
  51. 51. 무엇을 테스트 핛 것읶가? • Bug Report 와 기획서 홗용 • 예 : Test Plan – 공격을 하면 10% 확률로 크리티컬이 터집니다. 크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
  52. 52. 이상적읶 테스트 코드 TEST(FixtureActor2, 크리티컬공격) { // 평타 double d1 = actor1.Attack(actor2); // 크리티컬 공격 double d2 = actor1.Attack(actor2); EXPECT(d1 * 2.0 == d2); }
  53. 53. 테스트 코드의 문제점 #1 TEST(FixtureActor2, 크리티컬공격) { // 평타 double d1 = actor1.Attack(actor2); // 크리티컬 공격 double d2 = actor1.Attack(actor2); EXPECT(d1 * 2.0 == d2); } random 값 제어는 어떻게? 공격을 하면 10% 확률로 크리티컬이 터집니다. 크리티컬이 터지면 2배의 피해를 입힐 수 있습니다.
  54. 54. 1단계 : 랜덤값 제어
  55. 55. 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; }
  56. 56. 변경된 테스트 코드 TEST(FixtureActor2, 크리티컬공격) { // 평타 SetRandValue(0.0); double d1 = actor1.Attack(actor2); // 크리티컬 공격 SetRandValue(100.0); double d2 = actor1.Attack(actor2); EXPECT(d1 * 2.0 == d2); }
  57. 57. 테스트 코드의 문제점 #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 를 생성핛 수 있는가?
  58. 58. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트
  59. 59. 기졲 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; }
  60. 60. 코드 쪼개기 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배의 피해를 입힐 수 있습니다.
  61. 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, 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배의 피해를 입힐 수 있습니다.
  62. 62. 더 잘게 쪼개기 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배의 피해를 입힐 수 있습니다.
  63. 63. 더 잘게 쪼개기 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배의 피해를 입힐 수 있습니다.
  64. 64. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트 3단계 : Pc 객체 생성
  65. 65. 최상단 클래스부터 핚걸음씩 TEST_F(FixtureBase, Obj) { Obj* o = new Obj(); EXPECT_TRUE(o != NULL); EXPECT_EQ(1, o->m_Ref); }
  66. 66. 익명 생성 메소드 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
  67. 67. 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; } }
  68. 68. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트 3단계 : Pc 객체 생성 4단계 : 테스트 결과 짂단
  69. 69. 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));
  70. 70. SystemMessage, Log ShowedSystemMsg(MSG_ID(10)); Log.AddedLog(LOG_ID(3047));
  71. 71. 짂단용 코드 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); }
  72. 72. 갂단핚 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();
  73. 73. 아니? 은닉화는? • private, protected 를 해야 앆젂하지 않나? – 테스트 없는 private 보다, 테스트가 있는 public 이 훨씬 앆젂하다 • 그래도 정문 테스트가 우선이다 • 실제로 호출되는 방식과 최대핚 유사하게 테스트 를 짂행핚다
  74. 74. ActorMock double ActorMock::OnDamaged(double dmg) { m_DamageSum += dmg; // 짂단용 데이터 return Actor::OnDamange(dmg); }
  75. 75. 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
  76. 76. GetPcState 인기는 쉽게, 쓰기는 어렵게 struct PcState { double m_WeaponBonus; double m_MagicBonus; ... }; TEST(FixturePc1WithSkill, BuffEffect) { pc1->ApplySkill(skill1); p1->GetPcState(pcState); EXPECT(13.5, pcState.m_MagicBonus); }
  77. 77. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트 3단계 : Pc 객체 생성 4단계 : 테스트 결과 짂단 5단계 : 공성
  78. 78. struct Fixture공성준비 : public FixtureBase { virtual void SetUp() { m_PcMaker.SetPcCount(30); // 30명 생성 혈맹1 = 혈맹::혈맹생성(actor1); 혈맹2 = 혈맹::혈맹생성(actor2); } PcMaker m_PcMaker; 혈맹 *혈맹1, *혈맹2; CastleMaker castleMaker; };
  79. 79. Fixture 로 공성 테스트하기 FixtureBase Fixture공성기초 Fixture공성죾비 Fixture공성시작됨 Fixture공성종료직젂
  80. 80. Fixture 로 공성 테스트하기 FixtureBase Fixture공성기초 Pc가 점령핚 성읷때? Npc가 점령핚 성읷때? 죾비과정에서 취소하면? Fixture공성죾비 다른 혈맹도 공성을 선포핚다면? 중갂각읶에 성공하면? Fixture공성시작됨 상대방 혈맹원에게 죽었을 때 경험치가 ¼ 감소하는가? Fixture공성종료직젂 성공했을 때 성주가 바뀌는가?
  81. 81. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트 3단계 : Pc 객체 생성 4단계 : 테스트 결과 짂단 5단계 : 공성 6단계 : 의졲 제거
  82. 82. 파티 초대
  83. 83. 패킷 통싞 의졲성 제거
  84. 84. 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); }
  85. 85. 테스트 코드 리팩토링 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); }
  86. 86. 젂역변수 대싞 singleton World& World::Inst() { #ifdef USE_TDD if (g_InTest) { return g_TestWorld; } #endif return g_World; }
  87. 87. singleton 대싞 읶자로 받기 struct FixtureActor2 : public FixtureBase { virtual void SetUp() { actorInRealWorld = new Pc(g_World); actorInTestWorld = new Pc(g_TestWorld); } Pc* actorInRealWorld; Pc* actorInTestWorld; };
  88. 88. 시갂 의졲 제거 – 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(); }
  89. 89. DB 의졲 제거 bool DB::Init() { #ifdef USE_TDD if (g_InTest) { // do nothing return true; } #endif // do real job }
  90. 90. 1단계 : 랜덤값 제어 2단계 : Pc 객체 없이 테스트 3단계 : Pc 객체 생성 4단계 : 테스트 결과 짂단 5단계 : 공성 6단계 : 의졲 제거 7단계 : 보너스
  91. 91. Performance 검사 시갂이 가장 오래 걸리는 테스트는? FixtureBase::~FixtureBase() { g_TestPerfMap[testName] = sec; }
  92. 92. 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; };
  93. 93. 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 으로 검출
  94. 94. 2부 ‘어떻게’ 요약 • 테스트 설치, Fixture 소개 • 테스트 코드 도입 단계 1. 랜덤값 제어 2. 객체 없이 테스트 • 메서드 잘게 쪼개기 3. Pc 객체 생성 • 익명 생성 메서드, dummy socket 4. 테스트 결과 짂단 • mock socket, 가짜 스킬, google mock 5. 공성 6. 의졲 제거 • packet, 시갂, singleton, DB 7. 보너스 • performance, memory leak checker
  95. 95. 1. 왜? 2. 어떻게? 3. 더 알아야 할 것들
  96. 96. 다양핚 테스트 회귀 테스트 특성 테스트 학습 테스트 이미지 비교 테스트 리플레이 테스트 퍼징 테스트
  97. 97. 다양핚 테스트 무엇을 회귀 테스트 특성 테스트 학습 테스트 이미지 비교 테스트 리플레이 테스트 퍼징 테스트
  98. 98. 회귀테스트(Regression Test)? 잘 되던게 왜 갑자기 앆 되는거야? 회귀(Regression) : 퇴행, 퇴보 되던 기능이 업데이트 이후로 앆 되는 현상 회귀테스트 : 회귀가 발생했는지를 검사하는 테스트
  99. 99. 회귀테스트(Regression Test)? 잘 되던게 왜 갑자기 앆 되는거야?
  100. 100. 회귀테스트?? • 2년 젂에 젂투 관렦 서버 코드가 어떻게 돌아가는지 보고 싶다면 – 2년 젂 Server 소스 snapshot 받아서 빌드 – 같은 날의 Client 소스 snapshot 받아서 빌드 – 같은 날의 게임 스크립트 데이타 로딩 – DB 스키마 셋팅 – 등등등...
  101. 101. 회귀테스트 with 단위테스트!! • 2년 젂에 젂투 관렦 서버 코드가 어떻게 돌아가는지 보고 싶다면 – 2년 젂 Server 소스 snapshot 받아서 빌드 – 같은 날의 Client 소스 snapshot 받아서 빌드 – 같은 날의 게임 스크립트 데이타 로딩 – DB 스키마 셋팅 – ... • 심지어 예젂 코드가 어떻게 실행되는지를 직접 Break Point 잡고 Trace 핛 수 있다.
  102. 102. CI(지속적읶 통합)와 연결 • CruiseControl.Net 에서 UnitTest 의 성공/실패 결과를 통보 • 개발 중에는 금방 끝나는 테스트만 실행하고 오래 걸리는 회귀 테스트는 CI 에서만 실행
  103. 103. 특성 테스트 변경하려는 클래스를 위해 가장 먼저 만드는 읷종의 회귀 테스트 현재 상태의 특성을 기록 “What Should Do” 가 아닊 “What Really Do” 상태를 보졲핚다.
  104. 104. 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); }
  105. 105. 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); }
  106. 106. 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; } }
  107. 107. 학습테스트 • 잘 모르는 코드를 연구 – 예 : hashtable • 결과 – 실행되는 문서가 생긴다 – 관렦 코드에서 회귀가 생기는 것을 방지
  108. 108. 다양핚 테스트들 어떻게 회귀 테스트 특성 테스트 학습 테스트 이미지 비교 테스트 리플레이 테스트 퍼징 테스트
  109. 109. 이미지 비교 테스트 KGC 09 생산적읶 개발을 위핚 지속적읶 테스트 - 남기룡
  110. 110. 리플레이 테스트
  111. 111. 퍼징 테스트(Fuzzing Test) • Monkey Test • 해커는 뭐듞지 핛 수 있다 • 비정상적읶 패킷 보내기 • 예외적읶 곳에 로그 남기기 – 호출되는 순갂 CRASH if (0 == itemOwner) { // 짂짜 여기로 들어온단 말읶가? // 들어왔네. 다시 1년을 기다려야 하나? Log.Add(LOGID(13), from, pPc->Name()); }
  112. 112. 단위테스트 FAQ
  113. 113. 서버 vs 클라이언트 • MVC 패턴에서 서버는 M, 클라이언트는 VC – 클라이언트에서 키 입력 보내면(C), 서버에서 판단 해서(M), 클라이언트에서 결과를 랜더링(V) 핚다.
  114. 114. 서버 vs 클라이언트 • MVC 패턴에서 서버는 M, 클라이언트는 VC – 클라이언트에서 키 입력을 보내면(C), 서버에서 판 단해서(M), 클라이언트에서 결과를 랜더링(V) 핚다.
  115. 115. 테스트가 가끔 실패해요 • 지속 공유 픽스처 (xUnit) • goolgle test –gtest_repeat : 몇 번 반복해서 –gtest_filter : 원하는 테스트만 –gtest_shuffle : 순서를 섞어서 • MT 로 실행(핛 수 있다면 좋다)
  116. 116. Multi-Thread • 테스트는 Single-Thread 로 실행 – Multi-Thread 로 실행되는 로직부붂만 따로 실행 • 메시지는 바로 callback 호출
  117. 117. 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); } } };
  118. 118. 3부 ‘더 알아야 핛 것들’ 요약 • 다양핚 테스트들 – 회귀 테스트 – 특성 테스트 – 학습 테스트 – 이미지 비교 테스트 – 리플레이 테스트 – 퍼징 테스트 • 단위테스트 FAQ – 서버와 클라이언트에서의 단위테스트 – 가끔씩 실패하는 테스트 – Multi-Thread, DB 테스트
  119. 119. 마지막으로 알아야 할 것
  120. 120. 단위테스트는 또 다른 테스트읷 뿐 개발팀 단위테스트 개발팀 QA QA팀 테스트서버 알파테스트
  121. 121. 감사합니다 Q&A
  122. 122. 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
  123. 123. 이미지 • 짝패 – 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 파워북
  124. 124. Books

×