UnitTest 프레임웍을 슬슬 바꿀 시점인가?

회사에서 C++로 작성하는 프로그램의 이런저런 유닛테스팅 용도로 UnitTest++을 사용해왔다. 굉장히 간결한 라이브러리이고 (header file 몇 개 + 라이브러리 하나), 전체 테스트 스윗이나 테스트 케이스 설정도 다음과 같은 형태라 무척 간단한다.

SUITE( SomeSuite )
{
    TEST( Test1 )
    {
          CHECK( … );
     }

    …
}

그래서 이 UnitTest++을 꽤나 편하게 쓰고 있었는데 굉장히 맘에 안드는 점이 몇 가지 있다.

테스트 케이스를 선별적으로 실행하기 힘들다 / 테스트 실행 인터페이스가 기본적으론 제공되지 않는다

기본적으로 제공되는 외부 인터페이스가 1갠데, 테스트를 실행하는 함수인 UnitTest::RunAllTest() 함수가 그 것이고 다음과 같은 인자를 받는다.

  • TestReporter : console 출력할지 특정 형태(XML이나 텍스트…)로 출력할지 결정함
  • Test-case들의 목록
  • 실행할 suite 이름
  • 실행할 test-case 이름

을 인자로 받고, 대부분의 경우 NULL이어도 된다. 

일부만 실행하려면 suite을 주거나 test-case 이름을 줘야하는데, 이러면 테스트가 전체적으로 잘 짜여있는 경우는 쉬운데 그렇지 않은 경우엔 참 뭣 같다(…). 게다가 저걸 커맨드라인이나 TestRunner 류에서 어떻게 전달할지도 따로 구현되어있질 않다.

내 경우엔 boost::program_options를 써서 *nix 스러운 커맨드라인 해석[1] 을 하게 했는데, 일단 짜서 유지해야하는게 좀 귀찮더라. 라이브러리 말고 딴걸 추가로 들고다녀야 된다는 점에서 -_-

VisualUnitTest++ 이라고  VisualStudio 2005/2008과 통합되는 TestRunner가 있다는게 다행이지만, 커맨드라인 인터페이스가 없다는 점은 정말 감점 요인(…).

근데 최근에 다른 UnitTest 프레임웍을 살펴보다가 google test를 써보게 되었는데 이 녀석은 기본적으로 커맨드 라인 파서가 포함되어 있다. 게다가 환경변수로 지정하는 것도 가능해서 수고가 많이 줄어든다. 덤으로 미운놈은 뭘 해도 밉다고 UnitTest++의 XML 출력은 사람이 읽으란게 아니다(한줄로 나오냐 orz)

 

테스트 케이스의 흐름(control-flow)를 조절하기가 힘들다

덤으로 테스트 케이스 쓸 때 제일 짜증나는게, assert를 함부로 쓸 순 없으니(유닛 테스트 자체가 멈추니까), 검사는 하되 그 검사가 실패하면 해당 테스트 케이스 전체를 실패로 처리하는 기능이 자주 필요했는데, 그런게 없다. 

UnitTest++에는 값 검사 매크로가 CHECK(), CHECK_EQUAL() 등등이 있는데, 값 검사가 실패(테스트 케이스가 잘못된)인 경우에도 무조건 실행된다. 이건 사실 당연한거지만, 특정한 경우 — 예를 들어 특정 파일을 여는데 이 파일이 없어서 그 이하의 테스트가 무의미한 경우 등등 — 에는 이게 좀 짜증난다. 테스트 케이스에서 내가 예외 처리도 하고 있어야 겠니? Orz

google test의 경우 값 검사 매크로가 ASSERT_* 계열과 EXPECT_* 계열로 나뉜다. ASSERT_* 류의 경우 해당 값 검사가 실패하면 그 테스트 케이스를 실패로 간주하고 다음 테스트 케이스로 넘어간다. EXPECT_* 류는 UnitTest++의 동작(실패하건 말건 다음 값 검사로 넘어감)과 같다.

즉, 어떤 파일을 연다고 치자. ASSERT_TRUE( some_fstream.good() ); 을 쓴다면 파일을 못 열면 해당 테스트 케이스는 실패다. 그리고 그 테스트의 다른 검사는 실행되지 않는다. 반대로 EXPECT_TRUE( some_fstream.good() ) 이라면 파일이 안 열려도 테스트는 속행하겠단 얘기다… UnitTest++ 에선 이런 실행 흐름의 조절이 불가능하고, 이는 일종의 스트레스 — 특히 저런 테스트 이후에 뭔가 실행되면 assertion-failure가 일어나는 경우 등등 — 로 다가올 뿐.

CruiseControl 과 통합하는 문제도 별 어려움이 없을듯하니, 이대로면 조만간 UnitTest++ 을 버리고 google test로 넘어가지 않을까 한다. 결정적으로 문서화의 질/양도 후자가 앞섬에야[2]

ps. gtest는 페도라 등 주류 *nix 계열 OS에 팩키지로 제공되기도 하고(…), 덤으로 google mock framework이란 훌륭한 물건도 있다.

  1. –help 가 들어오면 help-message를, –ouput or -o 가 들어오면 파일 출력의 이름을 받게 했다. 비슷하게 –suite/-s 로 스윗 이름을, –testcase/-t로 테스트 케이스를 받는다. getopt 류의 해석을 해주는 boost 라이브러리가 있다는 점에 ㄳ []
  2. UnitTest++이 매우 단순한 라이브러리인 탓도 있는데, 단 한 장의 문서로 모든걸 설명하고 있다. google test의 경우 기본문서/상세문서 두 가지가 꽤 자세한 설명/예와 합쳐져 있다. []

Author: rein

나는 ...

4 thoughts on “UnitTest 프레임웍을 슬슬 바꿀 시점인가?”

  1. 팀에서 쓰고 있는 디버그 관련 라이브러리가, ASSERT에 콜백을 등록해서 실행 단위마다 ASSERT 실패 동작을 다르게 할 수 있게 되어 있다. (M모 게임에서 물려받은 유산이지)

    테스트 프로젝트에서는 ASSERT(false) 걸리면 throw 하게 하니 UnitTest++도 쓸만했음 (…)

  2. 아니 그러면 되긴하는데 (실제로 그런 류의 매크로를 만들기도 하고), 왠지 이런식으로 계속 고치는게 좀 피곤해서 -_-;;

    그냥 잘 만들어진 라이브러리도 있는데 내가 고치려니 억울하더라고(?)

  3. 적절한 Cpp용 UnitTest를 찾고 있는 중인데 구글 테스트를 한 번 써봐야 겠군요. 처음 접해서 그런 건지, Cpp용 Unit이 원래 그런건지 다른 모듈들에 비해 추가적인 작업을 해줘야 하는 것들이 많네요(혹은 Visual Studio를 써서 그런건지)

  4. 감성코드 / 한 줄 요약하자면 Reflection이 없어서(…) 좀 그렇죠.

    Java/C# 혹은 python 등등의 언어에선 해당 클래스에 무슨 메서드가 있는지 runtime에 확인할 수 있지만 C++에선 그런게 *적어도 쉽게는* 안됩니다. (Java나 C#이면 특정 클래스를 던져주고 거기에 있는 test??? 메서드를 다 실행한다! 하는 식이 쉽지만 C++에선 그렇게 하긴 힘드니)

    그래서 (일단 코드상으론 바로 보이지 않는) 각종 static 변수를 동원하거나, 매크로를 써서 자동으로 등록되게 해놓거나 해서 그렇습니다.

Leave a Reply