2008年09月29日

TopCoderの為に少しやる気になってきたところで、Macでフリーで使える C++ のテストフレームワークをいくつか試してみたのでメモ。

ファースト・インプレッション

  • CppUnitはテストの記述が若干面倒な気が。表示はシンプルで悪くない。
  • CxxTestはインストール方法が他と違って少し悩んだが、記述量が少なくて取っつきやすかった。
  • googletestは記述量が少なめで、赤と緑のカラー表示コンソールで、マクロの種類も豊富。ASSERT マクロと EXPECT マクロの対応も分かりやすい。但し、出たばかりで日本語での情報が少ない。
  • Boost.Testは普段Boostに慣れ親しんでいるなら良いかも。マクロの種類は多め。

とりあえず、googletestを使ってみようと思う。使い込んだらまた違った感想をもつかもしれない。


CppUnit - C++ Port of JUnit

使用可能なマクロ:
- CPPUNIT_ASSERT( condition )
- CPPUNIT_ASSERT_MESSAGE( message, condition )
- CPPUNIT_FAIL( message )
- CPPUNIT_ASSERT_EQUAL( expected, actual )
- CPPUNIT_ASSERT_EQUAL_MESSAGE( message, expected, actual )
- CPPUNIT_ASSERT_DOUBLES_EQUAL( expected, actual, delta )
- CPPUNIT_ASSERT_THROW( expression, ExceptionType )
- CPPUNIT_ASSERT_NO_THROW( expression )
- CPPUNIT_ASSERT_ASSERTION_FAIL( assertion )
- CPPUNIT_ASSERT_ASSERTION_PASS( assertion )
テストの題材に使ったのは、指定したデリミタ(charもしくはstring)で文字列を分割し vector<string> を返す自作関数 split など。
vector<string> split(string str, string delim);
vector<string> split(string str, int delim=' ');
vector<int> map_atoi(vector<string> nums);
テストコード例:
#include <cppunit/extensions/HelperMacros.h>
class SplitTest : public CppUnit::TestFixture {
  CPPUNIT_TEST_SUITE( SplitTest );
  CPPUNIT_TEST( test_split1 );
  CPPUNIT_TEST( test_split2 );
  CPPUNIT_TEST( test_map_atoi );
  CPPUNIT_TEST( test_split_atoi );
  CPPUNIT_TEST_SUITE_END();

public:
  void setUp() {}
  void tearDown() {}

public:
  void test_split1() {
    vector<string> result;

    result = split("", "<>");
    CPPUNIT_ASSERT( result.size() == 0 );
    // CPPUNIT_ASSERT_EQUAL( (size_t)0, result.size() );

    // "a","<>" => ["a"]
    result = split("a", "<>");
    CPPUNIT_ASSERT( result.size() == 1 );
    // CPPUNIT_ASSERT_EQUAL( (size_t)1, result.size() );
    CPPUNIT_ASSERT( result[0] == "a" );
    // CPPUNIT_ASSERT_EQUAL( (string)"a", result[0] );

    // "a<>b<>c","<>" => ["a","b","c"]
    result = split("a<>b<>c", "<>");
    CPPUNIT_ASSERT( result.size() == 3 );
    CPPUNIT_ASSERT( result[0] == "a" );
    CPPUNIT_ASSERT( result[1] == "b" );
    CPPUNIT_ASSERT( result[2] == "c" );
    ...
    ...
  }

  ...
  ...
};

#include <cppunit/TextTestRunner.h>

int main(int argc, char *argv[])
{
  CPPUNIT_TEST_SUITE_REGISTRATION( SplitTest );

  CppUnit::TextTestRunner runner;

  runner.addTest( SplitTest::suite() );
  runner.run("", false);
  return 0;
}

//% g++ -o splittest splittest_cppunit.cc -lcppunit
CPPUNIT_ASSERT_EQUALの使い勝手がよくない。

テスト実行時の表示例。テストではエラーが出るようにテストに1カ所ずつ細工してある。(以下同じ)
.F...


!!!FAILURES!!!
Test Results:
Run:  4   Failures: 1   Errors: 0


1) test: SplitTest::test_split1 (F) line: 23 splittest_cppunit.cc
assertion failed
- Expression: result.size() != 0

CxxTest

  • 記述量は少なめ
  • Test suiteクラスを定義したら、そこからPerl(もしくはPython)スクリプトで test runner を生成
  • X11とかQtとか・・・(Qt 4.4.3をダウンロードしてきたらCxxが対応しているのはver3までらしいのでQt4に対応すべく書き換えるなど少し深入りしていた件は割愛)
  • CxxTest User's Guide
  • ※自分で好きなところにインストール
