この記事は、
C++ Advent Calendar 2012: 17日目の記事になります。
お題は「Google Test ユーザーが Boost.Test を使ってみた」です。
(2012/12/27:
補足記事を書きました。)
これまで、C++ の testing framework には Google Test を使ってきたのですが、
この機会に
Boost.Test に挑戦したいと思います。
今年2月に行われた「Boost.勉強会 #8 大阪」の参加報告で Boost.Test 使うぜ!っと意気込んでおいて今更かという感じではありますが・・・
では、なぜ今まで使わなかったのかというと
- boost の導入がめんどくさそう
- 日本語情報が少ない
- Google Test が使いやすかった
と、いう勝手なイメージがあったからです。最後のが一番大きな理由でした。
でも、他のフレームワークのことも知らずに「Google Test がいいよ!」というのもあれですよね。
それでは、そろそろ本題に入ります。
以下は、今回この記事を書くにあたって参考にさせて頂いたところです。
はじめに
実は、そもそも boost を使ったことありません。
というわけで、まずは boost のセットアップからしました。
開発環境は、Visual Studio 2012 Express 2012 for Windows Desktop です。
boost のバージョンは 1.52.0 です。
~~~略~~~
思っていた以上に簡単にできました!
(本題ではないので
別記事にしました。)
include オンリーでも使えた
boost をビルドしたんですが、実は
ヘッダオンリーでも使えました。
ヘッダオンリーだと気軽に試せるので良いですね!
では、まずはヘッダーオンリーでテストを書いてみましょう。
簡単な例
まずは、簡単なテストから見ていきたいと思います。
#define BOOST_TEST_MAIN // main関数を定義
#include <boost/test/included/unit_test.hpp>
BOOST_AUTO_TEST_SUITE(sample)
BOOST_AUTO_TEST_CASE(hoge)
{
BOOST_CHECK_EQUAL(2*2, 4);
}
BOOST_AUTO_TEST_CASE(fuga)
{
BOOST_CHECK_EQUAL(2*3, 6);
}
BOOST_AUTO_TEST_SUITE_END()
この辺は特に問題ないですね。
BOOST_AUTO_TEST_SUITE でグループ化できて、BOOST_AUTO_TEST_CASE でテストの中身が書けます。
書いたテストは Boost.Test が自動に検出して実行してくれます。
ちなみに、BOOST_AUTO_TEST_SUITE は必須ではありません。
Google Test と比較してみました。
Google Test
//
TEST(sample, hoge)
{
EXPECT_EQ(2*2, 4);
}
TEST(sample, fuga)
{
EXPECT_EQ(2*3, 6);
}
//
Boost.Test
BOOST_AUTO_TEST_SUITE(sample)
BOOST_AUTO_TEST_CASE(hoge)
{
BOOST_CHECK_EQUAL(2*2, 4);
}
BOOST_AUTO_TEST_CASE(fuga)
{
BOOST_CHECK_EQUAL(2*3, 6);
}
BOOST_AUTO_TEST_SUITE_END()
テストスイートが Google Test における TestCase に、
テストケースが Test(Info) にあたる感じです。
アサーション
値が等しいか検証する BOOST_CHECK_EQUAL など基本的なアサーションはそろっています。
どんなアサーションがあるかは、Boost.Test の
リファレンスを参照してください。
(BOOST_CHECK_EQUAL_COLLECTIONS は Google Test にはないアサーションでイイなぁーと思いました。)
また、 Boost.Test のアサーションにはレベルが3種類あって、
REQUIRE,CHECK,WARN が使えます。これらのレベルのことを Boost.Test ではフレーバー(flavor)といいます。
Google Test | Boost.Test | エラーカウント | テストの実行 |
ASSERT | REQUIRE | 増加 | 中断 |
EXPECT | CHECK | 増加 | 継続 |
--- | WARN | そのまま | 継続 |
(※ Boost.Test の中断は例外を使った
テストの中断、Google Test は return 文での
テスト関数の中断)
Google Test には WARN フレーバーがありませんが、コードを加えることで対応可能です。
詳しくはこちらを参考にしてください。
ブログズミ - Google Test で Boost.Test の WARN flavor を実現する
あと、大事な点を注意書きで済ませてしまってますが、まぁそこは気をつけてくださいってことで。
グループ化
先の説明で Boost.Test のテストスイートが Google Test の TestCase にあたると書きましたが、Boost.Test では namespace のように BOOST_AUTO_TEST_SUITE を複数ネストして使えます。
これは
Google Test ではできないことです。
BOOST_AUTO_TEST_SUITE(sample)
BOOST_AUTO_TEST_SUITE(group1)
BOOST_AUTO_TEST_CASE(hoge)
{
BOOST_CHECK_EQUAL(2*2, 4);
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()
テストフィクスチャ
Boost.Test でもテストフィクスチャが使用できます。
テストフィクスチャはグローバル、テストスイート、テストケースの3箇所で設置できます。
テストフィクスチャの役割は、主に
- テスト開始前のセットアップ
- テストに関する特定の状態の提供
- テスト終了後の後片付け
の3つです。
この辺は Google Test と変わりませんが、テストフィクスチャの構成や挙動に違いがあるので下記サンプルで説明していきます。
struct Fixture1 {
Fixture1() : fix1(1) { ::std::cout << "F1 ->" << ::std::endl; }
~Fixture1() { ::std::cout << "<- F1" << ::std::endl; }
int fix1;
};
struct Fixture2 {
Fixture2() : fix2(2) { ::std::cout << "F2 ->" << ::std::endl; }
~Fixture2() { ::std::cout << "<- F2" << ::std::endl; }
int fix2;
};
struct Fixture3 {
Fixture3() : fix3(3) { ::std::cout << "F3 ->" << ::std::endl; }
~Fixture3() { ::std::cout << "<- F3" << ::std::endl; }
int fix3;
};
BOOST_GLOBAL_FIXTURE(Fixture1);
BOOST_FIXTURE_TEST_SUITE(sample, Fixture2)
BOOST_FIXTURE_TEST_CASE(hoge, Fixture3)
{
// BOOST_CHECK_EQUAL(1, fix1); // グローバルはできない?
// BOOST_CHECK_EQUAL(2, fix2); // Fixture2 は適応されないみたい
BOOST_CHECK_EQUAL(3, fix3); // フィクスチャメンバーにアクセスできる
}
BOOST_AUTO_TEST_CASE(fuga)
{
// BOOST_CHECK_EQUAL(1, fix1);
BOOST_CHECK_EQUAL(2, fix2);
++fix2;
}
BOOST_AUTO_TEST_CASE(bar)
{
// BOOST_CHECK_EQUAL(1, fix1);
BOOST_CHECK_EQUAL(2, fix2);
++fix2;
}
BOOST_AUTO_TEST_SUITE_END()
まず、挙動に関してです。
テストケースでは、テストフィクスチャの状態(メンバー)にアクセスすることができます。
しかし、複数のテストフィクスチャは適応されないようで、BOOST_FIXTURE_TEST_CASE(hoge, Fixture3) では Fixture2 が実行されないようです。
当然、Fixture2 の状態にもアクセスできません。
また、fuga, bar のテストフィクスチャである Fixture2 もテストケース毎に生成/削除されるようです。
Google Test ユーザーとしては、最初「?」となりました。
この辺は実際の出力をご覧ください。
(※ --log_level=test_suite で実行)
続いて、構成です。
| Google Test | Boost.Test |
グローバル | ::testing::Environment | BOOST_GLOBAL_FIXTURE |
テストスイート毎
(テストケース毎) | ::testing::Test::SetUpTestCase,
::testing::Test::TearDownTestCase | |
テストケース毎
(テスト毎) | ::testing::Test::SetUp,
::testing::Test::TearDown | BOOST_FIXTURE_TEST_SUITE
BOOST_FIXTURE_TEST_CASE |
(※ ()書きは Google Test の場合)
(※ SetUp/TearDown に着目した表です。)
Boost.Test の場合、どこでも同じテストフィクスチャを使えますが、
Google Test の場合は、それぞれ書き方が違います。
同じテストフィクスチャでも Google Test と Boost.Test で大分異なりますね。
型をパラメータ化したテスト
複数の型に対して、同様のテストを書きたい場合の方法です。
(
Boost.Test Example)
typedef boost::mpl::list<int,long,unsigned char> test_types;
BOOST_AUTO_TEST_CASE_TEMPLATE( my_test, T, test_types )
{
BOOST_CHECK_EQUAL( sizeof(T), (unsigned)4 );
}
型リストには boost::mpl::list を使います。
この型リストの分だけテストが実行され、型は第二引数に指定した名前で参照します。
実行結果がこちら。
(※ --log_level=test_suite で実行)
型リストの分だけテストが実行されていることがわかると思います。
このように template 関数やクラスのテストを書くにあたって、型のパラメータ化テストが使えるとテストを非常に簡潔に書くことができます。
値をパラメータ化したテスト
Boost.Test に値のパラメータ化テストはない?
あって不思議じゃないんですが・・・リファレンス眺めた感じだと見あたらなかったです。
2012/12/20 追記:私の目が節穴だったようです。すみません。m(__)m
値のパラメータ化ありました。
リファレンスは
こちらです。
実行時オプション
Boost.Test でもコマンドライン引数でテストの挙動を実行時に変更できます。
(※ 環境変数でも可能。詳しくはリファレンスを見てください。)
| Google Test | Boost.Test |
テストのリスト表示 | gtest_list_tests | |
テストの選択 | gtest_filter=[filter] | run_test=[filter] |
無効テストの実行 | gtest_also_run_disabled_tests | |
シャッフルテスト | gtest_shuffle | random=[0|seed] |
乱数シード | gtest_random_seed=[seed] | random=[seed] |
繰り返しテスト | gtest_repeat=[count] | |
失敗時ブレークポイント停止 | gtest_break_on_failure | |
失敗時例外throw | gtest_throw_on_failure | |
ログレベル | | log_level=[level] |
レポートレベル | | report_level=[no|confirm|short|detailed] |
ログフォーマット | (gtest_output=xml[:path]) | output_format=[HRF|XML]
report_format=[HRF|XML] |
色付き出力 | gtest_color=[yes|no|auto] | |
テスト時間出力 | gtest_print_time=[0|1|auto] | |
プログレス表示 | | show_progress=[yes|no] |
? | | auto_start_dbg=[yes|no] |
ビルド情報の出力 | | build_info=[yes|no] |
? | | catch_system_errors=[yes|no] |
framework での例外キャッチ | gtest_catch_exceptions=[0|1] | |
メモリリーク検出 | | detect_memory_leak=[0|1|value > 1] |
? | | detect_fp_exceptions=[yes|no] |
? | | use_alt_stack=[yes|no] |
終了コード | | result_code=[yes|no] |
ヘルプ | help | help |
(※ 各オプションの前に "--" が必要です。)
ぱっと見ただけでも、Google Test と Boost.Test でできることが違うのがわかります。
テストの選択や
シャッフルテストはどちらも使えます。これは欠かせない機能ですね。
使い方がよくわからなかったオプションについては「?」としています。これは Boost.Test をもっと使っていけば分かるようになるんでしょう(きっと)。
Boost.Test ではログの出力レベルを細かく設定できます。その詳細です。
--log_level=test_suite はテストスイートの情報が表示されます。↑のサンプル画像を撮る際に使いました。
Value | 内容 |
all | すべて表示 |
success | all と同じ |
test_suite | TestSuite のメッセージを表示 |
message | ユーザーメッセージを表示 |
warning | 警告レポートを表示 |
error | エラーレポートを表示 |
cpp_exception | キャッチされなかった例外を表示 |
system_error | 致命的でないシステムエラーの表示 |
fatal_error | 致命的なエラーの表示 |
nothing | なにも表示しない |
(※ 太字がデフォルト設定になります。)
Google Test の場合はログレベルの概念はありませんが、イベントリスナーをユーザーが定義することで独自の出力をさせることができます。
カスタムイベントリスナーの例はこちらを参照してください。
ブログズミ - Google Test で成功時のメッセージを非表示にする方法
テストレポート(XML)
Boost.Test の結果も Jenkins に集計させることができます!
個人的にこれがあるかないかで評価が大分変わってきます。
Google Test とは少し設定方法が違うので簡単に紹介をします。
Boost.Test で XML を出力するには、「--output_format=XML」オプションでできました。
また、Jenkins でのレポートの集計には、
xUnit Plugin を使用します。
Jenkins の設定は
Wiki を参考にしてください。
1点だけ注意。
--output_format=XML --log_level=all --report_level=no
Jenkins で集計してもらうためには、ログレベルなどのオプションも必要になります。
集計した結果はこんな感じで見れます。
イイ感じ!
ライブラリ版を使ってみる
ここまでヘッダーオンリーでテストを書いてきましたが、実運用で Boost.Test を使用する場合は
Dynamic Library か Static Library を使うことをオススメします。ヘッダーオンリーでは、
ソースファイルの分割ができないのと、
コンパイルに時間がかかるからです。
ヘッダーオンリーからライブラリを使うように変更するのは簡単です。
まず、インクルードするヘッダーファイルを、
boost/test/included/unit_test.hpp から
boost/test/unit_test.hpp に変更します。
次に、[ライブラリディレクトリ]にビルドした boost の lib ファイルのあるパスを追加します。
リンクは unit_test.hpp でしてくれるので、あとは普通にビルドするだけです。
せっかくなので、ビルド時間を比較してみました。
struct Fixture1 {
Fixture1() : fix1(1) { ::std::cout << "F1 ->" << ::std::endl; }
~Fixture1() { ::std::cout << "<- F1" << ::std::endl; }
int fix1;
};
BOOST_AUTO_TEST_SUITE(GROUPNAME)
BOOST_AUTO_TEST_SUITE(a)
BOOST_AUTO_TEST_CASE(X)
{
BOOST_CHECK_EQUAL(2*3, 6);
}
BOOST_AUTO_TEST_CASE(Y)
{
BOOST_CHECK_EQUAL(2*3, 6);
}
BOOST_FIXTURE_TEST_CASE(Z, Fixture1)
{
BOOST_CHECK_EQUAL(2*3, 6);
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_FIXTURE_TEST_SUITE(b, Fixture1)
BOOST_AUTO_TEST_CASE(X)
{
BOOST_CHECK_EQUAL(2*3, 6);
}
BOOST_AUTO_TEST_CASE(Y)
{
BOOST_CHECK_EQUAL(2*3, 6);
}
BOOST_AUTO_TEST_SUITE_END()
typedef boost::mpl::list<int,long,unsigned char> test_types;
BOOST_AUTO_TEST_CASE_TEMPLATE( my_test, T, test_types )
{
BOOST_CHECK_EQUAL( sizeof(T), (unsigned)4 );
}
BOOST_AUTO_TEST_SUITE_END()
上記コード x N個 (プリプロセッサで増やした) ものを
Windows7, メモリ: 4GB, CPU: Intel Core i3 2.53GHz, Visual Studio 2012 Express 2012 for Windows Desktop
でビルドしました。
結果
N | インクルード | ライブラリ |
10 | 00:00:16.88 | 00:00:05.44 |
100 | 00:00:20.33 | 00:00:11.00 |
1000 | 00:04:04.31 | 00:03:52.23 |
5000 | error C1060:
ヒープの領域を使い果たしました。
error C1001:
コンパイラで内部エラーが発生しました。 | 02:04:23.22 |
10000 | error C1060:
ヒープの領域を使い果たしました。
error C1001:
コンパイラで内部エラーが発生しました。 |
(※ /bigobj オプション付き)
テスト数に関係なくライブラリを使った方が、10秒ちょっと早い結果になりました。
もっと差が出るものかと思ったのですが…検証コードが良くなかったのか?
まとめ
まだまだ Boost.Test を説明しきれていないですが、
冒頭の Boost.Test を使わなかった理由がどのように変わったかで、まとめにしたいと思います。
- boost の導入がめんどくさそう
とても簡単に導入できました。
ヘッダオンリーでも使えるので、試しに使いたい人はダウンロードするだけですぐに使えます。
- 日本語情報が少ない
この記事を書いているときに検索した感じでは、少なくはなかったです。
ただ、もっと「ここがスゴイ!便利だ!」って感じの記事があるといいかもしれませんね。
- Google Test が使いやすかった
個人的にはやっぱり Google Test がいいなと思いました。
理由としては値のパラメータ化テストとテストの繰り返し実行、失敗時ブレークポイント停止がないのが大きいです。特にテストデータをランダム生成して --gtest_repeat=-1 --gtest_break_on_failure で失敗するまでエイジングする使い方をよくするので、それができないのが残念です。
最後に
なんだかまとまりのないブログになってしまいましたが、最後までお付き合い頂きありがとうございました。
内容に間違いなどありましたら、コメント下さい(^^ゞ
最後に宣伝。
オレオレテスティングフレームワーク開発中です!
Boost.Test を使ってみていいなと思った機能は自作テスティングフレームワークの
iutest に取り込んでいきたいと思います(^^
そして本日、
iutest v1.2.0 をリリースしました。
C++ Advent Calendar 2012: 次は、
krustf さんで「
Cer に知って欲しい C++」です。 →「
krustf の雑記」
See ya!