SlideShare una empresa de Scribd logo
1 de 55
C++ 프로젝트에 단위 테스트 도입하기 최우영 위메이드 엔터테인먼트
강연자 소개 최우영 (주)위메이드 엔터테인먼트 신규 게임 개발팀 -서버 파트 (주)라온 엔터테인먼트 테일즈런너 신규 게임 개발
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 <gtesttest.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 <gtesttest.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 <gtesttest.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 <gtesttest.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_True c:...wap_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:...wap_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:...wap_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 … ASSERT_EQ( 15, x ) << "x must be 15“; …
#include <gtesttest.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 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메서드를오버라이드 하여 체크
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.com Twitter: whoo24 Blog : http://blog.wychoe.net

Más contenido relacionado

La actualidad más candente

Domain Modeling in a Functional World
Domain Modeling in a Functional WorldDomain Modeling in a Functional World
Domain Modeling in a Functional WorldDebasish Ghosh
 
Unit Testing with Python
Unit Testing with PythonUnit Testing with Python
Unit Testing with PythonMicroPyramid .
 
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기Heejong Ahn
 
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Mario Fusco
 
関数型プログラミング入門 with OCaml
関数型プログラミング入門 with OCaml関数型プログラミング入門 with OCaml
関数型プログラミング入門 with OCamlHaruka Oikawa
 
クロージャデザインパターン
クロージャデザインパターンクロージャデザインパターン
クロージャデザインパターンMoriharu Ohzu
 
Swift で JavaScript 始めませんか? #iOSDC
Swift で JavaScript 始めませんか? #iOSDCSwift で JavaScript 始めませんか? #iOSDC
Swift で JavaScript 始めませんか? #iOSDCTomohiro Kumagai
 
If You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are WrongIf You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are WrongMario Fusco
 
20190625 OpenACC 講習会 第3部
20190625 OpenACC 講習会 第3部20190625 OpenACC 講習会 第3部
20190625 OpenACC 講習会 第3部NVIDIA Japan
 
エキスパートGo
エキスパートGoエキスパートGo
エキスパートGoTakuya Ueda
 
日本語テストメソッドについて
日本語テストメソッドについて日本語テストメソッドについて
日本語テストメソッドについてkumake
 
Google mock for dummies
Google mock for dummiesGoogle mock for dummies
Google mock for dummiesLuis Goldster
 
XunitとMoq 公開用
XunitとMoq 公開用XunitとMoq 公開用
XunitとMoq 公開用ESM SEC
 
F#によるFunctional Programming入門
F#によるFunctional Programming入門F#によるFunctional Programming入門
F#によるFunctional Programming入門bleis tift
 

La actualidad más candente (20)

GMock framework
GMock frameworkGMock framework
GMock framework
 
Domain Modeling in a Functional World
Domain Modeling in a Functional WorldDomain Modeling in a Functional World
Domain Modeling in a Functional World
 
Unit Testing with Python
Unit Testing with PythonUnit Testing with Python
Unit Testing with Python
 
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기
 
Pytest KT.pptx
Pytest KT.pptxPytest KT.pptx
Pytest KT.pptx
 
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...
 
関数型プログラミング入門 with OCaml
関数型プログラミング入門 with OCaml関数型プログラミング入門 with OCaml
関数型プログラミング入門 with OCaml
 
API Design - 3rd Edition
API Design - 3rd EditionAPI Design - 3rd Edition
API Design - 3rd Edition
 
クロージャデザインパターン
クロージャデザインパターンクロージャデザインパターン
クロージャデザインパターン
 
JUNit Presentation
JUNit PresentationJUNit Presentation
JUNit Presentation
 
Swift で JavaScript 始めませんか? #iOSDC
Swift で JavaScript 始めませんか? #iOSDCSwift で JavaScript 始めませんか? #iOSDC
Swift で JavaScript 始めませんか? #iOSDC
 
