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

55
C++ 프프프프프 프프 프프프 프프프프 최최최 최최최최 최최최최최최

Upload: heo-seungwook

Post on 11-May-2015

6.431 views

Category:

Travel


6 download

TRANSCRIPT

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

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

최우영위메이드 엔터테인먼트

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

강연자 소개

최우영 ( 주 ) 위메이드 엔터테인먼트

신규 게임 개발팀 - 서버 파트 ( 주 ) 라온 엔터테인먼트

테일즈런너 신규 게임 개발

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

Test?

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

통합 테스트 VS 단위 테스트

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

단위 테스트 단일 단위를 분리하여 실행하는 테스트

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

단위 테스트란

다른 코드를 호출한 후 몇 가지 가정이 성립하는지 검사하는 코드

여기서 ' 단위 (unit)' 란 메서드나 함수를 의미

X + Y = Z ?

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

좋은 단위 테스트

자동화 반복 실행 쉬운 구현 쉬운 실행 빠른 속도 로직 없는 테스트

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

테스트 프레임워크

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

Chromium, Protocol Buffers, … 등등

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

Gtest 사용하기

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

int main(int argc, _TCHAR* argv[]){

::testing::InitGoogleTest(&argc, argv);

return RUN_ALL_TESTS();}

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

테스트 문법의 기본

ASSERT_TRUE( ACTUAL )ASSERT_FALSE( ACTUAL )

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

ASSERT_FLOAT_EQ( EXPECTED, ACTUAL )

ASSERT_STREQ( EXPECTED, ACTUAL)

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

테스트 작성TEST( test_suite, test_case ){

...ASSERT_XXX()...

}

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

간단한 테스트의 작성

swap() 함수 구현 X, Y 를 인자로 받고 X = Y, Y = X 로

교환

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

테스트 파일 생성 Ex) Swap_Test.cpp

테스트 스위트 , 케이스 이름 결정 보수의 용이성을 위해 테스트 이름을 정한다 테스트 스위트는 테스트 카테고리 케이스 이름은 세부적인 테스트를 나타낸다

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

#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.

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

build => fail 존재하지 않는 함수 호출로 빌드 실패

#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";

}

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

build => successtest => failure

빌드 성공 , 테스트 실패

#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";

}

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

[==========] 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

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

[==========] 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

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

[==========] 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“;…

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

build => successtest => pass

테스트 통과

#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";

}

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

[==========] 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.

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

의존성 제거

클래스 간 복합적인 관계 의존성 존재 테스트 저해 설계 : 파일 , 스레드 , 통신등

외부 의존물을 사용 통합테스트 => 단위 테스트

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

CPacketHandler

+ProcessPacket()

CPacketParser

+ParsePacket()

CConnectionManager

+SendPacket()

통신 담당

패킷 변환테스트 대상

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

...TEST( CPacketHandler, ProcessPacketSuccessWithItemBuyReq){

CPacketHandler PacketHandler;

MSG_ITEM_BUY_REQ ItemBuyReq;

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

ASSERT_TRUE( bRET );}

CPacketHandler

+ProcessPacket()

CPacketParser

+ParsePacket()

CConnectionManager

+SendPacket()

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

class CPacketHandler {

CConnectionManager* m_pConnectionManager;CPacketParser* m_pPacketParser;

public:CPacketHandler() {

m_pConnectionManager = new CConnectionMan-ager();

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

+ProcessPacket()

CPacketParser

+ParsePacket()

CConnectionManager

+SendPacket()

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

class CPacketParser {public:

......

MSG_BASE* ParsePacket( const char* pBuf, size_t packetSize ) {

MSG_BASE* pMsg = (MSG_BASE*)pBuf;... // 검증 코드 및 데이터 채우기return pMsg;

}

......};

CPacketHandler

+ProcessPacket()

CPacketParser

+ParsePacket()

CConnectionManager

+SendPacket()

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

CPacketHandler

+ProcessPacket()

CPacketParser

+ParsePacket()

CConnectionManager

+SendPacket()

class CConnectionManager {

CSendQueue* m_pSendQueue;public:

......

void SendPacket( MSG_BASE* pMsg ) {

// 실제로 메시지를 보내는 코드m_pSendQueue->PostMessage(pMsg);

}

......};

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

CPacketHandler

+ProcessPacket()

CPacketParser

+ParsePacket()

CConnectionManager

+SendPacket()

CSendQueue

+PostMessage()

class CConnectionManager {

CSendQueue* m_pSendQueue;public:

......

void SendPacket( MSG_BASE* pMsg ) {

// 실제로 메시지를 보내는 코드m_pSendQueue->PostMessage(pMsg);

}

......};

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

CPacketHandler

+ProcessPacket()

CPacketParser

+ParsePacket()

CConnectionManager

+SendPacket()

CSendQueue

+PostMessage()

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

CPacketHandler

+ProcessPacket()

CConnectionManager

+SendPacket()

CSendQueue

+PostMessage()

CTcpSocket CThreadBase

CPacketParser

+ParsePacket()

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

CPacketHandler

+ProcessPacket()

CConnectionManager

+SendPacket()

CSendQueue

+PostMessage()

CTcpSocket CThreadBase

CPacketParser

+ParsePacket()

CPacketArchieve

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

스텁 (Stub)

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

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

CPacketHandler

+ProcessPacket()

CPacketParser

+ParsePacket()

CConnectionManager

+SendPacket()

CPacketHandler

+ProcessPacket()

CConnectionManager

+SendPacket()

CSendQueue

+PostMessage()

CTcpSocket CThreadBase

CPacketParser

+ParsePacket()

CPacketArchieve

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

간접 계층

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

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

CPacketHandler

+ProcessPacket()

CConnectionManager

+SendPacket()

CSendQueue

+PostMessage()

CTcpSocket CThreadBase

CPacketParser

+ParsePacket()

CPacketArchieve

CPacketHandler

+ProcessPacket()

CPacketParser

+ParsePacket()

CConnectionManager

+SendPacket()

CSendQueue

+PostMessage()

IConnectionManager

+SendPacket()

FakeConnectionManager

+SendPacket()

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

class CPacketHandler {

IConnectionManager* m_pConnectionManager;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, pack-etSize );

...if( pMsg->GetProtocol() == MSG_PROTOCOL_ITEM_BUY_REQ ) {

m_pConnectionManager->SendPacket( new PACKET_ITEM_BUY_ANS() );

return TRUE;}...return FALSE;

}...

};

