2008年09月29日
TopCoderの為に少しやる気になってきたところで、Macでフリーで使える C++ のテストフレームワークをいくつか試してみたのでメモ。
- CppUnit - C++ Port of JUnit
- CxxTest
- googletest - Google C++ Testing Framework
- Boost.Test
ファースト・インプレッション
- CppUnitはテストの記述が若干面倒な気が。表示はシンプルで悪くない。
- CxxTestはインストール方法が他と違って少し悩んだが、記述量が少なくて取っつきやすかった。
- googletestは記述量が少なめで、赤と緑のカラー表示コンソールで、マクロの種類も豊富。ASSERT マクロと EXPECT マクロの対応も分かりやすい。但し、出たばかりで日本語での情報が少ない。
- Boost.Testは普段Boostに慣れ親しんでいるなら良いかも。マクロの種類は多め。
とりあえず、googletestを使ってみようと思う。使い込んだらまた違った感想をもつかもしれない。
CppUnit - C++ Port of JUnit
- 記述量ほんのちょっと多めか
- 表示は簡潔
- CppUnit @ SourceForge.net
- Making assertions
- C++アプリケーションの効率的なテスト手法(CppUnit編)/επιστημη
- CppUnit 入門
- CppUnit cookbook
テストの題材に使ったのは、指定したデリミタ(charもしくはstring)で文字列を分割し vector<string> を返す自作関数 split など。- 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 )
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
- ※自分で好きなところにインストール
テストコード例 (splittest_cxxtest.h):- 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 )
#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
- 最近出たらしい
- xUnitベース
- 記述量は少なめ
- マクロの種類が豊富
- コンソールでも赤緑で表示されるのは嬉しい
- googletest - Google Code
- Google C++ Testing Framework Primer
- Google C++ Testing Framework Advanced Guide
テストコード例:- 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
参考:
- Exploring the C++ Unit Testing Framework Jungle - Games from Within
ここに挙げられていてあと試してないのは CppUnitLite, NanoCppUnit, Unit++ の3つ。 googletestはここで "Ideal Framework" として挙げられている記法にかなり近い。 - C/C++ unit testing tools - opensourcetesting.org
(21:39)
トラックバックURL
この記事へのコメント
1. Posted by あまのりょー 2008年09月30日 00:31
TUTってのもありますよ。
http://tut-framework.sourceforge.net/
http://tut-framework.sourceforge.net/
2. Posted by naoya_t 2008年09月30日 16:51
あまのさん情報どうも有難うございます。TUTも見てみます。


