지금까지 CookBook 의 예제를 따라서 하시다 보면 드는 의문이 있을 것입니다. 원칙대로 따라서 소스코드를 친 다음에 나온 결과물을 실행하면

.
OK


라고 덜렁 나오는 것을 볼 수가 있을것입니다. NUnit 을 써보신 분들은 그 화려한 GUI 나 , TextMode 일때에도 나오는 그 많은 정보들이 하나도 나오지 않습니다. 심지어는 내가 무엇을 테스트 하는지에 대한 정보조차 없습니다.  CppUnit 상당히 실망스러울 수밖에 없었습니다. 하지만 없을리가 없지 않습니까 ㅎㅎ , 그래서 조금 조사해보니 답이 나오더군요.

cppTest.cpp 만 바꿔 주면 됩니다.

int main(int argc , char **argv)
{

 // 결과값을 저장하기 위한 매니저와 콘트롤러 성격을 선언해줍니다.
  CPPUNIT_NS::TestResult controller;


  // 결과값을 저장하기 위한 컬렉터를 추가해줍니다.
  CPPUNIT_NS::TestResultCollector result;
  controller.addListener( &result );


  // 테스트가 진행되는 동안 "." 을 표시해서 진행시간 추정하게 해주는
  // 프로그래스 부분을 추가합니다.
  CPPUNIT_NS::BriefTestProgressListener progress;
  controller.addListener( &progress );


  // 기존과 동일하게 테스트 러너를 돌려줍니다.

  CPPUNIT_NS::TextUi::TestRunner runner;
  runner.addTest(  CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest()  );
  runner.run( controller );

  return result.wasSuccessful() ? 0 : 1 ;
}

쉽지요? 기존의  코드랑 다른점은 또 있습니다. CppUnit 대신 CPPUNIT_NS 가 쓰이지요? 그게 강조사항이더군요. 그래서 적용해줬습니다.  이렇게 바꾸고 실행해보세요 결과는 직접 확인하시고요.

참 추가해야 할 헤더 파일 들 입니다.

#include <cppunit/TestResultCollector.h>
#include <cppunit/BriefTestProgressListener.h>

추가 해야지만 컴파일 됩니다.
Helper Macros

TestRunner 에서 봤듯이 Fixture 에는 Static 함수는 suite() 가
필요합니다. 하지만 이것을 매번 작성하다보면 , 내용이 귀찮고,
반복적으로 해야 하고 그러다 보면 실수를 유발하게 됩니다. 그래서
이부분을 편리한 매크로로 만들어서 쓰게 할 수 있습니다.

기존의 파일에서

#include <cppunit/extensions/HelperMacros.h>

를 추가하면 macro를 쓸 수 있게 해줍니다.

CPPUNIT_TEST_SUITE ( ComplexNumberTest) ;

