CppUnit을 실무에 활용해 보자! 개발이야기

CppUnit을 요즘 실무에 사용하고 있는데요.
제가 주로 쓰는 방법에 대해서 한번 얘기해보고자 합니다.
CppUnit을 빌드하는 방법이나 기본적인 것들은 생략하고 합니다.
이미 인터넷에 많은 자료가 있으니까요

CppUnit을 접해보지 못하신 분들에게는 아래 링크를 추천드립니다.



우선 여기서 테스트할 클래스를 보겠습니다.
[-] Collapse
//
// 이 클래스를 테스트할 것 입니다.
// 매우 간단한 클래스이지만 실제 업무에서 로직을 처리하는 클래스라고 생각하고 응용하시면 됩니다.
//
class Calculator
{
public:
    Calculator(void);
    ~Calculator(void);

public:
    int add(int x, int y);
    int subtract(int x, int y);
    int divide(int x, int y);
    int multiply(int x, int y);
};

구현부는 안봐도 비디오니 생략하겠구요~

그럼 이 클래스를 어떻게 테스트하느냐?! 100번 설명하는 것보다 코드를 보는게 빠르죠? ㅋ
자 아래 코드를 보시죠~
[-] Collapse
#pragma once

#include <cppunit/extensions/HelperMacros.h>
#include "../MyProjectLib/Calculator.h"

//
// Calculator 클래스를 테스트합니다.
//
class CalculatorTest : public CPPUNIT_NS::TestFixture
{
    CPPUNIT_TEST_SUITE(CalculatorTest);
    CPPUNIT_TEST( testAdd );
    CPPUNIT_TEST( testSubTract );
    CPPUNIT_TEST( testDivide );
    CPPUNIT_TEST( testMultiply );
    CPPUNIT_TEST_SUITE_END();
public:
    CalculatorTest(void);
    ~CalculatorTest(void);

    // setUp / tearDown은 사용하지 않도라도 선언해야만 합니다.
    void setUp();
    void tearDown();

    //
    // 테스트할 메소드
    //
    void testAdd();
    void testSubTract();
    void testDivide();
    void testMultiply();
    // 테스트할 메소드앞에 test를 붙이는게 일반적인 관례입니다.

private:
    // 테스트할 클래스( 테스트용어로는 SUT(System Under Test)라고 합니다.)
    Calculator m_calc;
};


일반적으로 이러한 템플릿으로 테스트케이스를 만들면 됩니다~
저의 경우 하나의 메소드를 여러 테스트메소드를 만드는 경우는 드물었구요.
보통 하나의 메소드당 하나의 테스트메소드를 작성하였습니다.
에러처리되는 부분들까지 모두 테스트케이스로 만들어주면 좋겠지만 그렇게까지하기는 쉽지 않습니다.

    CPPUNIT_TEST_SUITE(CalculatorTest);
    CPPUNIT_TEST( testAdd );
    CPPUNIT_TEST( testSubTract );
    CPPUNIT_TEST( testDivide );
    CPPUNIT_TEST( testMultiply );
    CPPUNIT_TEST_SUITE_END();

이 부분을 통해 어떤게 테스트메소드인지를 알려줍니다. MFC의 메시지맵 매크로와 유사하죠? 
내부적으로 어떻게 동작되는지 공부하면 좋겠지만 일단은 몰라도 됩니다.
"위와 같은 형식으로 테스트메소드를 알려주는구나" 만 이해하면 OK 입니다.

그 다음 알아야 하는게 setUp / tearDown인데요.

 void setUp();
 void tearDown();

