본문 바로가기

MS/C++

CppUnit 다운로드 및 설치하기


출처 : IBM.com


유닛 테스트를 위한 오픈 소스 도구에 대한 시리즈에서 두 번째인 이 기사에서는 매우 유명한 CppUnit(Eric Gamma와 Kent Beck이 처음 개발한 JUnit 테스트 프레임워크의 C++ 포트)에 대해 소개한다. C++ 포트는 Michael Feathers에 의해 만들어졌으며 화이트 박스 테스트와 사용자 고유의 회귀 스위트 작성을 지원하는 다양한 클래스를 구성한다. 이 기사에서는 TestCase, TestSuite, TestFixture, TestRunner 및 헬퍼 매크로와 같이 더 유용한 CppUnit 기능 중 일부에 대해 소개한다.

자주 사용하는 약어

  • GUI: Graphical user interface
  • XML: Extensible Markup Language

CppUnit 다운로드하기 및 설치하기

이 기사의 목적상 CppUnit을 다운로드하여 g++-3.2.3 및 make-3.79.1이 설치된 Linux® 시스템(커널 2.4.21)에 설치했다. 설치는 간단하며 표준 설치이다. configure 명령을 실행한 후 makemake install을 실행한다. Cygwin과 같은 특정 플랫폼의 경우에는 이 프로세스가 원활하게 실행되지 않을 수 있기 때문에 설치와 함께 제공되는 INSTALL-unix 문서에서 세부 사항을 확인해야 한다. 설치가 완료되면 설치 경로에 CppUnit을 위한 include 및 lib 폴더가 표시된다(해당 경로를 CPPUNIT_HOME이라고 함). Listing 1에 파일 구조가 표시된다.


Listing 1. CppUnit 설치 계층 구조
	
[arpan@tintin] echo $CPPUNIT_HOME
/home/arpan/ibm/cppUnit
[arpan@tintin] ls $CPPUNIT_HOME
bin  include  lib  man  share

CppUnit을 사용하는 테스트를 컴파일하려면 소스를 빌드해야 한다.

g++ <C/C++ file> -I$CPPUNIT_HOME/include –L$CPPUNIT_HOME/lib -lcppunit

CppUnit의 공유 라이브러리 버전을 사용하는 경우 소스를 컴파일하려면 –ldl 옵션을 사용해야 할 수 있다. 설치 후 libcppunit.so의 위치를 반영하려면 UNIX® 환경 변수 LD_LIBRARY_PATH를 수정해야 할 수도 있다.


CppUnit에 대해 살펴보는 데 가장 적절한 방법은 리프 레벨 테스트를 작성하는 것이다. CppUnit은 테스트 설계 중에 활용할 사전 정의된 클래스의 전체 호스트와 함께 제공된다. 연속성을 위해 이 시리즈의 part 1에서 설명한 설계가 부실한 문자열 클래스를 떠올려 보자(Listing 2 참조).


Listing 2. 창의적이지 않은 문자열 클래스
	
#ifndef _MYSTRING
#define _MYSTRING

class mystring { 
  char* buffer; 
  int length;
  public: 
    void setbuffer(char* s) { buffer = s; length = strlen(s); } 
    char& operator[ ] (const int index) { return buffer[index]; }
    int size( ) { return length; }
 }; 

#endif

일반적인 문자열 관련 검사에는 비어 있는 문자열의 크기가 0인지 확인하는 것과 오류 메시지/예외에 있는 문자열 결과에서 색인을 벗어나서 액세스하는 것 등이 포함된다. Listing 3에서는 이러한 테스트에 CppUnit을 사용한다.


Listing 3. 문자열 클래스에 대한 유닛 테스트
	
#include <cppunit/TestCase.h>
#include <cppunit/ui/text/TextTestRunner.h>

class mystringTest : public CppUnit::TestCase {
public:

  void runTest() {
    mystring s;
    CPPUNIT_ASSERT_MESSAGE("String Length Non-Zero", s.size() != 0);
  }
};

int main ()
{
  mystringTest test;
  CppUnit::TextTestRunner runner;
  runner.addTest(&test);

  runner.run();
  return 0;
}