('' 이것을 선언해주면 suite 를 쓰겠다고 선언하는 것입니다.

suite 에 추가해줄 함수들을 추가하고요

CPPUNIT_TEST (testEquality ) ;
CPPUNIT_TEST (testAddition ) ;


그리고 suite 선언이 끝났다고 선언해주는 것입니다.

CPPUNIT_TEST_SUITE_END () ;


여기까지 차례로 쓰면 , 아래 나와 있는 메서드 전체를 선언 과 구현해준
것입니다.

static CppUnit::TestSuite * suite () ;

나머지 부분들은 기존과 동일하게 처리하시면 됩니다.

complextest.h 를 보기로 하지요

class ComplexNumberTest : public CppUnit::TestFixture  {
 private:
  Complex *m_10_1, *m_1_1, *m_11_2;
 public:

  CPPUNIT_TEST_SUITE( ComplexNumberTest );
  CPPUNIT_TEST( testEquality);
  CPPUNIT_TEST( testAddition);
  CPPUNIT_TEST_SUITE_END();


 public:
  void testDivideByZeroThrows ()
  {

  }
  void setUp()
  {
    m_10_1 = new Complex( 10, 1 );
    m_1_1 = new Complex( 1, 1 );
    m_11_2 = new Complex( 11, 2 );
  }

  void tearDown()
  {
    delete m_10_1;
    delete m_1_1;
    delete m_11_2;
  }

  void testEquality()
  {
    CPPUNIT_ASSERT( *m_10_1 == *m_10_1 );
    CPPUNIT_ASSERT( !(*m_10_1 == *m_11_2) );
  }

  void testAddition()
  {
    CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
  }

};



EXCEPTION 부분은 생략하기로 합니다.


TestFactoryRegistry

TestFactoryRegistry 는 두가지 이유로 씁니다.
 fixture suite 를 테스트 러너에 추가하는 것을 가끔 잊어먹을때가
 있습니다. 다른 파일에 보통 fixuture 가 있기 때문에 종종 그렇게
 잊어먹곤 합니다.

 test case 의 모든 헤더를 한곳에 집중해서 선언하면 컴파일 할때 병목
 현상이 생기기 때문입니다.


다음의 예를 봅시다.

ComplexNumberTest 를 등록 시키기 위해서는 .cpp 파일에 다음과 같이
추가합니다.

CPPUNIT_TEST_SUITE_REGISTRATION ( ComplexNumberTest) ;

이 이면에는 AutoRegisterSuite 의 static 변수가 선언되어 , 생성시에 그
변수가 TestSuiteFactory 를 TestFactoryRegistry 에 등록시킵니다. 그리고
TestSuiteFacctory 가 ComplexNumberTest::suite() 에 의해 반환되는
TestSuite를 반환합니다.

주절 주절 말을 늘어놓아지만 결국 할 이야기는 TestSuite 를 짜고 등록을
시키면 나중에 한꺼번에 몰아서 호출이 가능하다는 이야기 입니다.

실제로 이 테스트를 실행시키려고 하면 우리는 더이상 주저리 주저리
헤더를 등록 시킬 필요 없이

#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>

Factory 와 TestRunner 만 인클루드 시키면 됩니다.


 
CppUnit::TestFactoryRegistry & registry =
 CppUnit::TestFactoryRegistry::getRegistry();


이렇게 하면 CPPUNIT_TEST_SUITE_REGISTRATION () 를 통해서 등록한 모든
test suite를 가져올수 있습니다.

 
runner.addTest (registry.makeTest () );
 runner.run () ;

 return 0 ;

이렇게 실행합니다. (기존의 코드를 지우고 새로 위의 코드들을 써 넣으십시요)

main 함수가 간단해 지고 , 간결해 지는 장점이 있습니다.


Post build Check 부분은 나중에 따로 다른 예로 설명 드리고 CookBook 은
여기서 마치는 걸로 하겠습니다.

Suite

테스트 케이스를 한꺼번에 몰아서 돌릴려고 하면 , Suite 가 필요합니다. 정확히는 TestSuite 이지요. 전 포스팅에서 한개체를 돌리는 경우를 봤으니 이번에는 한번에 돌리는걸 알아 보도록 하죠.

cppTest.cpp

#include <cppunit/TestSuite.h>

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

    CppUnit::TestSuite suite;
    CppUnit::TestResult result;
     suite.addTest( new CppUnit::TestCaller<ComplexNumberTest>(
                       "testEquality",
                       &ComplexNumberTest::testEquality ) );
      suite.addTest( new CppUnit::TestCaller<ComplexNumberTest>(
                       "testAddition",
                       &ComplexNumberTest::testAddition ) );
       suite.run( &result );

     return 0;
}


main 을 위와 같이 변경합니다. TestSuite 를 한개 선언하고 suite 에 한개씩 TestCaller 를 이용해서 test 메서드를 채워 넣고 한꺼번에 호출하는 형식입니다. 위 예제에서는 testEquality , testAddtion 을 추가하고 있습니다.

이 뿐만 아니라 미리 만들어둔 suite 가 있다면 새로운 suite 에 병합을 시킬 수도 있습니다.

CppUnit::TestSuite suite;
CppUnit::TestResult result;
suite.addTest( ComplexNumberTest::suite() );
suite.addTest( SurrealNumberTest::suite() );
suite.run( &result );

suite 는 Test 기반 Interface 를 가진 것들은 전부 포함시킬 수가 있습니다.

TestRunner

드디어 TestRunner 까지 왔습니다. 지금까지의 TestCase 들은 동작하면 아무런 문제 없이 프로그램이 종료됐고, 문제가 발생했다면( Test 가 실패했다면 ) 프로그램이 종료됐습니다. 조금 답답한 인터페이스 이지요. Test 를 하고 그 결과를 보고 싶을때는 어떻게 하는가에 대한 대답이 TestRunner 입니다.

TestRunner 도 Test Interface 를 가진 Test 들을 (Suite 나 TestCase ) 추가로 포함해서 실행하고 그 결과를 보여 줄 수 있습니다.

다만 TestRunner 에게 연결이 될려면 CppUnit::Test * 형식을 반환하는 static 함수가 포함되어야 합니다.