If You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are WrongIf You Think You Can Stay Away from Functional Programming, You Are Wrong
If You Think You Can Stay Away from Functional Programming, You Are Wrong
 
Phpunit testing
Phpunit testingPhpunit testing
Phpunit testing
 
20190625 OpenACC 講習会 第3部
20190625 OpenACC 講習会 第3部20190625 OpenACC 講習会 第3部
20190625 OpenACC 講習会 第3部
 
Unit testing with JUnit
Unit testing with JUnitUnit testing with JUnit
Unit testing with JUnit
 
エキスパートGo
エキスパートGoエキスパートGo
エキスパートGo
 
日本語テストメソッドについて
日本語テストメソッドについて日本語テストメソッドについて
日本語テストメソッドについて
 
Google mock for dummies
Google mock for dummiesGoogle mock for dummies
Google mock for dummies
 
XunitとMoq 公開用
XunitとMoq 公開用XunitとMoq 公開用
XunitとMoq 公開用
 
F#によるFunctional Programming入門
F#によるFunctional Programming入門F#によるFunctional Programming入門
F#によるFunctional Programming入門
 

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

테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)Suwon Chae
 
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 TestOkjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 Testbeom kyun choi
 
데이터베이스패턴
데이터베이스패턴데이터베이스패턴
데이터베이스패턴Suan Lee
 
10장 결과 검증
10장 결과 검증10장 결과 검증
10장 결과 검증dagri82
 
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
 
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
 
Effective unit testing ch3. 테스트더블
Effective unit testing   ch3. 테스트더블Effective unit testing   ch3. 테스트더블
Effective unit testing ch3. 테스트더블YongEun Choi
 
Sonarqube 20160509
Sonarqube 20160509Sonarqube 20160509
Sonarqube 20160509영석 조
 
TDD - Test Driven Development
TDD - Test Driven DevelopmentTDD - Test Driven Development
TDD - Test Driven DevelopmentChangHyeon Bae
 
자바 테스트 자동화
자바 테스트 자동화자바 테스트 자동화
자바 테스트 자동화Sungchul Park
 
테스터가 말하는 테스트코드 작성 팁과 사례
테스터가 말하는 테스트코드 작성 팁과 사례테스터가 말하는 테스트코드 작성 팁과 사례
테스터가 말하는 테스트코드 작성 팁과 사례SangIn Choung
 
xUnitTestPattern/chapter7
xUnitTestPattern/chapter7xUnitTestPattern/chapter7
xUnitTestPattern/chapter7종빈 오
 
Backend Master | 3.2.1 Test - JUnit
Backend Master | 3.2.1 Test - JUnitBackend Master | 3.2.1 Test - JUnit
Backend Master | 3.2.1 Test - JUnitKyunghun Jeon
 
Multi mechanize
Multi mechanizeMulti mechanize
Multi mechanizeSungMin OH
 
TDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDTDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDSuwon Chae
 
불어오는 변화의 바람, 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 명신 김
 

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

테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
 
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 TestOkjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
 
데이터베이스패턴
데이터베이스패턴데이터베이스패턴
데이터베이스패턴
 
10장 결과 검증
10장 결과 검증10장 결과 검증
10장 결과 검증
 
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
 
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...
 
Effective unit testing ch3. 테스트더블
Effective unit testing   ch3. 테스트더블Effective unit testing   ch3. 테스트더블
Effective unit testing ch3. 테스트더블
 
Sonarqube 20160509
Sonarqube 20160509Sonarqube 20160509
Sonarqube 20160509
 
TDD - Test Driven Development
TDD - Test Driven DevelopmentTDD - Test Driven Development
TDD - Test Driven Development
 
자바 테스트 자동화
자바 테스트 자동화자바 테스트 자동화
자바 테스트 자동화
 
Cygnus unit test
Cygnus unit testCygnus unit test
Cygnus unit test
 
TEST?
TEST?TEST?
TEST?
 