살펴볼 CppUnit 코드 베이스의 첫 번째 클래스는 TestCase이다. 문자열 클래스에 대한 유닛 테스트를 작성하려면 CppUnit::TestCase 클래스를 서브클래스화하고 runTest 메소드를 대체해야 한다. 테스트가 정의되었으므로 개별 테스트를 추가해야 하는 제어기의 일종인 TextTestRunner 클래스를 인스턴스화한다(vide addTest 메소드). Listing 4에는 run 메소드의 출력이 표시된다.


Listing 4. Listing 3의 코드 출력
	
[arpan@tintin] ./a.out
!!!FAILURES!!!
Test Results:
Run:  1   Failures: 1   Errors: 0


1) test:  (F) line: 26 try.cc
assertion failed
- Expression: s.size() == 0
- String Length Non-Zero

가정이 적용되도록 CPPUNIT_ASSERT_MESSAGE 매크로에서 조건을 부정한다. Listing 5에는 조건이 s.size() ==0인 경우의 코드 출력이 표시된다.


Listing 5. 조건 s.size( ) == 0이 포함된 Listing 3의 코드 출력
	
[arpan@tintin] ./a.out

OK (1 tests)

TestRunner는 단일 테스트 또는 테스트 스위트를 실행하는 유일한 방법은 아니다. CppUnit은 대체 클래스 계층 구조—즉, 템플리트화된 TestCaller 클래스—를 제공하여 테스트를 실행한다. runTest 메소드 대신 TestCaller 클래스를 사용하여 모든 메소드를 실행할 수 있다. Listing 6에 간단한 예제가 제공된다.


Listing 6. TestCaller를 사용하여 테스트 실행하기
	
class ComplexNumberTest ... { 
  public: 
     void ComplexNumberTest::testEquality( ) { … } 
};

CppUnit::TestCaller<ComplexNumberTest> test( "testEquality", 
                                             &ComplexNumberTest::testEquality );
CppUnit::TestResult result;
test.run( &result );

위 예제에서 ComplexNumberText 유형의 클래스는 testEquality 메소드로 두 복소수의 동일성을 테스트하여 정의된다. TestCallerTestRunner의 경우와 마찬가지로 이 클래스를 사용하여 템플리트화되며 사용자는 테스트 실행을 위해 run 메소드에 대한 호출을 작성한다. TestCaller 클래스를 지금 상태대로 사용하는 것은 그다지 효율적이지 않다. TextTestRunner 클래스가 출력의 표시를 자동으로 처리한다. TestCaller의 경우 별도의 클래스를 사용하여 출력을 처리해야 한다. TestCaller 클래스를 사용하여 사용자 정의된 테스트 스위트를 정의하는 경우 기사 후반부에 이 유형의 코드 플로우가 표시된다.


CppUnit은 가장 일반적인 가정 시나리오에 대한 몇 가지 루틴을 제공한다. 이러한 루틴은 Asserter.h 헤더에서 사용할 수 있는 CppUnit::Asserter 클래스의 공용 정적 메소드로 정의된다. TestAssert.h 헤더에는 이러한 클래스 대부분에 대한 사전 정의된 매크로도 있다. Listing 2를 근거로 하여 CPPUNIT_ASSERT_MESSAGE는 다음과 같이 정의된다(Listing 7 참조).


Listing 7. CPPUNIT_ASSERT_MESSAGE의 정의
	
#define CPPUNIT_ASSERT_MESSAGE(message,condition)                          \
  ( CPPUNIT_NS::Asserter::failIf( !(condition),                            \
                                  CPPUNIT_NS::Message( "assertion failed", \
                                                       "Expression: "      \
                                                       #condition,         \
                                                       message ),          \
                                  CPPUNIT_SOURCELINE() ) )

가정의 근거가 되는 failIf 메소드의 선언이 Listing 8에 제공된다.


Listing 8. failIf 메소드의 선언
	
struct Asserter
{
…
  static void CPPUNIT_API failIf( bool shouldFail,
                                  const Message &message,
                                  const SourceLine &sourceLine = SourceLine() );
…
}