complextest.h

void testAddition()
  {
    CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
  }

 static CppUnit::Test *suite()
  {
    CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite( "ComplexNumberTest" );
    suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>(
                                   "testEquality",
                                   &ComplexNumberTest::testEquality ) );
    suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTest>(
                                   "testAddition",
                                   &ComplexNumberTest::testAddition ) );
    return suiteOfTests;
  }


기존의 code 중 testAddtion 함수 밑에 붉은 색 부분을 추가합니다.
그리고 main 함수를 바꿉니다.

cppTest.cpp 

#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/TestResult.h>


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

  CppUnit::TextUi::TestRunner runner;
  runner.addTest( ComplexNumberTest::suite() );
  runner.run();

  return 0;
}


일단 추가한 헤더파일은 TestRunner 의 헤더 파일입니다. cppunit 기본으로 Text 버젼이 포함되어 있습니다. MFC 버젼이나 QT , QT4 버젼을 쓰고 싶으신 분은 관련 자료를 쉽게 찾으실 수가 있습니다. - 관련 URL 링크 해뒀음

TestRunner 는 테스트를 실행하고 정보를 저장합니다. 성공하면 - 성공했다는 메시지와 함께 실패하면

 - 실패한 테스트 케이스 이름
 - 테스트를 포함한 소스 파일의 이름
 - 실패가 일어난 줄 번호
 - 실패를 야기한 곳의 모든 Text 정보입니다. ( 애매해서 의역이 심하네요 ㅎㅎ)



 

'테스트 주도 개발' 책에서 또 관심을 끌었던 부분입니다. 마치는 글을 마틴
파울러가 장식을 했군요.

그 부분도 올려봅니다.

 
테스트 주도 개발에 대해 설명하기 어려운 것 중 하나는 TDD를 하면
돌입하게 되는 정신상태다. C3 프로젝트에서 랄프 베티(Ralph Beattie)와
함께 했던 세션을 기억한다. 우리는 복잡한 지불 조건을 구현해야만
했다. 랄프는 그걸 여러 개의 테스트 케이스로 쪼갰고, 우리는 그것들이
작동하도록 하나씩 접근했다. 꾸준히 서두르지 않으면서
진행했다. 서두르지 않았기 때문에 느린 것처럼 보였지만, 얼마나 많은
일을 했는지 뒤돌아 보면 , 작업시의 느긋한 느낌에도 불구하고 진행
속도는 매우 빨랐다.
우리가 가진 화려한 도구들에도 불구하고, 프로그래밍은 여전히
어렵다. 동시에 여러 개의 공을 공중에 띄워놓고 저글링할 때처럼 ,
프로그래밍할 때도 잠시만 집중을 놓치면 모든 게 무너져 버릴 것 같은
느낌을 받은 적이 많다. 테스트 주도 개발은 이런 느낌을 감소시키는 데
도움이 되고, 그 결과로 신속한 느긋함을 얻게 된다.

 그렇게 생각하는 이유는, 테스트 주도 개발 스타일로 작업하면 한 번에
딱 하나의 공만 공중에 띄우는 느낌이 들고, 그렇기 때문에 그 공에
충분히 집중할 수 있고 그 일을 정말 훌륭히 해낼 수 있다는
것이다. 나는 새로운 기능을 추가하려고 할 때, 이 함수에는 정말 어떤
설계가 좋을지 고민하지 않고 , 할 수 있는 한 가장 쉽게 테스트를
통과시키려고만 노력한다. 리팩토링 모드로 바뀌면 새로운 기능을
추가하는 것에 신경 쓰지 않고, 올바른 설계를 얻는 데만 신경 쓴다. 이
양자를 통해 한 번에 딱 하나에만 집중하게 되고, 그 결과 그 한 가지에
대해 더 잘 집중할 수 있다.

 테스트 우선으로 기능을 추가하는 것과 리팩토링은 프로그래밍의 두 가지
단일사고적 특색이다. 최근 키보드를 두드리다가 또 다른 하나를 경험하게
됐다. 그것은 패턴 복사하기다. 데이터베이스에서 어떤 데이터를 가져오는
작은 루비 스크립트를 작성하고 있었다. 이 일을 하면서 데이터베이스
테이블을 감싸는(wrap) 클래스에 대해 작업을 시작했다. 그때 데이터베이스
패턴 책을 한 권 끝냈기 때문에 패턴을 하나 사용해야 한다는 생각이
들었다. 예제 코드는 자바였지만, 그걸 루비에 적용하는 건 어렵지
않았다. 프로그래밍하면서 문제에 대해서는 별로 생각하지 않고, 그 패턴을
현재 언어와 내가 다루던 특정 자료에 어떻게 멋지게 적용할까에 대해
생각했다.

 패턴 복사하기 자체는 훌륭한 프로그래밍이 아니다. 이 사실은 내가 패턴에