CPacketHandler

+ProcessPacket()

IConnectionManager

+SendPacket()

FakeConnectionManager

+SendPacket()

CPacketParser

+ParsePacket()

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

스텁 주입하기

생성자 get, set 프로퍼티 매개변수 추상 팩토리

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

생성자 주입

장점 테스트 코드의 가독성 향상 해당 매개 변수가 필수임을 알림

단점 상호 참조의 경우 생성 문제 매개 변수의 개수가 늘어날 수록

가독성 , 관리용이성 악화

class CPacketHandler {

IConnectionManager* m_pConnectionManager;...

public:CPacketHandler(IConnectionManager* pMan-

ager) {

m_pConnectionManager = pMan-ager;

...}

...};

...FakeConnectionManager FakeManager;CPacketHandler Handler( &FakeManager );...

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

class CPacketHandler {

IConnectionManager* m_pConnectionManager;...

public:SetConnectionManager

(IConnec-tionManager* pManager)

{m_pConnectionManager = pMan-

ager;}

...};

...FakeConnectionManager FakeManager;CPacketHandler Handler;Handler.SetConnectionManager( &FakeManager );...

get, set 프로퍼티

작성의 용이함 필수적이지 않은 매개변수

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

그 외 대표적 방법들

매개변수 전달 함수를 호출할 때 함께

의존물을 넣어준다 .

추상 팩토리 실제 객체와 스텁을

생성하는 팩토리를 생성하는 추상 팩토리를 생성

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

캡슐화 문제

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

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

픽스쳐

class fixture_name;

TEST_F( fixture_name, case_name ){

...

...// Some TestsASSERT_XXX();...

}

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

class fixture_name : public testing::Test{

void SetUp();void TearDown();

};

TEST_F( fixture_name, case_name ){

...

...// Some TestsASSERT_XXX();...

}

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

목 객체 (Mock Object)

단위 테스트의 통과 , 실패를 판단하는 가짜 객체

하나의 테스트에 하나의 목 객체 사용

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

스텁 vs 목

스텁 : 객체의 대체제 . 테스트가 가능하도록 의존물을 없애는 것

목 : 테스트의 통과 , 실패를 검증

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

목 객체의 사용

CPacketHandler 가 IConnectionMan-ager::SendPacket() 메서드를 호출하는지 확인

ConnectionManager 의 인터페이스를 추출 , 스텁으로 교체 .

스텁의 SendPacket 메서드를 오버라이드 하여 체크 CPacketHandler

+ProcessPacket()

CPacketParser

+ParsePacket()

IConnectionManager

+SendPacket()

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

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->Get-

Protocol() );}

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

격리 프레임워크 (Isolation Framework)

목과 스텁 객체를 쉽게 생성할 수 있게 해주는 API 의 모음

테스트의 반복 작성시 도움을 받을 수 있다 . 예상값 ( 기대값 ) 의 측정

함수 호출 호출 횟수 인자

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

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() );

};

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

함수 호출 측정

TEST( MockExample, Expect_call ){

MockImpl impl;EXPECT_CALL( impl, GetPos() );EXPECT_CALL( impl, SetPos( _, _ ) );…// calls function…

}

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

함수 호출 횟수 측정

TEST( MockExample, Expect_call ){

MockImpl impl;EXPECT_CALL( impl, GetPos() )

.Times( 3 );…// calls function…

}

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

리턴값 지정

TEST( MockExample, Expect_call ){

MockImpl impl;// ON_CALL( impl, GetPos() )EXPECT_CALL( impl, GetPos() )

.WillByDefault(Return(50.0f));…// calls function…

}

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

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->Get-

Protocol() );}

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

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 );

}

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

목 객체는 테스트 당 1 개 목 객체 , 테스트 객체를 제외한 모든

의존물은 Stub 으로 대체 ASSERT 는 가급적 테스트 당 1 개 목 객체를 재사용 : 목 객체 내부에 AS-

SERT 삽입 금지 모든 테스트는 격리해서 실행

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

감사합니다

[email protected] Twitter: whoo24 Blog : http://blog.wychoe.net