使用可能なマクロ:
- TS_FAIL( message )
- TS_ASSERT( expr )
- TS_ASSERT_EQUALS( x, y )
- TS_ASSERT_SAME_DATA( x, y, size )
- TS_ASSERT_DELTA( x, y, d )
- TS_ASSERT_DIFFERS( x, y )
- TS_ASSERT_LESS_THAN( x, y )
- TS_ASSERT_LESS_THAN_EQUALS( x, y )
- TS_ASSERT_PREDICATE( R, x )
- TS_ASSERT_RELATION( R, x, y )
- TS_ASSERT_THROWS( expr, type )
- TS_ASSERT_THROWS_EQUALS( expr, arg, x, y )
- TS_ASSERT_THROWS_ASSERT( expr, arg, assertion )
- TS_ASSERT_THROWS_ANYTHING( expr )
- TS_ASSERT_THROWS_NOTHING( expr )
- TS_TRACE( message )
テストコード例 (splittest_cxxtest.h):
#include <cxxtest/TestSuite.h>

class splittest_cxxtest : public CxxTest::TestSuite
{
 public:
  void setUp() {}
  void tearDown() {}

  void testSplit1( void )
  {
    vector<string> result;
	
    result = split("", "<>");
    TS_ASSERT_EQUALS( 10, result.size() );
	
    // "a","<>" => ["a"]
    result = split("a", "<>");
    TS_ASSERT_EQUALS( 1, result.size() );
    TS_ASSERT_EQUALS( "a", result[0] );
	
    // "a<>b<>c","<>" => ["a","b","c"]
    result = split("a<>b<>c", "<>");
    TS_ASSERT_EQUALS( 3, result.size() );
    TS_ASSERT_EQUALS( "a", result[0] );
    TS_ASSERT_EQUALS( "b", result[1] );
    TS_ASSERT_EQUALS( "c", result[2] );
  }
  ...
  ...
};
// main() 不要
//
//% cxxtestgen.pl --error-printer -o splittest_cxxtest.cpp splittest_cxxtest.h
//% g++ -I/usr/local/cxxtest -o splittest splittest_cxxtest.cpp
テスト実行時の表示例:
Running 4 tests
In splittest_cxxtest::testSplit1:
splittest_cxxtest.h:17: Error: Expected (1 == result.size()), found (1 != 0)
...
Failed 1 of 4 tests
Success rate: 75%

googletest - Google C++ Testing Framework

使用可能なマクロ:
- ASSERT_TRUE( condition ) / EXPECT_TRUE( condition )
- ASSERT_FALSE( condition ) / EXPECT_FALSE( condition )

- ASSERT_EQ( expected, actual ) / EXPECT_EQ( expected, actual )
- ASSERT_NE( val1, val2 ) / EXPECT_NE( val1, val2 )
- ASSERT_LT( val1, val2 ) / EXPECT_LT( val1, val2 )
- ASSERT_LE( val1, val2 ) / EXPECT_LE( val1, val2 )
- ASSERT_GT( val1, val2 ) / EXPECT_GT( val1, val2 )
- ASSERT_GE( val1, val2 ) / EXPECT_GE( val1, val2 )

- ASSERT_STREQ( expected_str, actual_str ) / EXPECT_STREQ( expected_str, actual_str )
- ASSERT_STRNE( str1, str2 ) / EXPECT_STRNE( str1, str2 )
- ASSERT_STRCASEEQ( expected_str, actual_str ) / EXPECT_STRCASEEQ( expected_str, actual_str )
- ASSERT_STRCASENE( str1, str2 ) / EXPECT_STRCASENE( str1, str2 )

- SUCCEED()
- FAIL()
- ADD_FAILURE()

- ASSERT_THROW( statement, exception_type ) / EXPECT_THROW( statement, exception_type )
- ASSERT_ANY_THROW( statement ) / EXPECT_ANY_THROW( statement )
- ASSERT_NO_THROW( statement ) / EXPECT_NO_THROW( statement )

- ASSERT_PRED1( pred1, val1 ) / EXPECT_PRED1( pred1, val1 )
- ASSERT_PRED2( pred2, val1, val2 ) / EXPECT_PRED2( pred2, val1, val2 )
- ... arity=5まで

- ASSERT_PRED_FORMAT1( pred_format1, val1 ) / EXPECT_PRED_FORMAT1( pred_format1, val1 )
- ASSERT_PRED_FORMAT2( pred_format2, val1, val2 ) / EXPECT_PRED_FORMAT2( pred_format2, val1, val2 )
- ...

- ASSERT_FLOAT_EQ( expected, actual ) / EXPECT_FLOAT_EQ( expected, actual )
- ASSERT_DOUBLE_EQ( expected, actual ) / EXPECT_DOUBLE_EQ( expected, actual )
// "almost equal" ... the two values are within 4 ULP's from each other.
- ASSERT_NEAR( val1, val2, abs_error ) / EXPECT_NEAR( val1, val2, abs_error )

- ASSERT_HRESULT_SUCCEEDED( expression ) / EXPECT_HRESULT_SUCCEEDED( expression )
- ASSERT_HRESULT_FAILED( expression) / EXPECT_HRESULT_FAILED( expression )