대해 이야기 할 때면 늘 강조한다. 패턴은 언제나 반숙 상태며, 자신의
프로젝트 오븐 속에서 적응시켜야 한다. 하지만 이렇게 하기 위한 좋은 방법
중 하나는 일단 무턱대고 패턴을 복사하고 나서, 리팩토링과 테스트 우선을
섞어 사용해서 그 적응과정을 수행하는 것이다. 패턴 복사를 할 때 이렇게
하면 해당 패턴에 대해서만 집중할 수 있게 된다(한번에 하나씩).

 XP 커뮤니티는 전체 그림에서 패턴이 어디에 위치해야 하는지 고심해
왔다. XP 주창자와 패턴 주창자들 사이에는 상당히 큰 일치점이 있기 때문에
(워드와 켄트는 두 분야 모두 리더다) XP 커뮤니티는 분명 패턴을
좋아한다고 볼 수 있다. 테스트 우선과 리팩토링이 그 자체로는 위험하지만
함께 하면 강력한 것처럼, 패턴 복사는 그 둘과 함께 가야 할 세 번째 단일
사고적 모드일지도 모르겠다.

 핵심 작업을 분간하고 한 번에 딱 하나만 집중하도록 하는 것이 어떤
활동을 체계화하는 데 주요한 역할을 한다. 조립 라인은 이런 예 중 정신을
멍하게 하는 예가 된다. 언제나 정해진 한 가지만 하기 때문이다. 아마
테스트 주도 개발이 제안하는 것은 프로그래밍의 행위를 여러 기본적 모드로
나누되, 그런 모드를 재빨리 전환해 가면서 단조로움을 피하는 것이지
않을까, 단일사고적 모드들과 전환의 조합을 통해 집중의 이득을 얻을 수
있고, 조립 라인의 단조로움 없이 뇌에 가해지는 스트레스를 낮출 수
있다.

 이 생각들이 다소 미숙하다는 것을 인정한다. 이 글을 쓰면서도 여전히
내가 하는 말을 스스로 믿는지 확신하지 못하고, 이 생각들을 몇 개월,
아니 꽤 오랫동안 고민할 것이라는 걸 안다. 하지만 어쨌거나, 테스트 우선
개발이 속하는 더 큰 그림에 대한 생각을 자극하기 때문에 독자가 내
아이디어를 좋아하지 않을까 하는 생각을 했다. 아직 우리가 분명히 볼 수
있는 그림은 아니지만 서서히 자신을 드러내고 있는 그림이라고 생각한다.


이 글은 실제로 TDD를 하면서 나타나는 일에 대한 집중도에 관해서 설명을
하고 있습니다. 저도 TDD를 쓰며 개발하면서 느끼는 것은 역시 일에 대한
집중도에 놀랐었는데, 마틴 파울러가 이 부분을 잘 지적을 하고 있는듯 합니다.

테스트 주도 개발 책을 읽으면서 그 책에서 제일 괜찮다고 생각하는
부분중에 한 부분입니다. TDD 개념을 가장 쉽게 접근할 수 있도록 만든
예제인데 무단으로 퍼서 소개할까 합니다. - 사실 본 책 안에 있는 내용은
좀 난해한 편이고 , 이 예제가 더 이해하기 쉽습니다. -

부록 B . 피보나치

이 책의 검토자 중 한 명의 질문에 대한 답으로 나는 피보나치 수열을
테스트 주도로 개발해 올렸다. 몇 명의 검토자들이 이 예를 보고 TDD가
어떻게 작동하는지 이해하는 데 큰 도움이 되었다고 했다. 하지만 이 책에
사용된 예제를 피보나치 예제로 바꾸기에는 피보나치 예제가 너무 짧고,
다양한 TDD 기술을 충분히 보여주지도 못한다. 이 책의 주 예제를 읽은
후에도 여전히 번쩍이는 깨달음을 얻지 못했다면 여기를 잠깐 들여다보고
머릿속 전등이 켜지는지 살펴보자.
 
