Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
최우영위메이드 엔터테인먼트
강연자 소개   최우영   (주)위메이드 엔터테인먼트     신규 게임 개발팀 - 서버 파트   (주)라온 엔터테인먼트     테일즈런너     신규 게임 개발
Test?
통합 테스트 VS 단위 테스트   통합 테스트     둘 이상의 모듈을 하나의 그룹으로 테스트 하      는것     소프트웨어에 기대되는 결과를 확인 하는 것.     많은 단위들을 실행   단위 테스트  ...
단위 테스트란   다른 코드를 호출한 후 몇 가지 가정이 성    립하는지 검사하는 코드   여기서 단위(unit)란 메서드나 함수를 의    미   X+Y=Z?
좋은 단위 테스트   자동화   반복 실행   쉬운 구현   쉬운 실행   빠른 속도   로직 없는 테스트
테스트 프레임워크   CppUnit, UnitTest++, TUT, …   사용하기 쉽고 신뢰할 수 있는 GoogleTest   http://code.google.com/p/googletest/   구글의 제품에...
Gtest 사용하기   gtest 라이브러리 다운로드   gtest.h 파일 include   라이브러리 빌드 후 lib 파일 링크#include <gtestgtest.h>#pragma comment(lib, “g...
테스트 문법의 기본   ASSERT_TRUE( ACTUAL )    ASSERT_FALSE( ACTUAL )   ASSERT_EQ( EXPECTED, ACTUAL )    ASSERT_NE( EXPECTED, ACT...
 테스트 작성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 ...
#include <gtestgtest.h>TEST( Swap_Test, Swap_True ){       int x = 30;       int y = 15;       swap( x, y );       ASSERT_...
#include <gtestgtest.h>void swap(int& x, int& y){}TEST( Swap_Test, Swap_True ){       int x = 30;       int y = 15;       ...
[==========] Running 1 test from 1 test case.[----------] Global test environment set-up.[----------] 1 test from Swap_Tes...
[==========] Running 1 test from 1 test case.[----------] Global test environment set-up.[----------] 1 test from Swap_Tes...
[==========] Running 1 test from 1 test case.[----------] Global test environment set-up.[----------] 1 test from Swap_Tes...
#include <gtestgtest.h>void swap(int& x, int& y){       int t = x;       y = x;       x = t;}TEST( Swap_Test, Swap_True ){...
[==========]   Running 1 test from 1 test case.[----------]   Global test environment set-up.[----------]   1 test from Sw...
의존성 제거   클래스 간 복합적인 관계 의존성 존재   테스트 저해 설계 : 파일, 스레드, 통신등    외부 의존물을 사용   통합테스트 => 단위 테스트
테스트 대상                                       패킷 변홖      CPacketHandler                        CPacketParser    +ProcessPac...
CPacketHandler                        CPacketParser                                                 +ProcessPacket()      ...
CPacketHandler                        CPacketParser                                                            +ProcessPac...
CPacketHandler                        CPacketParser                                                    +ProcessPacket()   ...
CPacketHandler                        CPacketParser                                                 +ProcessPacket()      ...
CPacketHandler                        CPacketParser                                                 +ProcessPacket()      ...
CPacketHandler                        CPacketParser+ProcessPacket()                        +ParsePacket()                 ...
CPacketHandler                        CPacketParser+ProcessPacket()                        +ParsePacket()                 ...
CPacketHandler                        CPacketParser+ProcessPacket()                        +ParsePacket()                 ...
스텁(Stub)   외부 의존물을 대신하기 위해 간접 계층    추가
CPacketHandler                        CPacketParser                                                         +ProcessPacket...
간접 계층   외부 의존물에 접근하기 위해 인터페이스    추가
CPacketHandler                        CPacketParser       CPacketHandler                     CPacketParser+ProcessPacket()...
CPacketHandler                     CPacketParser                                                                    +Proce...
스텁 주입하기   생성자   get, set 프로퍼티   매개변수   추상 팩토리
생성자 주입   장점                             class CPacketHandler                                   {     테스트 코드의 가독성 향상     ...
get, set 프로퍼티   작성의 용이함         class CPacketHandler                    {   필수적이지 않은 매개변수     IConnectionManager* m_pCon...
그 외 대표적 방법들   매개변수 젂달             추상 팩토리     함수를 호출할 때 함께        실제 객체와 스텁을 생     의존물을 넣어준다.           성하는 팩토리를 생성    ...
캡슐화 문제   테스트 용이성을 높이기 위한 방법   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 ){ ...
목 객체(Mock Object)   단위 테스트의 통과, 실패를 판단하는 가    짜 객체   하나의 테스트에 하나의 목 객체 사용
스텁 vs 목   스텁: 객체의 대체제. 테스트가 가능하도    록 의존물을 없애는 것   목: 테스트의 통과, 실패를 검증
목 객체의 사용   CPacketHandler가 IConnectionManager::    SendPacket() 메서드를 호출하는지 확인   ConnectionManager의 인터페이스를 추    출, 스텁으로 교...
class FakeConnectionManager: public IConnectionManager{public:  int m_nCalled;  MSG_BASE* m_pPacket;  ...     virtual void...
격리 프레임워크(Isolation Framework)   목과 스텁 객체를 쉽게 생성할 수 있게 해    주는 API의 모음   테스트의 반복 작성시 도움을 받을 수 있    다.   예상값(기대값)의 측정    ...
Google Mock 사용법   목 오브젝트 생성class Impl{public:      virtual void SetPos(float x, float y);      virtual float GetX() const...
함수 호출 측정TEST( MockExample, Expect_call ){      MockImpl impl;      EXPECT_CALL( impl, GetPos() );      EXPECT_CALL( impl, ...
함수 호출 횟수 측정TEST( MockExample, Expect_call ){      MockImpl impl;      EXPECT_CALL( impl, GetPos() )             .Times( 3 ...
리턴값 지정TEST( MockExample, Expect_call ){      MockImpl impl;      // ON_CALL( impl, GetPos() )      EXPECT_CALL( impl, GetP...
Class FakeConnectionManager: public IConnectionManager{public:  int m_nCalled;  MSG_BASE* m_pPacket;  ...     virtual void...
class FakeConnectionManager: public IConnectionManager{public:   MOCK_METHOD1( SendPakcet, void(MSG_BASE* pPacket);};int I...
팁   목 객체는 테스트 당 1개   목 객체, 테스트 객체를 제외한 모든 의존    물은 Stub으로 대체   ASSERT는 가급적 테스트 당 1개   목 객체를 재사용 : 목 객체 내부에    ASSERT 삽...
감사합니다   whoo24@gmail.com   Twitter: whoo24   Blog : http://blog.wychoe.net
Upcoming SlideShare
Loading in …5
×

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

3,978 views

Published on

  • Be the first to comment

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

  1. 1. 최우영위메이드 엔터테인먼트
  2. 2. 강연자 소개 최우영 (주)위메이드 엔터테인먼트  신규 게임 개발팀 - 서버 파트 (주)라온 엔터테인먼트  테일즈런너  신규 게임 개발
  3. 3. Test?
  4. 4. 통합 테스트 VS 단위 테스트 통합 테스트  둘 이상의 모듈을 하나의 그룹으로 테스트 하 는것  소프트웨어에 기대되는 결과를 확인 하는 것.  많은 단위들을 실행 단위 테스트  단일 단위를 분리하여 실행하는 테스트
  5. 5. 단위 테스트란 다른 코드를 호출한 후 몇 가지 가정이 성 립하는지 검사하는 코드 여기서 단위(unit)란 메서드나 함수를 의 미 X+Y=Z?
  6. 6. 좋은 단위 테스트 자동화 반복 실행 쉬운 구현 쉬운 실행 빠른 속도 로직 없는 테스트
  7. 7. 테스트 프레임워크 CppUnit, UnitTest++, TUT, … 사용하기 쉽고 신뢰할 수 있는 GoogleTest http://code.google.com/p/googletest/ 구글의 제품에 사용 중  Chromium, Protocol Buffers, … 등등
  8. 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. 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. 10.  테스트 작성TEST( test_suite, test_case ){ ... ASSERT_XXX() ...}
  11. 11. 간단한 테스트의 작성 swap() 함수 구현 X, Y 를 인자로 받고 X = Y, Y = X 로 교홖
  12. 12.  테스트 파일 생성  Ex) Swap_Test.cpp 테스트 스위트, 케이스 이름 결정  보수의 용이성을 위해 테스트 이름을 정한다  테스트 스위트는 테스트 카테고리  케이스 이름은 세부적인 테스트를 나타낸다
  13. 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. 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. 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. 16. [==========] 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_True1 FAILED TEST
  17. 17. [==========] 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_True1 FAILED TEST
  18. 18. [==========] 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: 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. 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. 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. 21. 의존성 제거 클래스 간 복합적인 관계 의존성 존재 테스트 저해 설계 : 파일, 스레드, 통신등 외부 의존물을 사용 통합테스트 => 단위 테스트
  22. 22. 테스트 대상 패킷 변홖 CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() 통신 담당
  23. 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. 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. 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. 26. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CConnectionManager +SendPacket()class CConnectionManager{ CSendQueue* m_pSendQueue;public: ...... void SendPacket( MSG_BASE* pMsg ) { // 실제로 메시지를 보내는 코드 m_pSendQueue->PostMessage(pMsg); } ......};
  27. 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. 28. CPacketHandler CPacketParser+ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() CSendQueue +PostMessage()
  29. 29. CPacketHandler CPacketParser+ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() CSendQueue +PostMessage()CTcpSocket CThreadBase
  30. 30. CPacketHandler CPacketParser+ProcessPacket() +ParsePacket() CConnectionManager +SendPacket() CSendQueue +PostMessage()CTcpSocket CThreadBase CPacketArchieve
  31. 31. 스텁(Stub) 외부 의존물을 대신하기 위해 간접 계층 추가
  32. 32. CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() CPacketHandler CPacketParser CConnectionManager+ProcessPacket() +ParsePacket() +SendPacket() CConnectionManager CSendQueue +SendPacket() +PostMessage() CTcpSocket CThreadBase CPacketArchieve
  33. 33. 간접 계층 외부 의존물에 접근하기 위해 인터페이스 추가
  34. 34. CPacketHandler CPacketParser CPacketHandler CPacketParser+ProcessPacket() +ParsePacket() +ProcessPacket() +ParsePacket() CConnectionManager IConnectionManager +SendPacket() +SendPacket() CSendQueue FakeConnectionManager CConnectionManager +PostMessage() +SendPacket() +SendPacket() CSendQueueCTcpSocket CThreadBase CPacketArchieve +PostMessage()
  35. 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. 36. 스텁 주입하기 생성자 get, set 프로퍼티 매개변수 추상 팩토리
  37. 37. 생성자 주입 장점 class CPacketHandler {  테스트 코드의 가독성 향상 IConnectionManager* m_pConnectionManager;  해당 매개 변수가 필수임을 알림 ... 단점 public:  상호 참조의 경우 생성 문제 CPacketHandler(IConnectionManager* pManager) {  매개 변수의 개수가 늘어날 수록 m_pConnectionManager = pManager; 가독성, 관리용이성 악화 ... } ... }; ... FakeConnectionManager FakeManager; CPacketHandler Handler( &FakeManager ); ...
  38. 38. get, set 프로퍼티 작성의 용이함 class CPacketHandler { 필수적이지 않은 매개변수 IConnectionManager* m_pConnectionManager; ... public: SetConnectionManager (IConnectionManager* pManager) { m_pConnectionManager = pManager; } ... }; ... FakeConnectionManager FakeManager; CPacketHandler Handler; Handler.SetConnectionManager( &FakeManager ); ...
  39. 39. 그 외 대표적 방법들 매개변수 젂달  추상 팩토리  함수를 호출할 때 함께  실제 객체와 스텁을 생 의존물을 넣어준다. 성하는 팩토리를 생성 하는 추상 팩토리를 생 성
  40. 40. 캡슐화 문제 테스트 용이성을 높이기 위한 방법 Public 상속 Friend 조건부 컴파일(#ifdef) 상용 프레임워크
  41. 41. 픽스쳐class fixture_name;TEST_F( fixture_name, case_name ){ ... ...// Some Tests ASSERT_XXX(); ...}
  42. 42. class fixture_name : public testing::Test{ void SetUp(); void TearDown();};TEST_F( fixture_name, case_name ){ ... ...// Some Tests ASSERT_XXX(); ...}
  43. 43. 목 객체(Mock Object) 단위 테스트의 통과, 실패를 판단하는 가 짜 객체 하나의 테스트에 하나의 목 객체 사용
  44. 44. 스텁 vs 목 스텁: 객체의 대체제. 테스트가 가능하도 록 의존물을 없애는 것 목: 테스트의 통과, 실패를 검증
  45. 45. 목 객체의 사용 CPacketHandler가 IConnectionManager:: SendPacket() 메서드를 호출하는지 확인 ConnectionManager의 인터페이스를 추 출, 스텁으로 교체. 스텁의 SendPacket 메서드를 오버라이드 하여 체크 CPacketHandler CPacketParser +ProcessPacket() +ParsePacket() IConnectionManager +SendPacket()
  46. 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. 47. 격리 프레임워크(Isolation Framework) 목과 스텁 객체를 쉽게 생성할 수 있게 해 주는 API의 모음 테스트의 반복 작성시 도움을 받을 수 있 다. 예상값(기대값)의 측정  함수 호출  호출 횟수  인자
  48. 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. 49. 함수 호출 측정TEST( MockExample, Expect_call ){ MockImpl impl; EXPECT_CALL( impl, GetPos() ); EXPECT_CALL( impl, SetPos( _, _ ) ); … // calls function …}
  50. 50. 함수 호출 횟수 측정TEST( MockExample, Expect_call ){ MockImpl impl; EXPECT_CALL( impl, GetPos() ) .Times( 3 ); … // calls function …}
  51. 51. 리턴값 지정TEST( MockExample, Expect_call ){ MockImpl impl; // ON_CALL( impl, GetPos() ) EXPECT_CALL( impl, GetPos() ) .WillByDefault(Return(50.0f)); … // calls function …}
  52. 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. 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. 54. 팁 목 객체는 테스트 당 1개 목 객체, 테스트 객체를 제외한 모든 의존 물은 Stub으로 대체 ASSERT는 가급적 테스트 당 1개 목 객체를 재사용 : 목 객체 내부에 ASSERT 삽입 금지 모든 테스트는 격리해서 실행
  55. 55. 감사합니다 whoo24@gmail.com Twitter: whoo24 Blog : http://blog.wychoe.net

×