failIf 메소드의 조건이 True가 되면 예외가 발생한다. run 메소드는 이 프로세스를 내부적으로 처리한다. 하지만 흥미롭고 유용한 또다른 매크로인 CPPUNIT_ASSERT_DOUBLES_EQUAL이 있다. 이 매크로는 허용 값으로 두 double 간 동일성을 검사한다(따라서, |expected – actual | ≤ delta). Listing 9에는 매크로 정의가 제공된다.


Listing 9. CPPUNIT_ASSERT_DOUBLES_EQUAL 매크로 정의
	
void CPPUNIT_API assertDoubleEquals( double expected,
                                     double actual,
                                     double delta,
                                     SourceLine sourceLine,
                                     const std::string &message );
#define CPPUNIT_ASSERT_DOUBLES_EQUAL(expected,actual,delta)        \
  ( CPPUNIT_NS::assertDoubleEquals( (expected),            \
                                    (actual),              \
                                    (delta),               \
                                    CPPUNIT_SOURCELINE(),  \
                                    "" ) )


mystring 클래스의 여러 패싯을 테스트하는 한 가지 방법은 runTest 메소드 내에 검사를 계속 추가하는 것이다. 하지만 아주 단순한 클래스를 제외하고는 이 방법을 사용하면 관리가 어려워진다. 여기서 테스트 스위트를 정의하고 사용해야 한다. 문자열 클래스에 대한 테스트 스위트를 정의하는 Listing 10을 살펴본다.


Listing 10. 문자열 클래스에 대한 테스트 스위트 만들기
	
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TextTestRunner.h>
#include <cppunit/extensions/HelperMacros.h>

class mystringTest : public CppUnit::TestCase {
public:
  void checkLength() {
    mystring s;
    CPPUNIT_ASSERT_MESSAGE("String Length Non-Zero", s.size() == 0);
  }

  void checkValue() {
    mystring s;
    s.setbuffer("hello world!\n");
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Corrupt String Data", s[0], 'w');
  }

  CPPUNIT_TEST_SUITE( mystringTest );
  CPPUNIT_TEST( checkLength );
  CPPUNIT_TEST( checkValue );
  CPPUNIT_TEST_SUITE_END();
};

Listing 10은 매우 단순하다. CPPUNIT_TEST_SUITE 매크로를 사용하여 테스트 스위트를 정의한다. mystringTest 클래스의 일부인 개별 메소드는 테스트 스위트에서 유닛 테스트를 형성한다. 잠시 후에 이러한 매크로와 컨텐츠에 대해 살펴본다. 하지만 먼저 이 테스트 스위트를 사용하는 Listing 11의 클라이언트 코드를 살펴본다.


Listing 11. mystring 클래스에 대한 테스트 스위트를 사용하는 클라이언트 코드
	
CPPUNIT_TEST_SUITE_REGISTRATION ( mystringTest );

int main ()
{
  CppUnit::Test *test =
    CppUnit::TestFactoryRegistry::getRegistry().makeTest();
  CppUnit::TextTestRunner runner;
  runner.addTest(test);

  runner.run();
  return 0;
}

Listing 12에는 Listing 11에 있는 코드가 실행될 때의 출력이 표시된다.


Listing 12. Listings 10 및 11의 코드 출력
	
[arpan@tintin] ./a.out
!!!FAILURES!!!
Test Results:
Run:  2   Failures: 2   Errors: 0


1) test: mystringTest::checkLength (F) line: 26 str.cc
assertion failed
- Expression: s.size() == 0
- String Length Non-Zero


2) test: mystringTest::checkValue (F) line: 32 str.cc
equality assertion failed
- Expected: h
- Actual  : w
- Corrupt String Data