첫 번째 테스트는 fib(0) = 0 이라는 걸 보여준다. 구현은 상수를
 반환한다.

 - 참고로 TDD는 실패하는 Test Code 부터 작성합니다.
 - 그리고 그것을 제대로 돌리기 위한 코드를 작성합니다.

public void testFibonacci () {
       assertEquals(0 , fib(0));
}

int fib(int n) {
    return 0;
}

(함수 하나밖에 안되기 때문에 나는 TestCase 클래스에 직접 해당 코드를
박아 넣구 있다)
 두 번째 테스트는 fib(1) = 1 이라는 걸 보여준다.

pulic void testFibonacci () {
      assertEquals (0 , fib(0));
      assertEquals (1, fib(1));
}

testFibonacciOfOneIsOne 이라는 테스트 메서드를 따로 작성하는 것에 큰
커뮤니케이션 가치가 있어 보이지 않아서 , 두 번째 단언을 같은 메서드
내에 집어 넣어 버렸다.
 이게 돌아가도록 하는 덴 몇 가지 방법이 있다. 나는 0을 특별한 경우로
 다루는 방법을 쓰겠다.

 int fib (int n) {
 if (n == 0) return 0;
 return 1;
}

테스트 케이스에 있는 중복이 점점 성가시게 느껴지기 시작하는데, 새
캐이스를 추가하면 더 악화되기만 할 것이다. 입력과 예상값으로 구성된
테이블을 통해 테스트가 돌아가게 하면 단언의 공통 구조를 추출할 수
있겠다.

public void testFibonacci () {
       int cases[][] = {{0,0} , {1,1}};
       for (int i= 0; i < cases.length ; i++)
           assertEquals(cases[i][1] , fib (cases[i][0]));
}

 이제 다음 케이스를 추가하려면 키보드를 여섯 번만 치면 되고, 줄을 새로
 추가할 필요는 없다.

 public void testFibonacci () {
  int case[][] = {{0,0} , {1,1} , {2,1}};
   for (int i=0 ; i < cases.length ; i++)
       assertEquals(cases[i][1] , fib(cases[i][0]));
}

당황스럽게도 테스트가 제대로 돌아간다. 우리가 고른 상수 1이 이
케이스에도 들어맞는 값이기 때문에 그런 일이 벌어졌다. 다음 테스트로
넘어가면

public void testFibonacci () {
  int case[][] = {{0,0} , {1,1} , {2,1}, {3,2}};
   for (int i=0 ; i < cases.length ; i++)
       assertEquals(cases[i][1] , fib(cases[i][0]));

}

야호, 이제 실패한다. 이전의 전략(더 작은 입력값을 특별한 경우로 다루는
것)을 똑같이 적용해서 다음과 같이 작성한다.

int fib(int n) {
    if (n == 0) return 0;
    if (n <= 2) return 1;
    return 2;
}

 이제 일반화할 준비가 되었다. 우리가 2라고 쓰긴 했지만 정말 2를
 뜻한것은 아니고 , 1+1 을 의미한다.

 int fib(int n) {
    if (n == 0) return 0;
    if (n <= 2) return 1;
    return 1+1;
}

첫 번째 1은 fin(n-1)로 볼 수 있다.

int fib(int n) {
    if (n == 0) return 0;
    if (n <= 2) return 1;
    return fib(n-1) + 1;
}

두 번째 1은 fib(n-2)로 볼 수 있다.

int fib(int n) {
    if (n == 0) return 0;
    if (n <= 2) return 1;
    return fib(n-1) + fib(n-2);
}

이제 좀 정리를 하면, 동일한 구조가 fib(2) 에서도 작동하기  때문에 결국
두 번째 조건을 강화할 수 있다.

int fib(int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    return fib(n-1) + fib(n-2);
}

이렇게 해서 우리는 완전히 테스트로부터 유도된 피보나치를 완성하게
되었다.



제가 어디서 본 이야기인데 , TDD 를 단지 단위테스트 용 기법으로
생각하시는 분들이 많다고 합니다. 그러나 TDD는 '분석기법이고,
설계기법' 입니다. 그런것을 염두에 두시고 다시 한번 피보나치 구현을
보시면 TDD가 어떻게 설계에 쓰였는지 감을 잡으실 수 있을 것입니다.

사용자 삽입 이미지

제목: 테스트 주도 개발

원제: Test - Driven Development by Example
지은이 : 켄트 벡
옮김 : 김창준 , 강규영


