SlideShare a Scribd company logo
1 of 55
Download to read offline
최우영
위메이드 엔터테인먼트
강연자 소개

   최우영
   (주)위메이드 엔터테인먼트
     신규 게임 개발팀 - 서버 파트
   (주)라온 엔터테인먼트
     테일즈런너
     신규 게임 개발
Test?
통합 테스트 VS 단위 테스트

   통합 테스트
     둘 이상의 모듈을 하나의 그룹으로 테스트 하
      는것
     소프트웨어에 기대되는 결과를 확인 하는 것.
     많은 단위들을 실행
   단위 테스트
     단일 단위를 분리하여 실행하는 테스트
단위 테스트란

   다른 코드를 호출한 후 몇 가지 가정이 성
    립하는지 검사하는 코드
   여기서 '단위(unit)'란 메서드나 함수를 의
    미
   X+Y=Z?
좋은 단위 테스트

   자동화
   반복 실행
   쉬운 구현
   쉬운 실행
   빠른 속도
   로직 없는 테스트
테스트 프레임워크

   CppUnit, UnitTest++, TUT, …
   사용하기 쉽고 신뢰할 수 있는 GoogleTest
   http://code.google.com/p/googletest/
   구글의 제품에 사용 중
     Chromium, Protocol Buffers, … 등등
Gtest 사용하기

   gtest 라이브러리 다운로드
   gtest.h 파일 include
   라이브러리 빌드 후 lib 파일 링크
#include <gtestgtest.h>
#pragma comment(lib, “gtest.lib”)

int main(int argc, _TCHAR* argv[])
{
       ::testing::InitGoogleTest(&argc, argv);
       return RUN_ALL_TESTS();
}
테스트 문법의 기본

   ASSERT_TRUE( ACTUAL )
    ASSERT_FALSE( ACTUAL )

   ASSERT_EQ( EXPECTED, ACTUAL )
    ASSERT_NE( EXPECTED, ACTUAL )

   ASSERT_FLOAT_EQ( EXPECTED, ACTUAL )

   ASSERT_STREQ( EXPECTED, ACTUAL)
 테스트 작성
TEST( test_suite, test_case )
{
  ...
  ASSERT_XXX()
  ...
}
간단한 테스트의 작성

   swap() 함수 구현
   X, Y 를 인자로 받고 X = Y, Y = X 로 교홖
   테스트 파일 생성
     Ex) Swap_Test.cpp
   테스트 스위트, 케이스 이름 결정
     보수의 용이성을 위해 테스트 이름을 정한다
     테스트 스위트는 테스트 카테고리
     케이스 이름은 세부적인 테스트를 나타낸다
#include <gtestgtest.h>
TEST( Swap_Test, Swap_True ){

}