CPPUNIT_ASSERT_EQUAL_MESSAGE는 TestAssert.h 헤더에 정의되어 있으며 예상 인수와 실제 인수가 일치하는지 확인한다. 일치하지 않는 경우에는 지정된 메시지가 표시된다. HelperMacros.h에 정의된 CPPUNIT_TEST_SUITE 매크로는 테스트 스위트 작성 및 이 테스트 스위트에 대한 개별 테스트 추가를 단순화한다. 내부적으로 CppUnit::TestSuiteBuilderContext 유형의 템플리트화된 오브젝트가 작성되고(CppUnit 컨텍스트에 있는 테스트 스위트의 동등 항목임) CPPUNIT_TEST에 대한 각각의 호출은 해당 클래스 메소드를 이 스위트에 추가한다. 말할 필요도 없이 코드에 대한 유닛 테스트 역할을 하는 것은 클래스 메소드이다. 매크로의 순서를 기록한다. 코드가 개별 CPPUNIT_TEST 매크로를 컴파일하려면 CPPUNIT_TEST_SUITE 매크로와 CPPUNIT_TEST_SUITE_END 매크로 사이에 있어야 한다.


개발자는 시간이 흐를수록 코드에 기능을 계속 추가하기 때문에 추가적인 테스트가 필요하다. 동일한 테스트 스위트에 테스트를 계속 추가하면 시간이 흐를수록 더 복잡해지기 때문에 처음 테스트를 개발할 때 의도한 점진적인 변화의 의미를 잃게 된다. 다행히도 CppUnit에는 기존 테스트 스위트를 확장하는 데 사용할 수 있는 CPPUNIT_TEST_SUB_SUITE라는 유용한 매크로가 있다. Listing 13에서는 이 매크로를 사용한다.


Listing 13. 테스트 스위트 확장하기
	
class mystringTestNew : public mystringTest {
public:
  CPPUNIT_TEST_SUB_SUITE (mystringTestNew, mystringTest);
  CPPUNIT_TEST( someMoreChecks );
  CPPUNIT_TEST_SUITE_END();

  void someMoreChecks() {
    std::cout << "Some more checks...\n";
  }
};

CPPUNIT_TEST_SUITE_REGISTRATION ( mystringTestNew );

새 클래스 mystringTestNew는 이전 myStringTest 클래스에서 파생된다. CPPUNIT_TEST_SUB_SUITE 매크로는 새 클래스와 상위 클래스를 두 개의 인수로 승인한다. 클라이언트측에서는 두 클래스 모두를 등록하는 대신 새 클래스만 등록한다. 이것으로 충분하다. 구문의 나머지 부분은 테스트 스위트를 작성하는 경우와 거의 동일하다.


고정 기능(또는 CppUnit 컨텍스트의 TestFixture)은 개별 테스트에 대한 깔끔한 설정 및 종료 루틴을 제공하기 위한 것이다. 고정 기능을 사용하려면 CppUnit::TestFixture에서 테스트 킅래스를 파생하고 사전 정의된 setUptearDown 메소드를 대체한다. setUp 메소드는 유닛 테스트 실행 전에 호출되고 tearDown은 테스트가 실행될 때 호출된다. Listing 14에는 TestFixture를 사용하는 방법이 표시된다.


Listing 14. 테스트 고정 기능을 사용하여 테스트 스위트 만들기
	
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TextTestRunner.h>
#include <cppunit/extensions/HelperMacros.h>

class mystringTest : public CppUnit::TestFixture {
public:
  void setUp() { 
     std::cout << “Do some initialization here…\n”;
  }

  void tearDown() { 
      std::cout << “Cleanup actions post test execution…\n”;
  }

  void checkLength() {
    mystring s;
    CPPUNIT_ASSERT_MESSAGE("String Length Non-Zero", s.size() == 0);
  }

  void checkValue() {
    mystring s;
    s.setbuffer("hello world!\n");
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Corrupt String Data", s[0], 'w');
  }

  CPPUNIT_TEST_SUITE( mystringTest );
  CPPUNIT_TEST( checkLength );
  CPPUNIT_TEST( checkValue );
  CPPUNIT_TEST_SUITE_END();
};

Listing 15에는 Listing 14의 코드 출력이 표시된다.


Listing 15. Listing 14의 코드 출력
	
[arpan@tintin] ./a.out
. Do some initialization here…
FCleanup actions post test execution…
. Do some initialization here…
FCleanup actions post test execution…

!!!FAILURES!!!
Test Results:
Run:  2   Failures: 2   Errors: 0