테스트 주도 개발 ! 참 요즘들어 많이 듣는 말 입니다. 하지만 실행하기가
그리 쉽지 않은 부분이기도 하지요. 원인이 무엇인가 조금 생각해보면 알
수 있습니다. 결국 '실천' 이 관련되었기 때문입니다. 자신이 익숙한 것만
하려고 드는 개발자의 특성상 무엇인가 새로운 것을 해보기가 쉽지가 않은
것이 또한 큰 이유중에 한가지 입니다.

저는 이책을 지인의 추천으로 가지고 있었는데 서문을 조금 읽어본 후에
계속해서 가지고 있었습니다. 그리 흥미를 못느꼈기 때문입니다. 좋은
이야기가 많이 쓰여져 있지만 실천하기 힘든 역시나 일반적인 서적하고
비슷한게 아닐까 라는 편견을 가지고 있었기 때문이기도 합니다. 하지만
인터넷에서 NUnit 관련 글을 볼때 나오는 TDD 개념을 아주 잘 정리한 글을
보게됐습니다. 그래서 갑자기 TDD 에 대한 관심이 무럭 무럭 증가 하더
군요. 그래서 이 책을 다시 꺼내들어서 읽었습니다.

결과적으로 책만을 두고 본다면, 켄트백이 지은 책도 두어권 보고,
김창준씨가 옮긴책도 여러권 봤지만, 역시 난해합니다. 제가 인터넷
페이지에서 봤던 글은 정말 간결하게 TDD를 설명해서 매력이 있었는데
솔직히 이 책에서는 그 맛을 느낄 수가 없습니다. 심지어 지인과는
켄트백이냐 김창준씨냐.. 대체 누가 글을 어렵게 쓰는 것인가? 라는
농담까지 할 정도였습니다.

지극히 개인적인 평이지만, 저는 뒤에 나온 피보나치 수열을 TDD로
개발하는 예제와 , 마틴 파울러의 글이 제일 맘에 들더군요.
-ㅅ-
Fixture 는 Test Case 들을 모아놓구 한꺼번에 실행 시킬때 편리하게 쓸 수 있는 클래스 입니다. 여러 XUnit 프레임 워크를 찾아보시면 Fixture 는 대게 setUp()tearDown() 두가지 메서드를 지원한다는 것을 아실 수 있을 것입니다.

즉 다시 설명하자면, 여러개의 테스트 메서드에서 공통적으로 필요한 준비물이 있다면 (예를 들자면 DB에 접속 한다던지 , 아니면 아래 예제의 경우처럼 복소수 여러개를 미리 준비한다 던지) 이렇게 '미리' 준비하는 부분이 setUp() 메서드 이고, setUp() 에서 설정한 내용을 해제 한다던지 메모리를 지운다던지 DB 접속을 끊는다던지 하는 마무리 작업을 해주는 곳을 tearDown() 이라고 합니다. - 쉽게 말하자면 constructor / destructorinitialize / finalize 를 생각하시면 쉽습니다.

실제로 보기로 하지요.

complextest.h 의 내용
class ComplexNumberTest : public CppUnit::TestFixture {
private:
  Complex *m_10_1, *m_1_1, *m_11_2;
public:
  void setUp()
  {
    m_10_1 = new Complex( 10, 1 );
    m_1_1 = new Complex( 1, 1 );
    m_11_2 = new Complex( 11, 2 ); 
  }

  void tearDown()
  {
    delete m_10_1;
    delete m_1_1;
    delete m_11_2;
  }
};

 setUp() 함수에서 복소수 세개를 생성하고 , tearDown() 에서 그것을 삭제하는 예를 보여줍니다. 이제 여기에 테스트 함수들을 추가해야 겠지요?

추가한 모습입니다.
complextest.h
class ComplexNumberTest : public CppUnit::TestFixture  {
private:
  Complex *m_10_1, *m_1_1, *m_11_2;
public:
  void setUp()
  {
    m_10_1 = new Complex( 10, 1 );
    m_1_1 = new Complex( 1, 1 );
    m_11_2 = new Complex( 11, 2 );
  }
  void tearDown()
  {
    delete m_10_1;
    delete m_1_1;
    delete m_11_2;
  }
  void testEquality()
  {
    CPPUNIT_ASSERT( *m_10_1 == *m_10_1 );
    CPPUNIT_ASSERT( !(*m_10_1 == *m_11_2) );
  }
  void testAddition()
  {
    CPPUNIT_ASSERT( *m_10_1 + *m_1_1 == *m_11_2 );
  }
};
testEquality 와 testAddition 이 추가 됐습니다.

testAddition 을 통과 시킬려면 + Overloading 이 필요합니다.
 