- ASSERT_DEATH( statement, regex` ) / EXPECT_DEATH( statement, regex` )
- ASSERT_EXIT( statement, predicate, regex` ) / EXPECT_EXIT( statement, predicate, regex` )

- SCOPED_TRACE( message )

- EXPECT_FATAL_FAILURE( statement, substring )
- EXPECT_NONFATAL_FAILURE( statement, substring )
テストコード例:
#include <gtest/gtest.h>

// テストケースを単体の関数として実装する
TEST(SplitTest, Split1)
{
  vector<string> result;

  result = split("", "<>");
  EXPECT_EQ( 1, result.size() );
  
  // "a","<>" => ["a"]
  result = split("a", "<>");
  EXPECT_EQ( 1, result.size() );
  EXPECT_EQ( "a", result[0] );
  
  // "a<>b<>c","<>" => ["a","b","c"]
  result = split("a<>b<>c", "<>");
  EXPECT_EQ( 3, result.size() );
  EXPECT_EQ( "a", result[0] );
  EXPECT_EQ( "b", result[1] );
  EXPECT_EQ( "c", result[2] );
  ...
}  

...
...

int main(int argc, char** argv)
{
  testing::InitGoogleTest(&argc, argv);

  return RUN_ALL_TESTS();
}
//% g++ -o splittest -lgtest splittest_google.cc
テスト実行時の表示例:
[==========] Running 4 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 4 tests from SplitTest
[ RUN      ] SplitTest.Split1
splittest_google.cc:29: Failure
Value of: result.size()
  Actual: 0
Expected: 1
[  FAILED  ] SplitTest.Split1
[ RUN      ] SplitTest.Split2
[       OK ] SplitTest.Split2
[ RUN      ] SplitTest.MapAtoi
[       OK ] SplitTest.MapAtoi
[ RUN      ] SplitTest.SplitAtoi
[       OK ] SplitTest.SplitAtoi
[----------] Global test environment tear-down
[==========] 4 tests from 1 test case ran.
[  PASSED  ] 3 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] SplitTest.Split1

 1 FAILED TEST
色がついてるのがなんとなく嬉しい。

Boost.Test

  • 記述量は少なめ
  • Boostテストライブラリ
  • (Boostが古いやつしか入っていなかったのでついでにインストールした)
使用可能なマクロ:
- BOOST_CHECKPOINT( message )
- BOOST_WARN( condition )
- BOOST_CHECK( condition )
- BOOST_CHECK_EQUAL( x, y )
- BOOST_CHECK_CLOSE( x, y, tolerance )
- BOOST_REQUIRE( condition )
- BOOST_MESSAGE( message )
- BOOST_WARN_MESSAGE( condition, message )
- BOOST_CHECK_MESSAGE( condition, message )
- BOOST_REQUIRE_MESSAGE( condition, message )
- BOOST_CHECK_PREDICATE( predicate, number_of_arguments, arguments_list )
- BOOST_REQUIRE_PREDICATE( prediate, number_of_arguments, arguments_list )
- BOOST_ERROR( message )
- BOOST_FAIL( message )
- BOOST_CHECK_THROW( statement, exception )
- BOOST_CHECK_EQUAL_COLLECTIONS( left_begin, left_end, right_begin )
- BOOST_IS_DEFINED( symbol )
テストコード例:
#include <boost/test/unit_test.hpp>
using namespace boost::unit_test_framework;

// テストケースを単体の関数として実装する
void test_split1()
{
  vector<string> result;

  result = split("", "<>");
  BOOST_CHECK_EQUAL( 0, result.size() );
  
  // "a","<>" => ["a"]
  result = split("a", "<>");
  BOOST_CHECK_EQUAL( 1, result.size() );
  BOOST_CHECK_EQUAL( "a", result[0] );
  
  // "a<>b<>c","<>" => ["a","b","c"]
  result = split("a<>b<>c", "<>");
  BOOST_CHECK_EQUAL( 3, result.size() );
  BOOST_CHECK_EQUAL( "a", result[0] );
  BOOST_CHECK_EQUAL( "b", result[1] );
  BOOST_CHECK_EQUAL( "c", result[2] );
  ...  
}

...
...

// main() は不要
//% g++ -o splittest splittest_boost.cc /usr/local/lib/libboost_unit_test_framework-xgcc40-mt.a
テスト実行時の表示例:
Running 4 test cases...
splittest_boost.cc(13): error in "test_split1": check result.size() == 1 failed

*** 1 failure detected in test suite "Master Test Suite"
make: *** [boost] Error 201

参考:

(21:39)

トラックバックURL

この記事へのコメント

1. Posted by あまのりょー   2008年09月30日 00:31
TUTってのもありますよ。
http://tut-framework.sourceforge.net/
2. Posted by naoya_t   2008年09月30日 16:51
あまのさん情報どうも有難うございます。TUTも見てみます。

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