1) test: mystringTest::checkLength (F) line: 26 str.cc
assertion failed
- Expression: s.size() == 0
- String Length Non-Zero


2) test: mystringTest::checkValue (F) line: 32 str.cc
equality assertion failed
- Expected: h
- Actual  : w
- Corrupt String Data

이 출력에서 볼 수 있듯이 유닛 테스트 실행당 한 번씩 설정 및 분석 루틴 메시지가 표시된다.


헬퍼 매크로를 사용하지 않고 테스트 스위트를 작성할 수 있다. 한 스타일을 다른 스타일 대신 사용해도 특별한 혜택은 없지만 비매크로 스타일 코딩을 사용하면 디버깅이 더 편하다. 매크로 없이 테스트 스위트를 작성하려면 CppUnit::TestSuite를 인스턴스화한 후 개별 테스트를 스위트에 추가한다. 마지막으로 run 메소드 호출 전에 스위트 자체를 CppUnit::TextTestRunner에 전달한다. Listing 16에서 알 수 있듯이 클라이언트측 코드는 거의 동일한 상태를 유지한다.


Listing 16. 헬퍼 매크로 없이 테스트 스위트 작성하기
	
int main ()
{
  CppUnit::TestSuite* suite = new CppUnit::TestSuite("mystringTest");
  suite->addTest(new CppUnit::TestCaller<mystringTest>("checkLength",
                &mystringTest::checkLength));
  suite->addTest(new CppUnit::TestCaller<mystringTest>("checkValue",
                &mystringTest::checkLength));

  // client code follows next 
  CppUnit::TextTestRunner runner;
  runner.addTest(suite);

  runner.run();
  return 0;
}

Listing 16의 내용을 이해하려면 CppUnit 네임스페이스의 두 클래스(TestSuite.h와 TestCaller.h에 각각 선언되어 있는 TestSuiteTestCaller)에 대해 이해해야 한다. runner.run() 호출이 실행되면 각 개별 runTest 메소드가 각각의 개별 TestCaller 오브젝트에 대한 CppUnit에 대해 내부적으로 호출된 후 TestCaller<mystringTest> 생성자에 전달된 것과 동일한 루틴을 호출한다. Listing 17에는 각 스위트에 대해 개별 테스트가 호출되는 방법에 대해 설명하는 CppUnit 소스의 코드가 표시된다.


Listing 17. 스위트에서 실행된 개별 테스트
	
void
TestComposite::doRunChildTests( TestResult *controller )
{
  int childCount = getChildTestCount();
  for ( int index =0; index < childCount; ++index )
  {
    if ( controller->shouldStop() )
      break;

    getChildTestAt( index )->run( controller );
  }
}

TestSuite 클래스는 CppUnit::TestComposite에서 파생된다.

CppUnit의 포인터 이해하기

CppUnit은 TestRunner 소멸자에 있는 TestSuite 포인터를 내부적으로 삭제하기 때문에 테스트 스위트가 힙에 선언되어 있는 것이 중요하다. 하지만 이것은 최적의 설계는 아니며 CppUnit 문서에서 명확하지 않다.


단일 조작에 TextTestRunner 오브젝트를 사용하여 여러 테스트 스위트를 작성한 후 실행할 수 있다. Listing 18에 표시된 대로 Listing 16에서와 같이 각 테스트 스위트를 작성한 후 동일한 addTest 메소드를 TextTestRunner에 추가하기만 하면 된다.


Listing 18. TextTestRunner를 사용하여 여러 스위트 실행하기
	
CppUnit::TestSuite* suite1 = new CppUnit::TestSuite("mystringTest");
suite1->addTest(…);
…
CppUnit::TestSuite* suite2 = new CppUnit::TestSuite("mymathTest");
…
suite2->addTest(…);
CppUnit::TextTestRunner runner;
runner.addTest(suite1);
runner.addTest(suite2);
…