class Complex {
  friend bool operator ==(const Complex& a, const Complex& b);
  double real, imaginary;
public:
  Complex( double r, double i = 0 )
    : real(r)
        , imaginary(i)
  {
  }

  Complex operator+(const Complex& a)
  {
    return Complex(real + a.real , imaginary + a.imaginary);
  }
};

bool operator ==( const Complex &a, const Complex &b )
{
  return a.real == b.real  &&  a.imaginary == b.imaginary;
}





이제 이렇게 되면 호출하는 방법이 조금 까다로운데 , TestFixture 중에서 하나의 테스트 메서드를 호출 하는 형식입니다.

#include <cppunit/TestCaller.h>
#include <cppunit/TestResult.h>
int _tmain(int argc, _TCHAR* argv[])
{
 
  CppUnit::TestCaller<ComplexNumberTest> test( "testEquality",
      &ComplexNumberTest::testEquality );
  CppUnit::TestResult result;
  test.run( &result );

  return 0;
}
저 위의 두 헤더를 추가하시고 , main 이 바뀐 모습처럼 호출해주시면 testEquality 함수를 호출하는 것입니다.

사실 이런 방법이 실제로는 잘 쓰이지 않습니다. TestRunner 에 결과를 위임해서 실패된거랑 성공된 것이랑 통계를 뽑아 내는 식이 많이 쓰이지요. 그래서 다음에는 test함수들을 등록시켜서 내가 원하는 함수들로 테스트를 쭉 뽑아내게 할 수 있는 suite 개념과 TestRunner 부분을 다뤄볼까 합니다.

ps. 참고로 저는 사용자 커스터마이징 INCLUDE , LIB 을 쓰는 것을 별로 좋아하지 않아서 include 파일들은 VC/include/cppunit 밑에 전부 두고, LIB 을 VC/lib 에 둡니다. 그리고 실행 dll 들은 WINDOWS/system32 밑에 둬서 간편한게 빌드하는 방법을 애용합니다. 이건 취향의 문제니 알아서들 원하시는 대로 하시면 됩니다.
CppUnit 을 공부하는 와중에 요리책 (Cookbook)을 따라하면서 한글로 좀 정리를 할 필요가 있어서 정리중입니다. 원래 글은 CookBook 을 직접 보시면 되시고 저는 저의 입장이 많이 반영된 글입니다.

환경 : .NET 2005 의 VC8.0

간단한 테스트 케이스 - Simple Test Case

지금부터 복소수(complex) Class 를 만들어 볼까 합니다. 이를 TDD (Test Driven Development)를 이용해서 작성할 것입니다.

처음에 프로젝트를 생성합니다. 일단 GUI 버젼을 빼고 Text 버젼으로 시작해보지요.

VS2005 에서
파일 - 새로만들기 - 프로젝트  를 선택하시고
Visual C++ 섹션 - Win32 - Win32 콘손 응용 프로그램
으로 새 콘솔용 프로젝트를 생성합니다.

프로젝트 이름은 간단하게 cppTest 라고 합니다.

Test First !! 를 주장하는 TDD 철칙에 따라서 Test Code 부터 주절 주절 작성합니다.

(컴파일도 안되는) Test Code 부터 작성한다.

complextest.h , 와 complextest.cpp 를 생성합니다.

complextest.h - (이거 색깔별로 이쁘게 하는 법이 있던대.. )
#pragma once
class ComplexNumberTest : public CppUnit::TestCase
{
 public:
 ComplexNumberTest( std::string name ) : CppUnit::TestCase (name ) {}
  void runTest ()
  {
    CPPUNIT_ASSERT ( Complex (10, 1) == Complex (10, 1));
    CPPUNIT_ASSERT ( !(Complex(1, 1) == Complex (2, 2)));
  }
};

complexest.cpp
#include "stdafx.h"
#include "complextest.h"

runTest 부분을 보시면 단지 복소수(complex) 가 제대로 생성되서 객체끼리 같은지 비교를 하고 또 다른지 판단을 하는것이 첫 순서라고 할 수 있겠습니다. 그래서 그부분을 테스트 하는 코드를 집어 넣었습니다.

여기서의 CPPUNIT_ASSERT 는 기존의 단언문(ASSERT ) 과 행동하는 것이 똑같습니다. (참이 아니면 프로그램이 살포시 죽어줍니다. )

이제 테스트 Class 를 생성해 주었으니 , 이를 메인에서 호출하게 해 줘야 겠지요.

