C++ 프로젝트에 단위 테스트 도입하기최우영위메이드 엔터테인먼트
강연자 소개최우영(주)위메이드 엔터테인먼트신규 게임 개발팀 -서버 파트(주)라온 엔터테인먼트테일즈런너신규 게임 개발
Test?
통합 테스트 VS 단위 테스트통합 테스트둘 이상의 모듈을 하나의 그룹으로 테스트 하는 것소프트웨어에 기대되는 결과를 확인 하는 것.많은 단위들을 실행단위 테스트단일 단위를 분리하여 실행하는 테스트
단위 테스트란다른 코드를 호출한 후 몇 가지 가정이 성립하는지 검사하는 코드여기서 '단위(unit)'란 메서드나 함수를 의미X + Y = Z ?
좋은 단위 테스트자동화반복 실행쉬운 구현쉬운 실행빠른 속도로직 없는 테스트
테스트 프레임워크CppUnit, UnitTest++, TUT, …사용하기 쉽고 신뢰할 수 있는 GoogleTesthttp://code.google.com/p/googletest/구글의 제품에 사용 중Chromium, Protocol Buffers, … 등등
Gtest 사용하기gtest라이브러리 다운로드gtest.h파일 include라이브러리 빌드 후 lib 파일 링크#include <gtest\gtest.h>#pragma comment(lib, “gtest.lib”)int main(intargc, _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 <gtest\gtest.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 <gtest\gtest.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 <gtest\gtest.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 => successtest => failure빌드 성공, 테스트 실패
[==========]Running 1 test from 1 test case.[----------]Global test environment set-up.[----------] 1 test from Swap_Test[ RUN      ]Swap_Test.Swap_Truec:\....\Swap_Test.cpp(124): error: Value of: x  Actual: 30Expected: 15x 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_Truec:\....\Swap_Test.cpp(124): error: Value of: x  Actual: 30Expected: 15x 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_Truec:\....\Swap_Test.cpp(124): error: Value of: x  Actual: 30Expected: 15x 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…ASSERT_EQ( 15, x ) << "x must be 15“;…
#include <gtest\gtest.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 => successtest => 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.
의존성 제거클래스 간 복합적인 관계 의존성 존재테스트 저해 설계 : 파일, 스레드, 통신등 외부 의존물을 사용통합테스트 => 단위 테스트
패킷 변환테스트 대상통신 담당
...TEST( CPacketHandler, ProcessPacketSuccessWithItemBuyReq){CPacketHandlerPacketHandler;	MSG_ITEM_BUY_REQ ItemBuyReq;boolbRet = PacketHandler.ProcessPacket( &ItemBuyReq, ItemBuyReq.Size );ASSERT_TRUE( bRET );}
class CPacketHandler{CConnectionManager*	m_pConnectionManager;CPacketParser*		m_pPacketParser;public:CPacketHandler() 	{m_pConnectionManager= new CConnectionManager();m_pPacketParser		= new CPacketParser();	...}BOOL ProcessPacket ( const char * pBuf, size_tpacketSize ) 	{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;}...};
class CPacketParser{public:	......MSG_BASE* ParsePacket( const char* pBuf, size_tpacketSize ) {	MSG_BASE* pMsg = (MSG_BASE*)pBuf;... // 검증 코드 및 데이터 채우기	return pMsg;}	......};
class CConnectionManager{CSendQueue* m_pSendQueue;public:	......void SendPacket( MSG_BASE* pMsg ) 	{// 실제로 메시지를 보내는 코드m_pSendQueue->PostMessage(pMsg);}	......};
class CConnectionManager{CSendQueue* m_pSendQueue;public:	......void SendPacket( MSG_BASE* pMsg ) 	{// 실제로 메시지를 보내는 코드m_pSendQueue->PostMessage(pMsg);}	......};
스텁(Stub)외부 의존물을 대신하기 위해 간접 계층 추가
간접 계층외부 의존물에 접근하기 위해 인터페이스 추가
class CPacketHandler{IConnectionManager*	m_pConnectionManager;CPacketParser*		m_pPacketParser;public:CPacketHandler() 	{m_pConnectionManager= new CConnectionManager();m_pPacketParser		= new CPacketParser();	...}BOOL ProcessPacket ( const char * pBuf, size_tpacketSize ) 	{		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;		...}...};...FakeConnectionManagerFakeManager;CPacketHandlerHandler( &FakeManager );...
class CPacketHandler{IConnectionManager* m_pConnectionManager;	...public:SetConnectionManager				(IConnectionManager* pManager) 	{m_pConnectionManager	= pManager;}	...};...FakeConnectionManagerFakeManager;CPacketHandler Handler;Handler.SetConnectionManager( &FakeManager );...get, set 프로퍼티작성의 용이함필수적이지 않은 매개변수
그 외 대표적 방법들매개변수 전달함수를 호출할 때 함께 의존물을 넣어준다.추상 팩토리실제 객체와 스텁을 생성하는 팩토리를 생성하는 추상 팩토리를 생성
캡슐화 문제테스트 용이성을 높이기 위한 방법Public상속Friend조건부 컴파일(#ifdef)상용 프레임워크
픽스쳐class fixture_name;TEST_F( fixture_name, case_name){......// Some TestsASSERT_XXX();...}
class fixture_name : public testing::Test{	void	SetUp();void	TearDown();};TEST_F( fixture_name, case_name){......// Some TestsASSERT_XXX();...}
목 객체(Mock Object)단위 테스트의 통과, 실패를 판단하는 가짜 객체하나의 테스트에 하나의 목 객체 사용
스텁 vs 목스텁: 객체의 대체제. 테스트가 가능하도록 의존물을 없애는 것목: 테스트의 통과, 실패를 검증
목 객체의 사용CPacketHandler가 IConnectionManager::SendPacket() 메서드를 호출하는지 확인ConnectionManager의 인터페이스를 추출, 스텁으로 교체.스텁의SendPacket메서드를오버라이드 하여 체크
class FakeConnectionManager: public IConnectionManager{public:intm_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 Framework)목과 스텁 객체를 쉽게 생성할 수 있게 해주는 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 ){MockImplimpl;EXPECT_CALL( impl, GetPos() );	EXPECT_CALL( impl, SetPos( _, _ ) );…// calls function…}
 함수 호출 횟수 측정TEST( MockExample, Expect_call ){MockImplimpl;EXPECT_CALL( impl, GetPos() )	.Times( 3 );…// calls function…}
리턴값 지정TEST( MockExample, Expect_call ){MockImplimpl;	//  ON_CALL( impl, GetPos() )EXPECT_CALL( impl, GetPos() )	.WillByDefault(Return(50.0f));…// calls function…}
Class FakeConnectionManager: public IConnectionManager{public:intm_nCalled;MSG_BASE* m_pPacket;	...virtual void SendPacket( MSG_BASE* pPacket ){	++m_nCalled;m_pPacket = pPacket;}};TEST( PacketHandler, ProcessPacketWithItemBuyReq ){FakeConnectionManagerFakeManager;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);};intIsPacket(MSG_BASE *p){return (p != NULL) && (p->GetProtocol() == MSG_PROTOCOL_ITEM_BUY_ANS);}TEST( PacketHandler, ProcessPacketWithItemBuyReq ){FakeConnectionManagerFakeManager;EXPECT_CALL( FakeManager, SendPacket( Truly(IsPacket) ) )	.Times( AtLeast(1) );PacketHandler	Handler( &FakeManager );...Handler.ProcessPacket( &ItemBuyReq);}
팁목 객체는 테스트 당 1개목 객체, 테스트 객체를 제외한 모든 의존물은Stub으로 대체ASSERT는 가급적 테스트 당 1개목 객체를 재사용 : 목 객체 내부에 ASSERT 삽입 금지모든 테스트는 격리해서 실행
감사합니다whoo24@gmail.comTwitter: whoo24Blog : http://blog.wychoe.net

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

  • 1.
    C++ 프로젝트에 단위테스트 도입하기최우영위메이드 엔터테인먼트
  • 2.
    강연자 소개최우영(주)위메이드 엔터테인먼트신규게임 개발팀 -서버 파트(주)라온 엔터테인먼트테일즈런너신규 게임 개발
  • 3.
  • 4.
    통합 테스트 VS단위 테스트통합 테스트둘 이상의 모듈을 하나의 그룹으로 테스트 하는 것소프트웨어에 기대되는 결과를 확인 하는 것.많은 단위들을 실행단위 테스트단일 단위를 분리하여 실행하는 테스트
  • 5.
    단위 테스트란다른 코드를호출한 후 몇 가지 가정이 성립하는지 검사하는 코드여기서 '단위(unit)'란 메서드나 함수를 의미X + Y = Z ?
  • 6.
    좋은 단위 테스트자동화반복실행쉬운 구현쉬운 실행빠른 속도로직 없는 테스트
  • 7.
    테스트 프레임워크CppUnit, UnitTest++,TUT, …사용하기 쉽고 신뢰할 수 있는 GoogleTesthttp://code.google.com/p/googletest/구글의 제품에 사용 중Chromium, Protocol Buffers, … 등등
  • 8.
    Gtest 사용하기gtest라이브러리 다운로드gtest.h파일include라이브러리 빌드 후 lib 파일 링크#include <gtest\gtest.h>#pragma comment(lib, “gtest.lib”)int main(intargc, _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 <gtest\gtest.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 <gtest\gtest.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 <gtest\gtest.h> voidswap(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 => successtest => failure빌드 성공, 테스트 실패
  • 16.
    [==========]Running 1 testfrom 1 test case.[----------]Global test environment set-up.[----------] 1 test from Swap_Test[ RUN ]Swap_Test.Swap_Truec:\....\Swap_Test.cpp(124): error: Value of: x Actual: 30Expected: 15x 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 testfrom 1 test case.[----------]Global test environment set-up.[----------] 1 test from Swap_Test[ RUN ]Swap_Test.Swap_Truec:\....\Swap_Test.cpp(124): error: Value of: x Actual: 30Expected: 15x 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 1test from 1 test case.[----------] Global test environment set-up.[----------] 1 test from Swap_Test[ RUN ] Swap_Test.Swap_Truec:\....\Swap_Test.cpp(124): error: Value of: x Actual: 30Expected: 15x 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…ASSERT_EQ( 15, x ) << "x must be 15“;…
  • 19.
    #include <gtest\gtest.h> voidswap(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 => successtest => pass테스트 통과
  • 20.
    [==========] Running 1test 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.
  • 23.
    ...TEST( CPacketHandler, ProcessPacketSuccessWithItemBuyReq){CPacketHandlerPacketHandler; MSG_ITEM_BUY_REQItemBuyReq;boolbRet = PacketHandler.ProcessPacket( &ItemBuyReq, ItemBuyReq.Size );ASSERT_TRUE( bRET );}
  • 24.
    class CPacketHandler{CConnectionManager* m_pConnectionManager;CPacketParser* m_pPacketParser;public:CPacketHandler() {m_pConnectionManager=new CConnectionManager();m_pPacketParser = new CPacketParser(); ...}BOOL ProcessPacket ( const char * pBuf, size_tpacketSize ) {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.
    class CPacketParser{public: ......MSG_BASE* ParsePacket(const char* pBuf, size_tpacketSize ) { MSG_BASE* pMsg = (MSG_BASE*)pBuf;... // 검증 코드 및 데이터 채우기 return pMsg;} ......};
  • 26.
    class CConnectionManager{CSendQueue* m_pSendQueue;public: ......voidSendPacket( MSG_BASE* pMsg ) {// 실제로 메시지를 보내는 코드m_pSendQueue->PostMessage(pMsg);} ......};
  • 27.
    class CConnectionManager{CSendQueue* m_pSendQueue;public: ......voidSendPacket( MSG_BASE* pMsg ) {// 실제로 메시지를 보내는 코드m_pSendQueue->PostMessage(pMsg);} ......};
  • 31.
  • 33.
    간접 계층외부 의존물에접근하기 위해 인터페이스 추가
  • 35.
    class CPacketHandler{IConnectionManager* m_pConnectionManager;CPacketParser* m_pPacketParser;public:CPacketHandler() {m_pConnectionManager=new CConnectionManager();m_pPacketParser = new CPacketParser(); ...}BOOL ProcessPacket ( const char * pBuf, size_tpacketSize ) { 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; ...}...};...FakeConnectionManagerFakeManager;CPacketHandlerHandler( &FakeManager );...
  • 38.
    class CPacketHandler{IConnectionManager* m_pConnectionManager; ...public:SetConnectionManager (IConnectionManager*pManager) {m_pConnectionManager = pManager;} ...};...FakeConnectionManagerFakeManager;CPacketHandler Handler;Handler.SetConnectionManager( &FakeManager );...get, set 프로퍼티작성의 용이함필수적이지 않은 매개변수
  • 39.
    그 외 대표적방법들매개변수 전달함수를 호출할 때 함께 의존물을 넣어준다.추상 팩토리실제 객체와 스텁을 생성하는 팩토리를 생성하는 추상 팩토리를 생성
  • 40.
    캡슐화 문제테스트 용이성을높이기 위한 방법Public상속Friend조건부 컴파일(#ifdef)상용 프레임워크
  • 41.
    픽스쳐class fixture_name;TEST_F( fixture_name,case_name){......// Some TestsASSERT_XXX();...}
  • 42.
    class fixture_name :public testing::Test{ void SetUp();void TearDown();};TEST_F( fixture_name, case_name){......// Some TestsASSERT_XXX();...}
  • 43.
    목 객체(Mock Object)단위테스트의 통과, 실패를 판단하는 가짜 객체하나의 테스트에 하나의 목 객체 사용
  • 44.
    스텁 vs 목스텁:객체의 대체제. 테스트가 가능하도록 의존물을 없애는 것목: 테스트의 통과, 실패를 검증
  • 45.
    목 객체의 사용CPacketHandler가IConnectionManager::SendPacket() 메서드를 호출하는지 확인ConnectionManager의 인터페이스를 추출, 스텁으로 교체.스텁의SendPacket메서드를오버라이드 하여 체크
  • 46.
    class FakeConnectionManager: publicIConnectionManager{public:intm_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 Framework)목과스텁 객체를 쉽게 생성할 수 있게 해주는 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 ){MockImplimpl;EXPECT_CALL( impl, GetPos() ); EXPECT_CALL( impl, SetPos( _, _ ) );…// calls function…}
  • 50.
    함수 호출횟수 측정TEST( MockExample, Expect_call ){MockImplimpl;EXPECT_CALL( impl, GetPos() ) .Times( 3 );…// calls function…}
  • 51.
    리턴값 지정TEST( MockExample,Expect_call ){MockImplimpl; // ON_CALL( impl, GetPos() )EXPECT_CALL( impl, GetPos() ) .WillByDefault(Return(50.0f));…// calls function…}
  • 52.
    Class FakeConnectionManager: publicIConnectionManager{public:intm_nCalled;MSG_BASE* m_pPacket; ...virtual void SendPacket( MSG_BASE* pPacket ){ ++m_nCalled;m_pPacket = pPacket;}};TEST( PacketHandler, ProcessPacketWithItemBuyReq ){FakeConnectionManagerFakeManager;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: publicIConnectionManager{public:MOCK_METHOD1( SendPakcet, void(MSG_BASE* pPacket);};intIsPacket(MSG_BASE *p){return (p != NULL) && (p->GetProtocol() == MSG_PROTOCOL_ITEM_BUY_ANS);}TEST( PacketHandler, ProcessPacketWithItemBuyReq ){FakeConnectionManagerFakeManager;EXPECT_CALL( FakeManager, SendPacket( Truly(IsPacket) ) ) .Times( AtLeast(1) );PacketHandler Handler( &FakeManager );...Handler.ProcessPacket( &ItemBuyReq);}
  • 54.
    팁목 객체는 테스트당 1개목 객체, 테스트 객체를 제외한 모든 의존물은Stub으로 대체ASSERT는 가급적 테스트 당 1개목 객체를 재사용 : 목 객체 내부에 ASSERT 삽입 금지모든 테스트는 격리해서 실행
  • 55.