지금까지는 테스트의 출력이 TextTestRunner 클래스에 의해 기본적으로 생성되었다. 하지만 CppUnit을 사용하면 출력에 사용자 정의 형식을 사용할 수 있다. 이를 위해 사용할 수 있는 클래스 중 하나는 CompilerOutputter.h 헤더에 선언된 CompilerOutputter이다. 무엇보다도 이 클래스를 사용하면 출력에 파일 이름 행 번호 정보를 표시하는 데 필요한 형식을 지정할 수 있다. 또한 로그를 화면에서 덤프하는 것과는 반대로 파일에 직접 저장할 수 있다. Listing 19에는 파일에 덤프되는 출력의 예제가 제공된다. %p:%l 형식을 살펴보자. 전자는 파일의 경로를 표시하고 후자는 행 번호를 표시한다. 이 형식을 사용하면 일반적으로 /home/arpan/work/str.cc:26과 같은 출력이 발생한다.


Listing 19. 사용자 정의된 형식을 사용하여 테스트 출력의 경로를 로그 파일로 재지정하기
	
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TextTestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/CompilerOutputter.h>

int main ()
{
  CppUnit::Test *test =
    CppUnit::TestFactoryRegistry::getRegistry().makeTest();
  CppUnit::TextTestRunner runner;
  runner.addTest(test);

  const std::string format("%p:%l");
  std::ofstream ofile;
  ofile.open("run.log");
  CppUnit::CompilerOutputter* outputter = new
    CppUnit::CompilerOutputter(&runner.result(), ofile);
  outputter->setLocationFormat(format);
  runner.setOutputter(outputter);

  runner.run();
  ofile.close();
  return 0;
}

CompilerOutputter에는 덤프하는 전반적인 정보의 서브세트를 가져오는 데 사용할 수 있는 printStatisticsprintFailureReport와 같은 다른 유용한 메소드의 호스트가 있다.


지금까지는 기본적으로 TextTestRunner를 사용하여 테스트를 실행해 왔다. TextTestRunner 유형의 오브젝트를 인스턴스화한 후 테스트 및 출력기를 추가한 다음 run 메소드를 호출하기만 하면 되었다. TestRunner(TextTestRunner의 수퍼클래스)와 리스너라는 클래스의 새 범주를 사용하여 지금 이 플로우를 벗어나 보자. 각각의 개별 테스트를 수행하는 데 걸리는 시간을 추적하려고 한다고 가정해 보자(성능 벤치마크를 수행하는 개발자에게 일반적으로 발생함). 자세히 설명하기 전에 Listing 20을 살펴보자. 이 코드는 TestListener에서 파생되는 세 가지 클래스(TestRunner, TestResultmyListener)를 사용한다. Listing 10에 있는 것과 동일한 mystringTest 클래스를 사용한다.


Listing 20. TestListener 클래스 살펴보기
	
class myListener : public CppUnit::TestListener {
public:
  void startTest(CppUnit::Test* test) {
    std::cout << "starting to measure time\n";
  }
  void endTest(CppUnit::Test* test) {
    std::cout << "done with measuring time\n";
  }
};

int main ()
{
  CppUnit::TestSuite* suite = new CppUnit::TestSuite("mystringTest");
  suite->addTest(new CppUnit::TestCaller<mystringTest>("checkLength",
                &mystringTest::checkLength));
  suite->addTest(new CppUnit::TestCaller<mystringTest>("checkValue",
                &mystringTest::checkLength));

  CppUnit::TestRunner runner;
  runner.addTest(suite);

  myListener listener;
  CppUnit::TestResult result;
  result.addListener(&listener);

  runner.run(result);
  return 0;
}

Listing 21에는 Listing 20의 출력이 표시된다.


Listing 21. Listing 20의 코드 출력
	
	[arpan@tintin] ./a.out
starting to measure time
done with measuring time
starting to measure time
done with measuring time

myListener 클래스는 CppUnit::TestListener에서 서브클래스화된다. startTestendTest 메소드를 적절하게 대체해야 하며 이러한 메소드는 각 테스트의 전과 후에 각각 실행된다. 이러한 메소드를 쉽게 확장하여 개별 테스트에 소요되는 시간을 확인할 수 있다. 그러니 이 기능을 설정/분석 루틴에 추가해 보자. 이 기능을 추가할 수 있지만 추가하게 되면 이는 각 테스트 스위트의 설정/분석 메소드에서 코드가 중복됨을 의미한다.