cppTest.cpp
#include "stdafx.h"
#include "complextest.h"
int _tmain(int argc, _TCHAR* argv[])
{
  ComplexNumberTest test("test") ;
  test.runTest();
        return 0;
}
 

붉은색이 제가 추가한 부분입니다.
자 이제 테스트 코드를 작성했으니 Build 를 해 줄 차례지요? IDE 에서 F7 를 눌러주거나  콘솔에서 가볍게 MSBuild.exe 를 쳐주시면 빌드가 진행됩니다.
당연히 예상하듯이 에러가 잔뜩 나올 것입니다. 여기까지가 첫째 코스 입니다.

에러코드를 살펴보시면 Complex 클래스가 없다는 이야기 부터 나올것입니다.  그러면 Complex 클래스를 작성해 줘야 겠지요? 이때 작성 순서는

'컴파일이 우선 가능하게 코드를 작성한다'

입니다.
complex.h 와 complex.cpp 를 추가합니다.

complex.h
class Complex
{
  friend bool operator==(const Complex& a, const Complex & b);
  double real , imaginary ;
 public:
  Complex (double r , double i = 0)
      : real(r) , imaginary(i)
  {
  }
};

comlex.cpp
#include "stdafx.h"
#include "complex.h"

bool operator== (const Complex &a , const Complex &b)
{
   return true; // 임시 방편임 컴파일만 가능하게..
}

그리고 complextest.h 에
#pragma once
#include <cppunit/TestCase.h>
#include "complex.h"
class ComplexNumberTest : public CppUnit::TestCase
{
 public:
 ComplexNumberTest( std::string name ) : CppUnit::TestCase (name ) {}
  void runTest ()
  {
    CPPUNIT_ASSERT ( Complex (10, 1) == Complex (10, 1));
    CPPUNIT_ASSERT ( !(Complex(1, 1) == Complex (2, 2)));
  }
};
붉은색 두줄을 추가해주면 Compile 은 성공합니다. 자 그러면 실행해볼까요? 실행하면 runTest 의 두번째 단언문에서 프로그램이 죽는 것을 알 수가 있습니다. 즉  operator == 가 제대로 안 만들어 졌다는 이야기 이지요 , 그렇다면 다음 과정은

'Test 가 성공하게 프로그램을 바꾸자'

입니다. 아직은 소스가 간단하니까 어디를 바꾸시면 되는지 금새 아시겠지요?

complex.cpp 입니다.

#include "stdafx.h"
#include "complex.h"

bool operator== (const Complex &a , const Complex &b)
{
  return a.real == b.real && a.imaginary == b.imaginary ;
}

이부분을 바꾸고 Build 하시고 실행하시면 프로그램은 문제 없이 돌아갑니다.
TDD 라고 해서 어려운 것이 아니고 제가 붉은 색으로 쓴 글을 반복하는 행위 입니다.

(컴파일도 안되는) Test Code 를 작성합니다. (실제 사용하는 사용자의 입장에서) - 컴파일이 되게 코드를 수정합니다. (모자라는 부분 이 있으면 추가를 해서 잽싸게 Compile 만 되게 만듭니다.) - Test 가 성공하게 프로그램을 수정합니다.

이 세가지 부분만 계속해서 반복하시면서 프로그램을 작성해 나가시는게 TDD 입니다.

많은 분들이 TDD 를 단위 테스팅 기법이라고 생각하시는 분이 많습니다. 그러나 실전에 도입해서 계속해서 사용하다 보면 TDD 는 '설계 나 분석' 기법에 가깝습니다.
컴파일 하면 TestRunner 쪽에서 에러가 발생합니다. 사실 TestRunner 없어도 cppunit 안에 Text 형식의 TestRunner 가 포함되어 있긴 하지만 (이걸 사실 제일 많이 씁니다)

그래도 안되는게 있으면 짜증이 나지요. 만지작 거리다 보니까 CPPUnit 쪽 위키에서 해결방안이 있더군요.

http://cppunit.sourceforge.net/cppunit-wiki/BuildingCppUnit1


('' 여기를 참조하시면 되고요. 실제로 수정하는 부분은

#import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version("7.0") lcid("0") raw_interfaces_only named_guids

여기서 7.0 인 부분을 8.0 으로 수정해 주시면 됩니다. .NET 2005 가 VC8.0 이기 때문입니다.

#import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version("8.0") lcid("0") raw_interfaces_only named_guids



즐겁게 TDD 를 응용하세요.

+ Recent posts