테스터가 말하는 테스트코드 작성 팁과 사례
테스터가 말하는 테스트코드 작성 팁과 사례테스터가 말하는 테스트코드 작성 팁과 사례
테스터가 말하는 테스트코드 작성 팁과 사례
 
xUnitTestPattern/chapter7
xUnitTestPattern/chapter7xUnitTestPattern/chapter7
xUnitTestPattern/chapter7
 
Backend Master | 3.2.1 Test - JUnit
Backend Master | 3.2.1 Test - JUnitBackend Master | 3.2.1 Test - JUnit
Backend Master | 3.2.1 Test - JUnit
 
Android unit testing
Android unit testingAndroid unit testing
Android unit testing
 
Unit Test With J Unit
Unit Test With J UnitUnit Test With J Unit
Unit Test With J Unit
 
Multi mechanize
Multi mechanizeMulti mechanize
Multi mechanize
 
TDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDTDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDD
 
불어오는 변화의 바람, 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
 

Más de Heo Seungwook

리팩토링 10장 p357_p369
리팩토링 10장 p357_p369리팩토링 10장 p357_p369
리팩토링 10장 p357_p369Heo Seungwook
 
리펙토링 11장 p389_p400
리펙토링 11장 p389_p400리펙토링 11장 p389_p400
리펙토링 11장 p389_p400Heo Seungwook
 
리펙토링 10장 p316_p324
리펙토링 10장 p316_p324리펙토링 10장 p316_p324
리펙토링 10장 p316_p324Heo Seungwook
 
리펙토링 8장 p236_p243
리펙토링 8장 p236_p243리펙토링 8장 p236_p243
리펙토링 8장 p236_p243Heo Seungwook
 
Ipv6 소켓프로그래밍
Ipv6 소켓프로그래밍Ipv6 소켓프로그래밍
Ipv6 소켓프로그래밍Heo Seungwook
 
해외취업이야기
해외취업이야기해외취업이야기
해외취업이야기Heo Seungwook
 
MS SharePoint를 활용한 개발환경 구축
MS SharePoint를 활용한 개발환경 구축MS SharePoint를 활용한 개발환경 구축
MS SharePoint를 활용한 개발환경 구축Heo Seungwook
 
Cruise control net_and_terminal_with_gamedev
Cruise control net_and_terminal_with_gamedevCruise control net_and_terminal_with_gamedev
Cruise control net_and_terminal_with_gamedevHeo Seungwook
 
리펙토링 6장 p147_p158
리펙토링 6장 p147_p158리펙토링 6장 p147_p158
리펙토링 6장 p147_p158Heo Seungwook
 
리펙토링 4장 테스트만들기
리펙토링 4장 테스트만들기리펙토링 4장 테스트만들기
리펙토링 4장 테스트만들기Heo Seungwook
 
2010 연말행사 온라인스터디
2010 연말행사 온라인스터디2010 연말행사 온라인스터디
2010 연말행사 온라인스터디Heo Seungwook
 
Client dispatcher server_pattern
Client dispatcher server_patternClient dispatcher server_pattern
Client dispatcher server_patternHeo Seungwook
 
Master slave pattern
Master slave patternMaster slave pattern
Master slave patternHeo Seungwook
 
프로그램은 왜 실패하는가
프로그램은 왜 실패하는가프로그램은 왜 실패하는가
프로그램은 왜 실패하는가Heo Seungwook
 

Más de Heo Seungwook (16)

리팩토링 10장 p357_p369
리팩토링 10장 p357_p369리팩토링 10장 p357_p369
리팩토링 10장 p357_p369
 
리펙토링 11장 p389_p400
리펙토링 11장 p389_p400리펙토링 11장 p389_p400
리펙토링 11장 p389_p400
 
리펙토링 10장 p316_p324
리펙토링 10장 p316_p324리펙토링 10장 p316_p324
리펙토링 10장 p316_p324
 