이 메소드는 템플릿을 통해 호출되므로 정의하지 않으면 오류가 발생됩니다. 사용하지 않더라도 정의해주셔야 되요~
그럼 이렇게 메소드를 구성하면 어떻게 동작이 되느냐? CalculatorTest 객체 하나가 생성되어 모든 메소드가 호출될까요?
그렇지 않습니다. 각각의 테스트메소드를 호출할때마다 객체가 생성됩니다. 
예를들어, testAdd 메소드를 호출하기 위해 setUp이 호출되고 testAdd가 호출되고 tearDown이 호출됩니다. 
그리고 그 테스트객체는 삭제되고 새로운 테스트객체가 생성되어 동일한 처리가 진행되는 겁니다.

이제 테스트클래스의 구현부를 보겠습니다.
[-] Collapse
#include "StdAfx.h"
#include "CalculatorTest.h"

// 아래 매크로를 통해 이름없는 TestSuite에 클래스가 등록됩니다.
CPPUNIT_TEST_SUITE_REGISTRATION( CalculatorTest );

CalculatorTest::CalculatorTest(void)
{
}

CalculatorTest::~CalculatorTest(void)
{
}


void CalculatorTest::setUp()
{

}

void CalculatorTest::tearDown()
{

}


void CalculatorTest::testAdd()
{
    CPPUNIT_ASSERT(m_calc.add(10, 10) == 20 );
}

void CalculatorTest::testSubTract()
{
    CPPUNIT_ASSERT(m_calc.subtract(10, 10) == 0 );
}

void CalculatorTest::testDivide()
{
    CPPUNIT_ASSERT(m_calc.divide(10, 10) == 0 );
}

void CalculatorTest::testMultiply()
{
    CPPUNIT_ASSERT(m_calc.multiply(10, 10) == 100 );
}

아래 매크로를 이해하는게 중요합니다. 이 매크로를 통해서 CalculatorTest를 이름없는 Test Suite에 등록이 되거든요.
이후에 볼 main 함수에서 등록된 Test Suite를 설명하게 될때 더 잘 이해하시게 될 겁니다.
CPPUNIT_TEST_SUITE_REGISTRATION( CalculatorTest );

다음은 어떻게 검증하느냐? 인데요.
CPPUNIT_ASSERT(m_calc.add(10, 10) == 20 );
위 매크로 조건문이 true이면 테스트가 성공하게 됩니다.
하나의 테스트메소드에 이 구문은 하나만 써야하는 것은 아닙니다. 
여러번 쓰셔도 되고 별도의 함수를 만들고 그 함수내에서 해당 구문을 써도 됩니다.

이제 위에서 작성한 테스트케이스클래스를 구동시키기 위한 코드를 보시죠~

[-] Collapse

#include "stdafx.h"

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


#ifdef _DEBUG
#pragma comment(lib, "cppunitd.lib")
#else
#pragma comment(lib, "cppunit.lib")
#endif


// 전체 test클래스를 테스트할지의 여부.
#define FULL_TEST

int _tmain(int argc, _TCHAR* argv[])
{
    // Adds the test to the list of test to run
    CPPUNIT_NS::TextUi::TestRunner runner;

#ifdef FULL_TEST
    // Get the top level suite from the registry
    CPPUNIT_NS::Test* suite = CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest();


    runner.addTest( suite );

    // Change the default outputter to a compiler error format outputter
    runner.setOutputter( new CPPUNIT_NS::CompilerOutputter( &runner.result(),
        CPPUNIT_NS::stdCOut() ));
#else
    // 특정 테스트클래스만 테스트하고 싶을때 여기처럼
    // 특정 테스트클래스만을 suite로 만들어서 사용합니다.
    CalculatorTest test;
    runner.addTest( test.suite() );

    // Change the default outputter to a compiler error format outputter
    runner.setOutputter( new CPPUNIT_NS::CompilerOutputter( &runner.result(),
        CPPUNIT_NS::stdCOut() ));
#endif

    // Run the test.
    bool wasSucessful = runner.run();

    // Return error code 1 if the one of test failed.
    return wasSucessful ? 0 : 1;
}