[==========]   Running 1 test from 1 test case.
[----------]   Global test environment set-up.
[----------]   1 test from Swap_Test
[ RUN      ]   Swap_Test.Swap_True
[       OK ]   Swap_Test.Swap_True (0 ms)
[----------]   1 test from Swap_Test (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[ PASSED ] 1 test.
#include <gtestgtest.h>
TEST( Swap_Test, Swap_True ){
       int x = 30;
       int y = 15;
       swap( x, y );
       ASSERT_EQ( 15, x ) << "x must be 15";
       ASSERT_EQ( 30, y ) << "y must be 30";
}

   build => fail
   존재하지 않는 함수 호출로 빌드 실패
#include <gtestgtest.h>
void swap(int& x, int& y){}

TEST( Swap_Test, Swap_True ){
       int x = 30;
       int y = 15;
       swap( x, y );
       ASSERT_EQ( 15, x ) << "x must be 15";
       ASSERT_EQ( 30, y ) << "y must be 30";
}

   build => success
    test => failure
   빌드 성공, 테스트 실패
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from Swap_Test
[ RUN      ] Swap_Test.Swap_True
c:....Swap_Test.cpp(124): error: Value of: x
  Actual: 30
Expected: 15
x must be 15
[ FAILED ] Swap_Test.Swap_True (0 ms)
[----------] 1 test from Swap_Test (0 ms total)

[----------]   Global test environment tear-down
[==========]   1 test from 1 test case ran. (0 ms total)
[ PASSED ]     0 tests.
[ FAILED ]     1 test, listed below:
[ FAILED ]     Swap_Test.Swap_True

1 FAILED TEST
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from Swap_Test
[ RUN      ] Swap_Test.Swap_True
c:....Swap_Test.cpp(124): error: Value of: x
  Actual: 30
Expected: 15
x must be 15
[ FAILED ] Swap_Test.Swap_True (0 ms)
[----------] 1 test from Swap_Test (0 ms total)

[----------]   Global test environment tear-down
[==========]   1 test from 1 test case ran. (0 ms total)
[ PASSED ]     0 tests.
[ FAILED ]     1 test, listed below:
[ FAILED ]     Swap_Test.Swap_True

1 FAILED TEST
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from Swap_Test
[ RUN      ] Swap_Test.Swap_True
c:....Swap_Test.cpp(124): error: Value of: x
  Actual: 30   …
Expected: 15   ASSERT_EQ( 15, x ) << "x must be 15“;
x must be 15   …
[ FAILED ] Swap_Test.Swap_True (0 ms)
[----------] 1 test from Swap_Test (0 ms total)

[----------]   Global test environment tear-down
[==========]   1 test from 1 test case ran. (0 ms total)
[ PASSED ]     0 tests.
[ FAILED ]     1 test, listed below:
[ FAILED ]     Swap_Test.Swap_True

 1 FAILED TEST
#include <gtestgtest.h>
void swap(int& x, int& y){
       int t = x;
       y = x;
       x = t;
}
TEST( Swap_Test, Swap_True ){
       int x = 30;
       int y = 15;
       swap( x, y );
       ASSERT_EQ( 15, x ) << "x must be 15";
       ASSERT_EQ( 30, y ) << "y must be 30";
}

   build => success
    test => pass
   테스트 통과
[==========]   Running 1 test from 1 test case.
[----------]   Global test environment set-up.
[----------]   1 test from Swap_Test
[ RUN      ]   Swap_Test.Swap_True
[       OK ]   Swap_Test.Swap_True (0 ms)
[----------]   1 test from Swap_Test (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[ PASSED ] 1 test.
의존성 제거

   클래스 간 복합적인 관계 의존성 존재
   테스트 저해 설계 : 파일, 스레드, 통신등
    외부 의존물을 사용
   통합테스트 => 단위 테스트
테스트 대상                                       패킷 변홖




      CPacketHandler                        CPacketParser

    +ProcessPacket()                        +ParsePacket()




                       CConnectionManager

                       +SendPacket()




                통신 담당
CPacketHandler                        CPacketParser

                                                 +ProcessPacket()                        +ParsePacket()




                                                                    CConnectionManager

                                                                    +SendPacket()



...
TEST( CPacketHandler, ProcessPacketSuccessWithItemBuyReq){
    CPacketHandler PacketHandler;

    MSG_ITEM_BUY_REQ ItemBuyReq;

    bool bRet = PacketHandler.ProcessPacket( &ItemBuyReq, ItemBuyReq.Size );

    ASSERT_TRUE( bRET );
}
CPacketHandler                        CPacketParser

                                                            +ProcessPacket()                        +ParsePacket()



class CPacketHandler
{                                                                              CConnectionManager
  CConnectionManager*      m_pConnectionManager;                               +SendPacket()
  CPacketParser*           m_pPacketParser;

public:
  CPacketHandler()
  {
      m_pConnectionManager    = new CConnectionManager();
      m_pPacketParser         = new CPacketParser();
      ...
  }

     BOOL ProcessPacket ( const char * pBuf, size_t packetSize )
     {
         MSG_BASE* pMsg = m_pPacketParser->ParsePacket( pBuf, packetSize );
         ...
         if( pMsg->GetProtocol() == MSG_PROTOCOL_ITEM_BUY_REQ )
         {
             m_pConnectionManager->SendPacket( new PACKET_ITEM_BUY_ANS() );
             return TRUE;
         }
         ...
         return FALSE;
     }
     ...
};
CPacketHandler                        CPacketParser

                                                    +ProcessPacket()                        +ParsePacket()




                                                                       CConnectionManager

                                                                       +SendPacket()




class CPacketParser
{
public:
        ......

       MSG_BASE* ParsePacket( const char* pBuf, size_t packetSize )
       {
              MSG_BASE* pMsg = (MSG_BASE*)pBuf;
              ... // 검증 코드 및 데이터 채우기
              return pMsg;
       }

       ......
};
CPacketHandler                        CPacketParser

                                                 +ProcessPacket()                        +ParsePacket()




                                                                    CConnectionManager

                                                                    +SendPacket()




class CConnectionManager
{
        CSendQueue* m_pSendQueue;
public:
        ......

       void SendPacket( MSG_BASE* pMsg )
       {
              // 실제로 메시지를 보내는 코드
              m_pSendQueue->PostMessage(pMsg);
       }

       ......
};
CPacketHandler                        CPacketParser
                                                 +ProcessPacket()                        +ParsePacket()




                                                                    CConnectionManager
                                                                    +SendPacket()




                                                                        CSendQueue
                                                                       +PostMessage()

class CConnectionManager
{
        CSendQueue* m_pSendQueue;
public:
        ......

       void SendPacket( MSG_BASE* pMsg )
       {
              // 실제로 메시지를 보내는 코드
              m_pSendQueue->PostMessage(pMsg);
       }

       ......
};
CPacketHandler                        CPacketParser
+ProcessPacket()                        +ParsePacket()




                   CConnectionManager
                   +SendPacket()




                       CSendQueue
                      +PostMessage()
CPacketHandler                        CPacketParser

+ProcessPacket()                        +ParsePacket()




                   CConnectionManager

                   +SendPacket()




                       CSendQueue

                      +PostMessage()




CTcpSocket          CThreadBase
CPacketHandler                        CPacketParser

+ProcessPacket()                        +ParsePacket()




                   CConnectionManager

                   +SendPacket()




                       CSendQueue

                      +PostMessage()




CTcpSocket          CThreadBase         CPacketArchieve
스텁(Stub)

   외부 의존물을 대신하기 위해 간접 계층
    추가
CPacketHandler                        CPacketParser

                                                         +ProcessPacket()                        +ParsePacket()




  CPacketHandler                        CPacketParser                       CConnectionManager
+ProcessPacket()                        +ParsePacket()                      +SendPacket()




                   CConnectionManager                                           CSendQueue
                   +SendPacket()                                               +PostMessage()




                                                         CTcpSocket          CThreadBase         CPacketArchieve
간접 계층

   외부 의존물에 접근하기 위해 인터페이스
    추가
CPacketHandler                        CPacketParser       CPacketHandler                     CPacketParser

+ProcessPacket()                        +ParsePacket()    +ProcessPacket()                     +ParsePacket()




                   CConnectionManager                                          IConnectionManager
                   +SendPacket()                                               +SendPacket()




                       CSendQueue                            FakeConnectionManager                CConnectionManager

                      +PostMessage()                         +SendPacket()                      +SendPacket()




                                                                                                       CSendQueue
CTcpSocket          CThreadBase         CPacketArchieve
                                                                                                      +PostMessage()
CPacketHandler                     CPacketParser
                                                                    +ProcessPacket()                     +ParsePacket()




                                                                                         IConnectionManager
                                                                                         +SendPacket()

class CPacketHandler
{
   IConnectionManager*    m_pConnectionManager;                        FakeConnectionManager
   CPacketParser*         m_pPacketParser;                             +SendPacket()


public:
   CPacketHandler()
   {
        m_pConnectionManager = new CConnectionManager();
        m_pPacketParser      = new CPacketParser();
        ...
   }

     BOOL ProcessPacket ( const char * pBuf, size_t packetSize )
     {
         MSG_BASE* pMsg = m_pPacketParser->ParsePacket( pBuf, packetSize );
         ...
         if( pMsg->GetProtocol() == MSG_PROTOCOL_ITEM_BUY_REQ )
         {
              m_pConnectionManager->SendPacket( new PACKET_ITEM_BUY_ANS() );
              return TRUE;
         }
         ...
         return FALSE;
     }
     ...
};
스텁 주입하기

   생성자
   get, set 프로퍼티
   매개변수
   추상 팩토리
생성자 주입
   장점                             class CPacketHandler
                                   {
     테스트 코드의 가독성 향상                 IConnectionManager* m_pConnectionManager;
     해당 매개 변수가 필수임을 알림
                                     ...

   단점                    public:
       상호 참조의 경우 생성 문제      CPacketHandler(IConnectionManager* pManager)
                             {
       매개 변수의 개수가 늘어날 수록        m_pConnectionManager = pManager;
        가독성, 관리용이성 악화            ...
                             }
                          ...
                          };

                                   ...
                                   FakeConnectionManager FakeManager;
                                   CPacketHandler Handler( &FakeManager );
                                   ...
get, set 프로퍼티
   작성의 용이함         class CPacketHandler
                    {
   필수적이지 않은 매개변수     IConnectionManager* m_pConnectionManager;
                      ...

                    public:
                       SetConnectionManager
                                      (IConnectionManager* pManager)
                       {
                           m_pConnectionManager = pManager;
                       }
                        ...
                    };

                    ...
                    FakeConnectionManager FakeManager;
                    CPacketHandler Handler;
                    Handler.SetConnectionManager( &FakeManager );
                    ...
그 외 대표적 방법들

   매개변수 젂달             추상 팩토리
     함수를 호출할 때 함께        실제 객체와 스텁을 생
     의존물을 넣어준다.           성하는 팩토리를 생성
                          하는 추상 팩토리를 생
                          성
캡슐화 문제

   테스트 용이성을 높이기 위한 방법
   Public
   상속
   Friend
   조건부 컴파일(#ifdef)
   상용 프레임워크
픽스쳐
class fixture_name;

TEST_F( fixture_name, case_name )
{
  ...
  ...// Some Tests
  ASSERT_XXX();
  ...
}
class fixture_name : public testing::Test
{
   void     SetUp();
   void     TearDown();
};

TEST_F( fixture_name, case_name )
{
  ...
  ...// Some Tests
  ASSERT_XXX();
  ...
}
목 객체(Mock Object)

   단위 테스트의 통과, 실패를 판단하는 가
    짜 객체
   하나의 테스트에 하나의 목 객체 사용
스텁 vs 목

   스텁: 객체의 대체제. 테스트가 가능하도
    록 의존물을 없애는 것
   목: 테스트의 통과, 실패를 검증
목 객체의 사용

   CPacketHandler가 IConnectionManager::
    SendPacket() 메서드를 호출하는지 확인
   ConnectionManager의 인터페이스를 추
    출, 스텁으로 교체.
   스텁의 SendPacket 메서드를 오버라이드
    하여 체크
                CPacketHandler                   CPacketParser

              +ProcessPacket()                   +ParsePacket()




                                 IConnectionManager

                                 +SendPacket()
class FakeConnectionManager: public IConnectionManager{
public:
  int m_nCalled;
  MSG_BASE* m_pPacket;
  ...

     virtual void SendPacket( MSG_BASE* pPacket ){
         ++m_nCalled;
         m_pPacket = pPacket;
     }
};

TEST( PacketHandler, ProcessPacketWithItemBuyReq ){
  ...
  Handler.ProcessPacket( &ItemBuyReq );

     ASSERT_EQ( 1, FakeManager.m_nCalled );
     ASSERT_EQ( MSG_PROTOCOL_ITEM_BUY_ANS, FakeManager.m_pPacket->GetProtocol() );
}
격리 프레임워크(Isolation Frame
work)
   목과 스텁 객체를 쉽게 생성할 수 있게 해
    주는 API의 모음
   테스트의 반복 작성시 도움을 받을 수 있
    다.
   예상값(기대값)의 측정
     함수 호출
     호출 횟수
     인자
Google Mock 사용법

   목 오브젝트 생성

class Impl
{
public:
      virtual void SetPos(float x, float y);
      virtual float GetX() const;
}

class MockImpl: public Impl {
public:
      MOCK_METHOD2( SetPos, void( float x, float y );
      MOCK_CONST_METHOD0( GetX, float() );
};
함수 호출 측정
TEST( MockExample, Expect_call )
{
      MockImpl impl;
      EXPECT_CALL( impl, GetPos() );
      EXPECT_CALL( impl, SetPos( _, _ ) );
      …
      // calls function
      …
}
함수 호출 횟수 측정
TEST( MockExample, Expect_call )
{
      MockImpl impl;
      EXPECT_CALL( impl, GetPos() )
             .Times( 3 );
      …
      // calls function
      …
}
리턴값 지정
TEST( MockExample, Expect_call )
{
      MockImpl impl;
      // ON_CALL( impl, GetPos() )
      EXPECT_CALL( impl, GetPos() )
             .WillByDefault(Return(50.0f));
      …
      // calls function
      …
}
Class FakeConnectionManager: public IConnectionManager{
public:
  int m_nCalled;
  MSG_BASE* m_pPacket;
  ...

     virtual void SendPacket( MSG_BASE* pPacket ){
         ++m_nCalled;
         m_pPacket = pPacket;
     }
};

TEST( PacketHandler, ProcessPacketWithItemBuyReq ){
  FakeConnectionManager FakeManager;

     PacketHandler Handler(&FakeManager);
     ...
     Handler.ProcessPacket( &ItemBuyReq );

     ASSERT_EQ( 1, FakeManager.m_nCalled );
     ASSERT_EQ( MSG_PROTOCOL_ITEM_BUY_ANS, FakeManager.m_pPacket->GetProtocol() );
}
class FakeConnectionManager: public IConnectionManager{
public:
   MOCK_METHOD1( SendPakcet, void(MSG_BASE* pPacket);
};

int IsPacket(MSG_BASE *p){
  return (p != NULL) && (p->GetProtocol() == MSG_PROTOCOL_ITEM_BUY_ANS);
}

TEST( PacketHandler, ProcessPacketWithItemBuyReq ){
  FakeConnectionManager FakeManager;
  EXPECT_CALL( FakeManager, SendPacket( Truly(IsPacket) ) )
      .Times( AtLeast(1) );

    PacketHandler Handler( &FakeManager );
    ...
    Handler.ProcessPacket( &ItemBuyReq );
}
팁

   목 객체는 테스트 당 1개
   목 객체, 테스트 객체를 제외한 모든 의존
    물은 Stub으로 대체
   ASSERT는 가급적 테스트 당 1개
   목 객체를 재사용 : 목 객체 내부에
    ASSERT 삽입 금지
   모든 테스트는 격리해서 실행
감사합니다

   whoo24@gmail.com
   Twitter: whoo24
   Blog : http://blog.wychoe.net

More Related Content

What's hot

덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012Esun Kim
 
.NET에서 비동기 프로그래밍 배우기
.NET에서 비동기 프로그래밍 배우기.NET에서 비동기 프로그래밍 배우기
.NET에서 비동기 프로그래밍 배우기Seong Won Mun
 
실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략YEONG-CHEON YOU
 
Multithread & shared_ptr
Multithread & shared_ptrMultithread & shared_ptr
Multithread & shared_ptr내훈 정
 
Android audio system(오디오 플링거 서비스 초기화)
Android audio system(오디오 플링거 서비스 초기화)Android audio system(오디오 플링거 서비스 초기화)
Android audio system(오디오 플링거 서비스 초기화)fefe7270
 
[IGC 2017] 펄어비스 민경인 - Mmorpg를 위한 voxel 기반 네비게이션 라이브러리 개발기
[IGC 2017] 펄어비스 민경인 - Mmorpg를 위한 voxel 기반 네비게이션 라이브러리 개발기[IGC 2017] 펄어비스 민경인 - Mmorpg를 위한 voxel 기반 네비게이션 라이브러리 개발기
[IGC 2017] 펄어비스 민경인 - Mmorpg를 위한 voxel 기반 네비게이션 라이브러리 개발기강 민우
 
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요? (2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요? 내훈 정
 
Tcache Exploitation
Tcache ExploitationTcache Exploitation
Tcache ExploitationAngel Boy
 
[MGDC] 리눅스 게임 서버 성능 분석하기 - 아이펀팩토리 김진욱 CTO
[MGDC] 리눅스 게임 서버 성능 분석하기 - 아이펀팩토리 김진욱 CTO[MGDC] 리눅스 게임 서버 성능 분석하기 - 아이펀팩토리 김진욱 CTO
[MGDC] 리눅스 게임 서버 성능 분석하기 - 아이펀팩토리 김진욱 CTOiFunFactory Inc.
 
Systems@Scale 2021 BPF Performance Getting Started
Systems@Scale 2021 BPF Performance Getting StartedSystems@Scale 2021 BPF Performance Getting Started
Systems@Scale 2021 BPF Performance Getting StartedBrendan Gregg
 
Linux Profiling at Netflix
Linux Profiling at NetflixLinux Profiling at Netflix
Linux Profiling at NetflixBrendan Gregg
 
BPF Internals (eBPF)
BPF Internals (eBPF)BPF Internals (eBPF)
BPF Internals (eBPF)Brendan Gregg
 
Résumé javascript bac info
Résumé javascript bac infoRésumé javascript bac info
Résumé javascript bac infoborhen boukthir
 
Refelction의 개념과 RTTR 라이브러리
Refelction의 개념과 RTTR 라이브러리Refelction의 개념과 RTTR 라이브러리
Refelction의 개념과 RTTR 라이브러리ssuser7c5a40
 
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션NHN FORWARD
 
게임서버프로그래밍 #2 - IOCP Adv
게임서버프로그래밍 #2 - IOCP Adv게임서버프로그래밍 #2 - IOCP Adv
게임서버프로그래밍 #2 - IOCP AdvSeungmo Koo
 
테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템QooJuice
 
How NOT to Write a Microbenchmark
How NOT to Write a MicrobenchmarkHow NOT to Write a Microbenchmark
How NOT to Write a MicrobenchmarkAzul Systems Inc.
 

What's hot (20)

HTTP Security Headers
HTTP Security HeadersHTTP Security Headers
HTTP Security Headers
 
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
 
.NET에서 비동기 프로그래밍 배우기
.NET에서 비동기 프로그래밍 배우기.NET에서 비동기 프로그래밍 배우기
.NET에서 비동기 프로그래밍 배우기
 
실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략실시간 게임 서버 최적화 전략
실시간 게임 서버 최적화 전략
 
Multithread & shared_ptr
Multithread & shared_ptrMultithread & shared_ptr
Multithread & shared_ptr
 
Android audio system(오디오 플링거 서비스 초기화)
Android audio system(오디오 플링거 서비스 초기화)Android audio system(오디오 플링거 서비스 초기화)
Android audio system(오디오 플링거 서비스 초기화)
 
[IGC 2017] 펄어비스 민경인 - Mmorpg를 위한 voxel 기반 네비게이션 라이브러리 개발기
[IGC 2017] 펄어비스 민경인 - Mmorpg를 위한 voxel 기반 네비게이션 라이브러리 개발기[IGC 2017] 펄어비스 민경인 - Mmorpg를 위한 voxel 기반 네비게이션 라이브러리 개발기
[IGC 2017] 펄어비스 민경인 - Mmorpg를 위한 voxel 기반 네비게이션 라이브러리 개발기
 
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요? (2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
 
Tcache Exploitation
Tcache ExploitationTcache Exploitation
Tcache Exploitation
 
Rapid json tutorial
Rapid json tutorialRapid json tutorial
Rapid json tutorial
 
[MGDC] 리눅스 게임 서버 성능 분석하기 - 아이펀팩토리 김진욱 CTO
[MGDC] 리눅스 게임 서버 성능 분석하기 - 아이펀팩토리 김진욱 CTO[MGDC] 리눅스 게임 서버 성능 분석하기 - 아이펀팩토리 김진욱 CTO
[MGDC] 리눅스 게임 서버 성능 분석하기 - 아이펀팩토리 김진욱 CTO
 
Systems@Scale 2021 BPF Performance Getting Started
Systems@Scale 2021 BPF Performance Getting StartedSystems@Scale 2021 BPF Performance Getting Started
Systems@Scale 2021 BPF Performance Getting Started
 
Linux Profiling at Netflix
Linux Profiling at NetflixLinux Profiling at Netflix
Linux Profiling at Netflix
 
BPF Internals (eBPF)
BPF Internals (eBPF)BPF Internals (eBPF)
BPF Internals (eBPF)
 
Résumé javascript bac info
Résumé javascript bac infoRésumé javascript bac info
Résumé javascript bac info
 
Refelction의 개념과 RTTR 라이브러리
Refelction의 개념과 RTTR 라이브러리Refelction의 개념과 RTTR 라이브러리
Refelction의 개념과 RTTR 라이브러리
 
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
 
게임서버프로그래밍 #2 - IOCP Adv
게임서버프로그래밍 #2 - IOCP Adv게임서버프로그래밍 #2 - IOCP Adv
게임서버프로그래밍 #2 - IOCP Adv
 
테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템테라로 살펴본 MMORPG의 논타겟팅 시스템
테라로 살펴본 MMORPG의 논타겟팅 시스템
 
How NOT to Write a Microbenchmark
How NOT to Write a MicrobenchmarkHow NOT to Write a Microbenchmark
How NOT to Write a Microbenchmark
 

Similar to C++ 프로젝트에 단위 테스트 도입하기

C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기Heo Seungwook
 
MapReduce 실행 샘플 (K-mer Counting, K-means Clustering)
MapReduce 실행 샘플 (K-mer Counting, K-means Clustering)MapReduce 실행 샘플 (K-mer Counting, K-means Clustering)
MapReduce 실행 샘플 (K-mer Counting, K-means Clustering)주영 송
 
3ds maxscript 튜토리얼_20151206_서진택
3ds maxscript 튜토리얼_20151206_서진택3ds maxscript 튜토리얼_20151206_서진택
3ds maxscript 튜토리얼_20151206_서진택JinTaek Seo
 
C#을 사용한 빠른 툴 개발
C#을 사용한 빠른 툴 개발C#을 사용한 빠른 툴 개발
C#을 사용한 빠른 툴 개발흥배 최
 
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자Donghyeok Kang
 
파이썬 스터디 15장
파이썬 스터디 15장파이썬 스터디 15장
파이썬 스터디 15장SeongHyun Ahn
 
NDC11_슈퍼클래스
NDC11_슈퍼클래스NDC11_슈퍼클래스
NDC11_슈퍼클래스noerror
 
Prometheus Project Journey
Prometheus Project JourneyPrometheus Project Journey
Prometheus Project JourneyJinwoong Kim
 
불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14 불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14 명신 김
 
Multi mechanize
Multi mechanizeMulti mechanize
Multi mechanizeSungMin OH
 
20220716_만들면서 느껴보는 POP
20220716_만들면서 느껴보는 POP20220716_만들면서 느껴보는 POP
20220716_만들면서 느껴보는 POPChiwon Song
 
120908 레거시코드활용전략 4장5장
120908 레거시코드활용전략 4장5장120908 레거시코드활용전략 4장5장
120908 레거시코드활용전략 4장5장tedypicker
 
20150212 c++11 features used in crow
20150212 c++11 features used in crow20150212 c++11 features used in crow
20150212 c++11 features used in crowJaeseung Ha
 
(스프링교육/마이바티스교육학원추천_탑크리에듀)#10.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
(스프링교육/마이바티스교육학원추천_탑크리에듀)#10.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)(스프링교육/마이바티스교육학원추천_탑크리에듀)#10.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
(스프링교육/마이바티스교육학원추천_탑크리에듀)#10.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)탑크리에듀(구로디지털단지역3번출구 2분거리)
 
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It종빈 오
 
[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이
[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이
[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이GangSeok Lee
 
파이썬 데이터베이스 연결 1탄
파이썬 데이터베이스 연결 1탄파이썬 데이터베이스 연결 1탄
파이썬 데이터베이스 연결 1탄SeongHyun Ahn
 

Similar to C++ 프로젝트에 단위 테스트 도입하기 (20)

C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기
 
MapReduce 실행 샘플 (K-mer Counting, K-means Clustering)
MapReduce 실행 샘플 (K-mer Counting, K-means Clustering)MapReduce 실행 샘플 (K-mer Counting, K-means Clustering)
MapReduce 실행 샘플 (K-mer Counting, K-means Clustering)
 
3ds maxscript 튜토리얼_20151206_서진택
3ds maxscript 튜토리얼_20151206_서진택3ds maxscript 튜토리얼_20151206_서진택
3ds maxscript 튜토리얼_20151206_서진택
 
C#을 사용한 빠른 툴 개발
C#을 사용한 빠른 툴 개발C#을 사용한 빠른 툴 개발
C#을 사용한 빠른 툴 개발
 
Basic git-commands
Basic git-commandsBasic git-commands
Basic git-commands
 
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자
 
파이썬 스터디 15장
파이썬 스터디 15장파이썬 스터디 15장
파이썬 스터디 15장
 
NDC11_슈퍼클래스
NDC11_슈퍼클래스NDC11_슈퍼클래스
NDC11_슈퍼클래스
 
Prometheus Project Journey
Prometheus Project JourneyPrometheus Project Journey
Prometheus Project Journey
 
Rx java intro
Rx java introRx java intro
Rx java intro
 
불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14 불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14
 
JUnit & AssertJ
JUnit & AssertJJUnit & AssertJ
JUnit & AssertJ
 
Multi mechanize
Multi mechanizeMulti mechanize
Multi mechanize
 
20220716_만들면서 느껴보는 POP
20220716_만들면서 느껴보는 POP20220716_만들면서 느껴보는 POP
20220716_만들면서 느껴보는 POP
 
120908 레거시코드활용전략 4장5장
120908 레거시코드활용전략 4장5장120908 레거시코드활용전략 4장5장
120908 레거시코드활용전략 4장5장
 
20150212 c++11 features used in crow
20150212 c++11 features used in crow20150212 c++11 features used in crow
20150212 c++11 features used in crow
 
(스프링교육/마이바티스교육학원추천_탑크리에듀)#10.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
(스프링교육/마이바티스교육학원추천_탑크리에듀)#10.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)(스프링교육/마이바티스교육학원추천_탑크리에듀)#10.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
(스프링교육/마이바티스교육학원추천_탑크리에듀)#10.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
 
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It
 
[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이
[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이
[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이
 
파이썬 데이터베이스 연결 1탄
파이썬 데이터베이스 연결 1탄파이썬 데이터베이스 연결 1탄
파이썬 데이터베이스 연결 1탄
 

More from OnGameServer

SDC 3rd 안중원님 - InGame CashShop 개발 하기
SDC 3rd 안중원님 - InGame CashShop 개발 하기SDC 3rd 안중원님 - InGame CashShop 개발 하기
SDC 3rd 안중원님 - InGame CashShop 개발 하기OnGameServer
 
이욱진님 - 메모리 관리자로부터 배우기
이욱진님 - 메모리 관리자로부터 배우기이욱진님 - 메모리 관리자로부터 배우기
이욱진님 - 메모리 관리자로부터 배우기OnGameServer
 
임영기님 - 코드 리뷰 시스템 도입하기
임영기님 - 코드 리뷰 시스템 도입하기임영기님 - 코드 리뷰 시스템 도입하기
임영기님 - 코드 리뷰 시스템 도입하기OnGameServer
 
안준석님 - 안드로이드 프로세스들의 통신 메커니즘 : 바인더 이야기
안준석님 - 안드로이드 프로세스들의 통신 메커니즘 : 바인더 이야기안준석님 - 안드로이드 프로세스들의 통신 메커니즘 : 바인더 이야기
안준석님 - 안드로이드 프로세스들의 통신 메커니즘 : 바인더 이야기OnGameServer
 
SDC 3rd 최흥배님 - Boost.multi_index 사용하기
SDC 3rd 최흥배님 - Boost.multi_index 사용하기SDC 3rd 최흥배님 - Boost.multi_index 사용하기
SDC 3rd 최흥배님 - Boost.multi_index 사용하기OnGameServer
 
Boost 라이브리와 C++11
Boost 라이브리와 C++11Boost 라이브리와 C++11
Boost 라이브리와 C++11OnGameServer
 
초보자를 위한 분산 캐시 이야기
초보자를 위한 분산 캐시 이야기초보자를 위한 분산 캐시 이야기
초보자를 위한 분산 캐시 이야기OnGameServer
 
Multi thread game server
Multi thread game serverMulti thread game server
Multi thread game serverOnGameServer
 
Windows os 상에서 효율적인 덤프
Windows os 상에서 효율적인 덤프Windows os 상에서 효율적인 덤프
Windows os 상에서 효율적인 덤프OnGameServer
 
Mongo db 시작하기
Mongo db 시작하기Mongo db 시작하기
Mongo db 시작하기OnGameServer
 
MinWin에 대해서
MinWin에 대해서MinWin에 대해서
MinWin에 대해서OnGameServer
 
Microsoft SharePoint를 활용한 개발환경 구축
Microsoft SharePoint를 활용한 개발환경 구축Microsoft SharePoint를 활용한 개발환경 구축
Microsoft SharePoint를 활용한 개발환경 구축OnGameServer
 
IPv6 이론과 소켓 프로그래밍
IPv6 이론과 소켓 프로그래밍IPv6 이론과 소켓 프로그래밍
IPv6 이론과 소켓 프로그래밍OnGameServer
 
해외 취업 이야기
해외 취업 이야기해외 취업 이야기
해외 취업 이야기OnGameServer
 
게임 개발에 도움을 주는 CruiseControl.NET과 Windows Terminal
게임 개발에 도움을 주는 CruiseControl.NET과 Windows Terminal게임 개발에 도움을 주는 CruiseControl.NET과 Windows Terminal
게임 개발에 도움을 주는 CruiseControl.NET과 Windows TerminalOnGameServer
 

More from OnGameServer (15)

SDC 3rd 안중원님 - InGame CashShop 개발 하기
SDC 3rd 안중원님 - InGame CashShop 개발 하기SDC 3rd 안중원님 - InGame CashShop 개발 하기
SDC 3rd 안중원님 - InGame CashShop 개발 하기
 
이욱진님 - 메모리 관리자로부터 배우기
이욱진님 - 메모리 관리자로부터 배우기이욱진님 - 메모리 관리자로부터 배우기
이욱진님 - 메모리 관리자로부터 배우기
 
임영기님 - 코드 리뷰 시스템 도입하기
임영기님 - 코드 리뷰 시스템 도입하기임영기님 - 코드 리뷰 시스템 도입하기
임영기님 - 코드 리뷰 시스템 도입하기
 
안준석님 - 안드로이드 프로세스들의 통신 메커니즘 : 바인더 이야기
안준석님 - 안드로이드 프로세스들의 통신 메커니즘 : 바인더 이야기안준석님 - 안드로이드 프로세스들의 통신 메커니즘 : 바인더 이야기
안준석님 - 안드로이드 프로세스들의 통신 메커니즘 : 바인더 이야기
 
SDC 3rd 최흥배님 - Boost.multi_index 사용하기
SDC 3rd 최흥배님 - Boost.multi_index 사용하기SDC 3rd 최흥배님 - Boost.multi_index 사용하기
SDC 3rd 최흥배님 - Boost.multi_index 사용하기
 
Boost 라이브리와 C++11
Boost 라이브리와 C++11Boost 라이브리와 C++11
Boost 라이브리와 C++11
 
초보자를 위한 분산 캐시 이야기
초보자를 위한 분산 캐시 이야기초보자를 위한 분산 캐시 이야기
초보자를 위한 분산 캐시 이야기
 
Multi thread game server
Multi thread game serverMulti thread game server
Multi thread game server
 
Windows os 상에서 효율적인 덤프
Windows os 상에서 효율적인 덤프Windows os 상에서 효율적인 덤프
Windows os 상에서 효율적인 덤프
 
Mongo db 시작하기
Mongo db 시작하기Mongo db 시작하기
Mongo db 시작하기
 
MinWin에 대해서
MinWin에 대해서MinWin에 대해서
MinWin에 대해서
 
Microsoft SharePoint를 활용한 개발환경 구축
Microsoft SharePoint를 활용한 개발환경 구축Microsoft SharePoint를 활용한 개발환경 구축
Microsoft SharePoint를 활용한 개발환경 구축
 
IPv6 이론과 소켓 프로그래밍
IPv6 이론과 소켓 프로그래밍IPv6 이론과 소켓 프로그래밍
IPv6 이론과 소켓 프로그래밍
 
해외 취업 이야기
해외 취업 이야기해외 취업 이야기
해외 취업 이야기
 
게임 개발에 도움을 주는 CruiseControl.NET과 Windows Terminal
게임 개발에 도움을 주는 CruiseControl.NET과 Windows Terminal게임 개발에 도움을 주는 CruiseControl.NET과 Windows Terminal
게임 개발에 도움을 주는 CruiseControl.NET과 Windows Terminal
 

C++ 프로젝트에 단위 테스트 도입하기

  • 2. 강연자 소개  최우영  (주)위메이드 엔터테인먼트  신규 게임 개발팀 - 서버 파트  (주)라온 엔터테인먼트  테일즈런너  신규 게임 개발
  • 4. 통합 테스트 VS 단위 테스트  통합 테스트  둘 이상의 모듈을 하나의 그룹으로 테스트 하 는것  소프트웨어에 기대되는 결과를 확인 하는 것.  많은 단위들을 실행  단위 테스트  단일 단위를 분리하여 실행하는 테스트
  • 5. 단위 테스트란  다른 코드를 호출한 후 몇 가지 가정이 성 립하는지 검사하는 코드  여기서 '단위(unit)'란 메서드나 함수를 의 미  X+Y=Z?
  • 6. 좋은 단위 테스트  자동화  반복 실행  쉬운 구현  쉬운 실행  빠른 속도  로직 없는 테스트
  • 7. 테스트 프레임워크  CppUnit, UnitTest++, TUT, …  사용하기 쉽고 신뢰할 수 있는 GoogleTest  http://code.google.com/p/googletest/  구글의 제품에 사용 중  Chromium, Protocol Buffers, … 등등
  • 8. Gtest 사용하기  gtest 라이브러리 다운로드  gtest.h 파일 include  라이브러리 빌드 후 lib 파일 링크 #include <gtestgtest.h> #pragma comment(lib, “gtest.lib”) int main(int argc, _TCHAR* argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }
  • 9. 테스트 문법의 기본  ASSERT_TRUE( ACTUAL ) ASSERT_FALSE( ACTUAL )  ASSERT_EQ( EXPECTED, ACTUAL ) ASSERT_NE( EXPECTED, ACTUAL )  ASSERT_FLOAT_EQ( EXPECTED, ACTUAL )  ASSERT_STREQ( EXPECTED, ACTUAL)
  • 10.  테스트 작성 TEST( test_suite, test_case ) { ... ASSERT_XXX() ... }
  • 11. 간단한 테스트의 작성  swap() 함수 구현  X, Y 를 인자로 받고 X = Y, Y = X 로 교홖
  • 12. 테스트 파일 생성  Ex) Swap_Test.cpp  테스트 스위트, 케이스 이름 결정  보수의 용이성을 위해 테스트 이름을 정한다  테스트 스위트는 테스트 카테고리  케이스 이름은 세부적인 테스트를 나타낸다
  • 13. #include <gtestgtest.h> TEST( Swap_Test, Swap_True ){ } [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from Swap_Test [ RUN ] Swap_Test.Swap_True [ OK ] Swap_Test.Swap_True (0 ms) [----------] 1 test from Swap_Test (0 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (0 ms total) [ PASSED ] 1 test.
  • 14. #include <gtestgtest.h> TEST( Swap_Test, Swap_True ){ int x = 30; int y = 15; swap( x, y ); ASSERT_EQ( 15, x ) << "x must be 15"; ASSERT_EQ( 30, y ) << "y must be 30"; }  build => fail  존재하지 않는 함수 호출로 빌드 실패
  • 15. #include <gtestgtest.h> void swap(int& x, int& y){} TEST( Swap_Test, Swap_True ){ int x = 30; int y = 15; swap( x, y ); ASSERT_EQ( 15, x ) << "x must be 15"; ASSERT_EQ( 30, y ) << "y must be 30"; }  build => success test => failure  빌드 성공, 테스트 실패
  • 16. [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from Swap_Test [ RUN ] Swap_Test.Swap_True c:....Swap_Test.cpp(124): error: Value of: x Actual: 30 Expected: 15 x must be 15 [ FAILED ] Swap_Test.Swap_True (0 ms) [----------] 1 test from Swap_Test (0 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (0 ms total) [ PASSED ] 0 tests. [ FAILED ] 1 test, listed below: [ FAILED ] Swap_Test.Swap_True 1 FAILED TEST
  • 17. [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from Swap_Test [ RUN ] Swap_Test.Swap_True c:....Swap_Test.cpp(124): error: Value of: x Actual: 30 Expected: 15 x must be 15 [ FAILED ] Swap_Test.Swap_True (0 ms) [----------] 1 test from Swap_Test (0 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (0 ms total) [ PASSED ] 0 tests. [ FAILED ] 1 test, listed below: [ FAILED ] Swap_Test.Swap_True 1 FAILED TEST
  • 18. [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from Swap_Test [ RUN ] Swap_Test.Swap_True c:....Swap_Test.cpp(124): error: Value of: x Actual: 30 … Expected: 15 ASSERT_EQ( 15, x ) << "x must be 15“; x must be 15 … [ FAILED ] Swap_Test.Swap_True (0 ms) [----------] 1 test from Swap_Test (0 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (0 ms total) [ PASSED ] 0 tests. [ FAILED ] 1 test, listed below: [ FAILED ] Swap_Test.Swap_True 1 FAILED TEST
  • 19. #include <gtestgtest.h> void swap(int& x, int& y){ int t = x; y = x; x = t; } TEST( Swap_Test, Swap_True ){ int x = 30; int y = 15; swap( x, y ); ASSERT_EQ( 15, x ) << "x must be 15"; ASSERT_EQ( 30, y ) << "y must be 30"; }  build => success test => pass  테스트 통과
  • 20. [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from Swap_Test [ RUN ] Swap_Test.Swap_True [ OK ] Swap_Test.Swap_True (0 ms) [----------] 1 test from Swap_Test (0 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (0 ms total) [ PASSED ] 1 test.
  • 21. 의존성 제거  클래스 간 복합적인 관계 의존성 존재  테스트 저해 설계 : 파일, 스레드, 통신등 외부 의존물을 사용  통합테스트 => 단위 테스트
  • 22. 테스트 대상 패킷 변홖 CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() 통신 담당
  • 23. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() ... TEST( CPacketHandler, ProcessPacketSuccessWithItemBuyReq){ CPacketHandler PacketHandler; MSG_ITEM_BUY_REQ ItemBuyReq; bool bRet = PacketHandler.ProcessPacket( &ItemBuyReq, ItemBuyReq.Size ); ASSERT_TRUE( bRET ); }
  • 24. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() class CPacketHandler { CConnectionManager CConnectionManager* m_pConnectionManager; +SendPacket() CPacketParser* m_pPacketParser; public: CPacketHandler() { m_pConnectionManager = new CConnectionManager(); m_pPacketParser = new CPacketParser(); ... } BOOL ProcessPacket ( const char * pBuf, size_t packetSize ) { MSG_BASE* pMsg = m_pPacketParser->ParsePacket( pBuf, packetSize ); ... if( pMsg->GetProtocol() == MSG_PROTOCOL_ITEM_BUY_REQ ) { m_pConnectionManager->SendPacket( new PACKET_ITEM_BUY_ANS() ); return TRUE; } ... return FALSE; } ... };
  • 25. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() class CPacketParser { public: ...... MSG_BASE* ParsePacket( const char* pBuf, size_t packetSize ) { MSG_BASE* pMsg = (MSG_BASE*)pBuf; ... // 검증 코드 및 데이터 채우기 return pMsg; } ...... };
  • 26. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() class CConnectionManager { CSendQueue* m_pSendQueue; public: ...... void SendPacket( MSG_BASE* pMsg ) { // 실제로 메시지를 보내는 코드 m_pSendQueue->PostMessage(pMsg); } ...... };
  • 27. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() CSendQueue +PostMessage() class CConnectionManager { CSendQueue* m_pSendQueue; public: ...... void SendPacket( MSG_BASE* pMsg ) { // 실제로 메시지를 보내는 코드 m_pSendQueue->PostMessage(pMsg); } ...... };
  • 28. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() CSendQueue +PostMessage()
  • 29. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() CSendQueue +PostMessage() CTcpSocket CThreadBase
  • 30. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() CSendQueue +PostMessage() CTcpSocket CThreadBase CPacketArchieve
  • 31. 스텁(Stub)  외부 의존물을 대신하기 위해 간접 계층 추가
  • 32. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CPacketHandler CPacketParser CConnectionManager +ProcessPacket() +ParsePacket() +SendPacket() CConnectionManager CSendQueue +SendPacket() +PostMessage() CTcpSocket CThreadBase CPacketArchieve
  • 33. 간접 계층  외부 의존물에 접근하기 위해 인터페이스 추가
  • 34. CPacketHandler CPacketParser CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() +ProcessPacket() +ParsePacket() CConnectionManager IConnectionManager +SendPacket() +SendPacket() CSendQueue FakeConnectionManager CConnectionManager +PostMessage() +SendPacket() +SendPacket() CSendQueue CTcpSocket CThreadBase CPacketArchieve +PostMessage()
  • 35. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() IConnectionManager +SendPacket() class CPacketHandler { IConnectionManager* m_pConnectionManager; FakeConnectionManager CPacketParser* m_pPacketParser; +SendPacket() public: CPacketHandler() { m_pConnectionManager = new CConnectionManager(); m_pPacketParser = new CPacketParser(); ... } BOOL ProcessPacket ( const char * pBuf, size_t packetSize ) { MSG_BASE* pMsg = m_pPacketParser->ParsePacket( pBuf, packetSize ); ... if( pMsg->GetProtocol() == MSG_PROTOCOL_ITEM_BUY_REQ ) { m_pConnectionManager->SendPacket( new PACKET_ITEM_BUY_ANS() ); return TRUE; } ... return FALSE; } ... };
  • 36. 스텁 주입하기  생성자  get, set 프로퍼티  매개변수  추상 팩토리
  • 37. 생성자 주입  장점 class CPacketHandler {  테스트 코드의 가독성 향상 IConnectionManager* m_pConnectionManager;  해당 매개 변수가 필수임을 알림 ...  단점 public:  상호 참조의 경우 생성 문제 CPacketHandler(IConnectionManager* pManager) {  매개 변수의 개수가 늘어날 수록 m_pConnectionManager = pManager; 가독성, 관리용이성 악화 ... } ... }; ... FakeConnectionManager FakeManager; CPacketHandler Handler( &FakeManager ); ...
  • 38. get, set 프로퍼티  작성의 용이함 class CPacketHandler {  필수적이지 않은 매개변수 IConnectionManager* m_pConnectionManager; ... public: SetConnectionManager (IConnectionManager* pManager) { m_pConnectionManager = pManager; } ... }; ... FakeConnectionManager FakeManager; CPacketHandler Handler; Handler.SetConnectionManager( &FakeManager ); ...
  • 39. 그 외 대표적 방법들  매개변수 젂달  추상 팩토리  함수를 호출할 때 함께  실제 객체와 스텁을 생 의존물을 넣어준다. 성하는 팩토리를 생성 하는 추상 팩토리를 생 성
  • 40. 캡슐화 문제  테스트 용이성을 높이기 위한 방법  Public  상속  Friend  조건부 컴파일(#ifdef)  상용 프레임워크
  • 41. 픽스쳐 class fixture_name; TEST_F( fixture_name, case_name ) { ... ...// Some Tests ASSERT_XXX(); ... }
  • 42. class fixture_name : public testing::Test { void SetUp(); void TearDown(); }; TEST_F( fixture_name, case_name ) { ... ...// Some Tests ASSERT_XXX(); ... }
  • 43. 목 객체(Mock Object)  단위 테스트의 통과, 실패를 판단하는 가 짜 객체  하나의 테스트에 하나의 목 객체 사용
  • 44. 스텁 vs 목  스텁: 객체의 대체제. 테스트가 가능하도 록 의존물을 없애는 것  목: 테스트의 통과, 실패를 검증
  • 45. 목 객체의 사용  CPacketHandler가 IConnectionManager:: SendPacket() 메서드를 호출하는지 확인  ConnectionManager의 인터페이스를 추 출, 스텁으로 교체.  스텁의 SendPacket 메서드를 오버라이드 하여 체크 CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() IConnectionManager +SendPacket()
  • 46. class FakeConnectionManager: public IConnectionManager{ public: int m_nCalled; MSG_BASE* m_pPacket; ... virtual void SendPacket( MSG_BASE* pPacket ){ ++m_nCalled; m_pPacket = pPacket; } }; TEST( PacketHandler, ProcessPacketWithItemBuyReq ){ ... Handler.ProcessPacket( &ItemBuyReq ); ASSERT_EQ( 1, FakeManager.m_nCalled ); ASSERT_EQ( MSG_PROTOCOL_ITEM_BUY_ANS, FakeManager.m_pPacket->GetProtocol() ); }
  • 47. 격리 프레임워크(Isolation Frame work)  목과 스텁 객체를 쉽게 생성할 수 있게 해 주는 API의 모음  테스트의 반복 작성시 도움을 받을 수 있 다.  예상값(기대값)의 측정  함수 호출  호출 횟수  인자
  • 48. Google Mock 사용법  목 오브젝트 생성 class Impl { public: virtual void SetPos(float x, float y); virtual float GetX() const; } class MockImpl: public Impl { public: MOCK_METHOD2( SetPos, void( float x, float y ); MOCK_CONST_METHOD0( GetX, float() ); };
  • 49. 함수 호출 측정 TEST( MockExample, Expect_call ) { MockImpl impl; EXPECT_CALL( impl, GetPos() ); EXPECT_CALL( impl, SetPos( _, _ ) ); … // calls function … }
  • 50. 함수 호출 횟수 측정 TEST( MockExample, Expect_call ) { MockImpl impl; EXPECT_CALL( impl, GetPos() ) .Times( 3 ); … // calls function … }
  • 51. 리턴값 지정 TEST( MockExample, Expect_call ) { MockImpl impl; // ON_CALL( impl, GetPos() ) EXPECT_CALL( impl, GetPos() ) .WillByDefault(Return(50.0f)); … // calls function … }
  • 52. Class FakeConnectionManager: public IConnectionManager{ public: int m_nCalled; MSG_BASE* m_pPacket; ... virtual void SendPacket( MSG_BASE* pPacket ){ ++m_nCalled; m_pPacket = pPacket; } }; TEST( PacketHandler, ProcessPacketWithItemBuyReq ){ FakeConnectionManager FakeManager; PacketHandler Handler(&FakeManager); ... Handler.ProcessPacket( &ItemBuyReq ); ASSERT_EQ( 1, FakeManager.m_nCalled ); ASSERT_EQ( MSG_PROTOCOL_ITEM_BUY_ANS, FakeManager.m_pPacket->GetProtocol() ); }
  • 53. class FakeConnectionManager: public IConnectionManager{ public: MOCK_METHOD1( SendPakcet, void(MSG_BASE* pPacket); }; int IsPacket(MSG_BASE *p){ return (p != NULL) && (p->GetProtocol() == MSG_PROTOCOL_ITEM_BUY_ANS); } TEST( PacketHandler, ProcessPacketWithItemBuyReq ){ FakeConnectionManager FakeManager; EXPECT_CALL( FakeManager, SendPacket( Truly(IsPacket) ) ) .Times( AtLeast(1) ); PacketHandler Handler( &FakeManager ); ... Handler.ProcessPacket( &ItemBuyReq ); }
  • 54. 팁  목 객체는 테스트 당 1개  목 객체, 테스트 객체를 제외한 모든 의존 물은 Stub으로 대체  ASSERT는 가급적 테스트 당 1개  목 객체를 재사용 : 목 객체 내부에 ASSERT 삽입 금지  모든 테스트는 격리해서 실행
  • 55. 감사합니다  whoo24@gmail.com  Twitter: whoo24  Blog : http://blog.wychoe.net