리펙토링 8장 p236_p243
리펙토링 8장 p236_p243리펙토링 8장 p236_p243
리펙토링 8장 p236_p243
 
Ipv6 소켓프로그래밍
Ipv6 소켓프로그래밍Ipv6 소켓프로그래밍
Ipv6 소켓프로그래밍
 
해외취업이야기
해외취업이야기해외취업이야기
해외취업이야기
 
MS SharePoint를 활용한 개발환경 구축
MS SharePoint를 활용한 개발환경 구축MS SharePoint를 활용한 개발환경 구축
MS SharePoint를 활용한 개발환경 구축
 
Cruise control net_and_terminal_with_gamedev
Cruise control net_and_terminal_with_gamedevCruise control net_and_terminal_with_gamedev
Cruise control net_and_terminal_with_gamedev
 
리펙토링 6장 p147_p158
리펙토링 6장 p147_p158리펙토링 6장 p147_p158
리펙토링 6장 p147_p158
 
리펙토링 4장 테스트만들기
리펙토링 4장 테스트만들기리펙토링 4장 테스트만들기
리펙토링 4장 테스트만들기
 
2010 연말행사 온라인스터디
2010 연말행사 온라인스터디2010 연말행사 온라인스터디
2010 연말행사 온라인스터디
 
Client dispatcher server_pattern
Client dispatcher server_patternClient dispatcher server_pattern
Client dispatcher server_pattern
 
Master slave pattern
Master slave patternMaster slave pattern
Master slave pattern
 
Pac pattern
Pac patternPac pattern
Pac pattern
 
Mvc pattern
Mvc patternMvc pattern
Mvc pattern
 
프로그램은 왜 실패하는가
프로그램은 왜 실패하는가프로그램은 왜 실패하는가
프로그램은 왜 실패하는가
 

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

  • 1. 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 <gtesttest.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 <gtesttest.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 <gtesttest.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 <gtesttest.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 빌드 성공, 테스트 실패
  • 16. [==========]Running 1 test from 1 test case. [----------]Global test environment set-up. [----------] 1 test from Swap_Test [ RUN ]Swap_Test.Swap_True c:...wap_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:...wap_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:...wap_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 … ASSERT_EQ( 15, x ) << "x must be 15“; …
  • 19. #include <gtesttest.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 테스트 통과
  • 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. 패킷 변환 테스트 대상 통신 담당
  • 23. ... TEST( CPacketHandler, ProcessPacketSuccessWithItemBuyReq){ CPacketHandlerPacketHandler; MSG_ITEM_BUY_REQ ItemBuyReq; 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: ...... void SendPacket( MSG_BASE* pMsg ) { // 실제로 메시지를 보내는 코드 m_pSendQueue->PostMessage(pMsg); } ...... };
  • 27. class CConnectionManager { CSendQueue* m_pSendQueue; public: ...... void SendPacket( MSG_BASE* pMsg ) { // 실제로 메시지를 보내는 코드 m_pSendQueue->PostMessage(pMsg); } ...... };
  • 28.
  • 29.
  • 30.
  • 31. 스텁(Stub) 외부 의존물을 대신하기 위해 간접 계층 추가
  • 32.
  • 33. 간접 계층 외부 의존물에 접근하기 위해 인터페이스 추가
  • 34.
  • 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 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메서드를오버라이드 하여 체크
  • 46. 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() ); }
  • 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: 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() ); }
  • 53. 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); }
  • 54. 팁 목 객체는 테스트 당 1개 목 객체, 테스트 객체를 제외한 모든 의존물은Stub으로 대체 ASSERT는 가급적 테스트 당 1개 목 객체를 재사용 : 목 객체 내부에 ASSERT 삽입 금지 모든 테스트는 격리해서 실행
  • 55. 감사합니다 whoo24@gmail.com Twitter: whoo24 Blog : http://blog.wychoe.net