이 코드는 거의 템플릿형태로 쓰일 수 있습니다. 다른 테스트프로젝트에서도 거의 동일하게 사용하시면 됩니다.
이름없는 Test Suite에 클래스가 등록된다고 했었죠? 이게 어떻게 생성되느냐면 아래 코드를 통해서 수행됩니다.
 CPPUNIT_NS::Test* suite = CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest();
 runner.addTest( suite );
  // Change the default outputter to a compiler error format outputter
  runner.setOutputter( new CPPUNIT_NS::CompilerOutputter( &runner.result()CPPUNIT_NS::stdCOut() ));

Test 클래스의 suite 객체에 모든 이름없는 Test Suite 테스트클래스를 담겨집니다.
그리고 suite 객체는 runner 객체에 추가되고 runner 객체에 의해 테스트가 실행됩니다.
setOutputter 함수를 통해 테스트결과를 어디다 출력할 것인지를 지정합니다.

이제 테스트 실행결과를 보시죠.
jUnit처럼 빨간막대나 초록막대를 원하시면 GUI를 이용하시면 됩니다.
저는 콘솔을 선호하는 편인데요. 리눅스에서도 동일한 코드로 사용이 가능하거든요.

다음은 일부러 실패를 시켜서 실패된 결과입니다.
4개중에 1개가 실패되었다고 보이죠? 
Failures 는 CPPUNIT_ASSERT가 실패된 경우에 카운팅이 됩니다.
그런데 Errors 는 뭔지 궁금하죠? 이건 Exception 이 발생된 경우에 숫자가 올라가게 됩니다
Exception을 통해 예외를 처리하게 되면 CppUnit의 예외체크기능을 이용할 수 있게 되어서 더 활용도가 높아집니다.
CppUnit을 활용하실생각이라면 오류처리는 예외로 하시는걸 추천드리고 싶네요.

이로써 테스트케이스를 작성하고 구동시켜봤는데요.
Visual Studio의 프로젝트 구성은 어떻게 되어야 될어야 할까요?
실무 경험이 있으신 분들은 Dependencies를 이용하여 정적라이브러리를 손쉽게 사용하고 계실텐데요.
모르는 분을 위해 소개해드리려고 합니다.
프로젝트 구성은 아래와 같이 구성됩니다.
MyProjectLib에 테스트할 클래스가 들어가게 되며 정적라이브러리입니다.
MyProjectLibTest에서는 MyProjectLib를 정적으로 링크하여 테스트하게 됩니다.
MyProject역시 MyProjectLib를 정적링크하여 이용합니다.



이렇게 프로젝트를 구성하는 이유는 테스트코드를 배포버전에 포함시키지 않기 위함입니다.
MyProject는 테스트코드가 없는 순수한 라이브러리코드와 링크가 되기 때문에 테스트코드가 섞일 염려를 안해도 되거든요.

Visual Studio 의 Project -> Project Dependencies ... 메뉴를 통해 아래와 같이 설정합니다.
이렇게 해놓고 빌드를 하면 별도로 lib파일을 지정해주지 않아도 알아서 링크가 일어나게 됩니다.
단위테스트를 작성함으로써 얻어지는 이점이 상당히 많은데요. 직접해보면서 느껴보시면 어떨까요?

마지막으로 여기서 테스트했던 테스트프로젝트소스를 첨부합니다.  CppUnit_Sample.zip


 



덧글

  • 감자농부 2013/04/04 16:45 # 삭제 답글

    정보 감사합니다...^^
  • 박진용 2013/04/18 23:48 #

    답글이 늦었지만.. 댓글 감사합니다^^
  • 이분좀짱인듯 2017/05/14 02:37 # 삭제 답글

    크... 프로젝트 나누는 법까지 설명해주시니 최고네요.
    sin 솔루션에 프로젝트 여러개를 띄울 수 있다는걸 예제파일 보고 처음 알았네요.
    이제 CPPUNIT 설치 방법을 찾아 떠나야겠네요.
    좋은 주말 되시길!
댓글 입력 영역