다음으로 run 메소드에서 TestResult 유형의 인수를 가져오는 TestRunner 클래스의 인스턴스인 실행 프로그램 오브젝트를 살펴본다. 리스너가 TestResult 오브젝트에 추가된다.

마지막으로 출력에 변화가 생겼는가? TextTestRunner에서는 run 메소드 뒤에 많은 정보가 표시되었지만 TestRunner에는 이러한 정보가 없다. 필요한 것은 테스트 실행 중에 리스너 오브젝트가 수집한 정보를 표시하는 출력기 오브젝트이다. Listing 22에는 Listing 20에서 변경해야 하는 항목이 표시된다.


Listing 22. 테스트 실행 정보를 표시하는 데 필요한 출력기 추가하기
	
runner.run(result);
CppUnit::CompilerOutputter outputter( &listener, std::cerr );
outputter.write();

하지만 잠시 기다리자. 이 방법도 코드를 컴파일하기에는 충분하지 않다. CompilerOutputter의 생성자는 TestResultCollector 유형의 오브젝트를 예상하며 TestResultCollectorTestListener(세부 사항은 CppUnit 클래스 계층 구조의 링크에 대한 참고자료 참조)에서 파생되기 때문에 TestResultCollector에서 myListener를 파생하기만 하면 된다. Listing 23에는 컴파일이 표시된다.


Listing 23. TestResultCollector에서 리스너 클래스 파생하기
	
class myListener : public CppUnit::TestResultCollector {
…
};

int main ()
{
  …

  myListener listener;
  CppUnit::TestResult result;
  result.addListener(&listener);

  runner.run(result);

  CppUnit::CompilerOutputter outputter( &listener, std::cerr );
  outputter.write();

  return 0;
}

출력이 Listing 24에 표시된다.


Listing 24. Listing 23의 코드 출력
	
[arpan@tintin] ./a.out
starting to measure time
done with measuring time
starting to measure time
done with measuring time
str.cc:31:Assertion
Test name: checkLength
assertion failed
- Expression: s.size() == 0
- String Length Non-Zero

str.cc:31:Assertion
Test name: checkValue
assertion failed
- Expression: s.size() == 0
- String Length Non-Zero

Failures !!!
Run: 0   Failure total: 2   Failures: 2   Errors: 0


이 기사에서는 CppUnit 프레임워크의 특정 클래스(TestResult, TestListener, TestRunner, CompilerOutputter 등)에 초점을 두었다. 독립형 유닛 테스트 프레임워크로서 CppUnit에는 제공할 수 있는 것이 더 많이 있다. XML 출력 생성을 위해 CppUnit에 클래스(XMLOutputter)가 있고 GUI 모드에 실행 중인 테스트(MFCTestRunnerQtTestRunner)가 있으며 플러그인 인터페이스(CppUnitTestPlugIn)도 있다. CppUnit 문서에서 클래스 계층 구조를 살펴보고 설치와 함께 제공되는 예제에서 자세한 내용을 살펴봐야 한다.


참고자료

교육

제품 및 기술 얻기

  • CppUnit 다운로드: CppUnit의 최신 버전을 다운로드하자.

  • IBM 제품 평가판: DB2®, Lotus®, Rational®, Tivoli® 및 WebSphere®의 애플리케이션 개발 도구 및 미들웨어 제품을 사용해 볼 수 있다.

토론

필자소개

Arpan은 EDA(Electronic Design Automation) 업계에 몸 담은 소프트웨어 개발자다. 솔라리스, 썬OS, HP-UX, 아이릭스 등과 같은 다양한 유닉스 기종은 물론 리눅스, 마이크로소프트 윈도우까지 다양한 플랫폼을 사용해 왔다. 그의 주된 관심사는 소프트웨어 성능 최적화 기법, 그래프 이론, 병렬 컴퓨팅이다. 기사를 집필하는 이유는 창의적인 욕구를 충족하기 위해서다. Sen은 소프트웨어 시스템 분야에서 대학원 학위를 받았다.

의견