ラベル boost の投稿を表示しています。 すべての投稿を表示
ラベル boost の投稿を表示しています。 すべての投稿を表示

2015年12月16日水曜日

Boost.Test v3 を使ってみた

この記事は 初心者 C++er Advent Calendar 2015 16 日目の記事です。
<15日目: matsuokah さん「C++初心者ならビルドはBazelでラクしちゃいましょう
>17日目:yumetodo さん「C99からC++14を駆け抜けるC++講座

アドベントカレンダー用に書いてたものじゃないんですが、空いていたので滑り込みました。



Boost 1.59.0がリリースされました - Faith and Brave - C++で遊ぼう

Boost 1.59.0 で Boost.Test v3 になってます。
リンク先にも書かれているように、主な変更点は BOOST_TEST マクロと Data driven test 対応でしょう。
今回はこの2つを試してみました。

Windows + Visual Studio な環境で Boost を即座に使う方法
Boost の Nuget パッケージが公開されているので、それを使います。
NuGet Gallery | boost 1.59.0
既にやり方がまとめてられています。こちらのほうが画像もあって参考になると思います。



BOOST_TEST マクロ
Catch のアサーションのように、式の各値を出力してくれる機能がついたアサーションです。
Catch に関しては以前のブログを参考にしてください。

使い方はこんな感じで
#define BOOST_TEST_MODULE example
#include "boost/test/unit_test.hpp"

int f() { return 42; }

BOOST_AUTO_TEST_CASE(test_op_reportings)
{
 int a = 43, b = 42, c = 0;
 BOOST_TEST(a == b);
 BOOST_TEST(f() == a);
 BOOST_TEST(a < b);
 BOOST_TEST(a - 1 < b);
 BOOST_TEST(b > a - 1);

 BOOST_TEST((f() && c));
}

こんなふうに出力されます。

|| や && は () で囲う必要があります。( () の中身は展開できないようです。)

仕組みとしては、Catch と同様に operator ->* を利用したもののようです。
これはかなり便利な機能が追加されましたね!

Data-driven test
Google Test の値のパラメータ化テストのことです。
Boost.Test でも BOOST_PARAM_TEST_CASE を使うことで出来ていました。これは 2012年のアドベントカレンダーの補足(ブログズミ: Google Test ユーザーが Boost.Test を使ってみた (補足))で書きました。
ちょっと面倒くささがあったのですが、v3 になって Data-driven test に置き換えられました。

基本
まずは簡単な例から
#define BOOST_TEST_MODULE example
#include <boost/test/unit_test.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/test/data/monomorphic.hpp>
#include <iostream>

namespace bdata = boost::unit_test::data;

BOOST_DATA_TEST_CASE(test1, bdata::xrange(5))
{
    std::cout << "test 1: " << sample << std::endl;
    BOOST_TEST((sample <= 4 && sample >= 0));
}
BOOST_DATA_TEST_CASE(test2, bdata::xrange(5), var)
{
    std::cout << "test 2: " << var << std::endl;
    BOOST_TEST((var <= 4 && var >= 0));
}

BOOST_DATA_TEST_CASE の第一引数がテスト名、第二引数がデータセットです。
この例では、0~4 の値でテストが実行されます。
パラメータは何も指定しなければ、「sample」変数で取得できます。(test1)
BOOST_DATA_TEST_CASE マクロの第三引数以降に名前を指定すると、指定した名前で取得できます。(test2)

データセット生成器
データセット生成器は、xrange, random, make の3つが提供されています。 Datasets generators - 1.59.0
xrange は任意の範囲の値を、random はランダムな値を、make は任意の値を指定することができます。

以下は make の例
BOOST_DATA_TEST_CASE(test3, bdata::make(1))
{
    std::cout << "test 3: " << sample << std::endl;
}

struct Hoge
{
    int x, y;
};

//::std::ostream& operator << (::std::ostream& os, const Hoge& hoge)
//{
//  return os << "X:" << hoge.x << " Y:" << hoge.y;
//}

BOOST_TEST_DONT_PRINT_LOG_VALUE(Hoge);

const char* a[] = { "ABC", "DEF", "G", "H" };
Hoge x[4] = { {0,1}, {2,3}, {4,5}, {6,7} };

BOOST_DATA_TEST_CASE(test4, bdata::make(a), v)
{
    std::cout << "test 4: " << a << << std::endl;
}
BOOST_DATA_TEST_CASE(test5, bdata::make(x), v)
{
    std::cout << "test 5: " << v.x << ", " << v.y << std::endl;
}

test3 では単一の値、test4, test5 では配列からデータセットを作成しています。
構造体などを使用する場合は、ostream への operator << を定義するか BOOST_TEST_DONT_PRINT_LOG_VALUE を使う必要があります。


続いて random の例ですが、下記コードだと実行時に例外が投げられます。
BOOST_DATA_TEST_CASE(test6, bdata::random(0, 100))
{
    std::cout << "test 6: " << sample << std::endl;
}
random は要素数の定義がないためです。
random を使うにはデータセットの組み合わせが必要になります。

データセットの組み合わせ
データセットは複数設定することができます。 Operations on dataset - 1.59.0
BOOST_DATA_TEST_CASE の第三引数以降にデータセットを受け取る変数名をパラメータ数分指定してください。

Zips
まずは、random の例として Zips を使います。
BOOST_DATA_TEST_CASE(test7, bdata::xrange(5) ^ bdata::random(0, 100), i, sample)
{
    std::cout << "test 7: " << i << ": " << sample << std::endl;
}

'^' が Zips のオペレーターです。2つのデータセットをペアにします。
上記の例では、0~4 のインデックスと乱数がパラメータとして得られます。


複数のデータセットをまとめることができますが、有限のデータセットの場合は要素数を揃える必要があるので注意。
BOOST_DATA_TEST_CASE(test8, bdata::xrange(5) ^ bdata::xrange(10, 15) ^ bdata::xrange(100, 110, 2)
    , var1, var2, var3)
{
    std::cout << "test 8: " << var1 << ", " << var2 << ", " << var3 << std::endl;
}



Grid (Cartesian products)
次に、データセットから直積のパラメータを生成します。
これは Google Test の Combine と同じ機能になります。
BOOST_DATA_TEST_CASE(test9, bdata::xrange(5) * bdata::xrange(5), var1, var2)
{
    std::cout << "test 9: " << var1 << ", " << var2 << std::endl;
}

'*'Grid (Cartesian products) のオペレーターです。
2つのデータセットのすべての組み合わせがパラメータとして得られます。

Joins
最後になりましたが、データセットは連結することもできます。
BOOST_DATA_TEST_CASE(test10, bdata::xrange(5) + bdata::xrange(10, 15))
{
    std::cout << "test 10: " << sample << std::endl;
}

'+'Joins のオペレーターです。
上記例では 0~4 と 10~14 がパラメータとして得られます。


最後にこれらの組み合わせを使うことで、より複雑なデータセットを作ることもできます。
BOOST_DATA_TEST_CASE(test11, (bdata::xrange(5) + bdata::xrange(10, 15)) ^ bdata::random(0,100), var1, var2)
{
    std::cout << "test 11: " << var1 << ", " << var2 << std::endl;
}


まとめ
Boost.Test v3 になってすごく便利に、そして書きやすくなってました。
個人的にはオペレーターで組み合わせ作れるのがいいなーと思ったので、あとは使う機会があればって感じです。
もし、Boost を業務で使うことがあれば、テスティングフレームワークは Boost.Test を使おうと思います。

2013年6月29日土曜日

「Boost.勉強会 #12 大阪」に参加してきました

Boost.勉強会 #12 大阪」に参加しました。
今回は、会社の同僚が何人か参加するらしいのでぼっち飯しなくて済みそうです。

それでは行ってきます。


というわけで行ってきました。
各セッションの資料は Boost.勉強会のページにまとまっていますので、そちらからご覧ください。
簡単ではありますが、感想を書きました。

C++で作るWEBアプリケーション
C++で作るWEBアプリケーション: @fadis_

Emscriptien を使うと C++ から JavaScript に変換できるとのこと。
OpenGL (ES) や OpenAL が使えて、ファイルシステムもあるそうで、Unreal Engine が動いたとか。
普通にゲーム作れそうですね。

自分が(仕事で)で使うことはないかと思うが、
自作プログラムでも変換してみますか。

C++初心者のためのBoost.MPL入門
C++初心者のためのBoost.MPL入門: @suibaka

高校生ですごいなってのが一番の感想。
Boost をほとんど使ったことなく、当然 MPL も知らないので、参考になりました。
ただ、ちょっと説明が分かりにくかったかなぁ。

boost::multiprecisionと適応型無誤差演算
boost::multiprecisionと適応型無誤差演算: @oniprog

これまた自分とは関わりの薄い分野でしたが、勉強になりました。
無誤差演算がこの先いつか必要になるかもしれないので覚えておきたいです。

constexpr中3女子テクニック―実践と濫用そしてC++14へ
constexpr中3女子テクニック―実践と濫用そしてC++14へ : @bolero_MURAKAMI

1時間半超…ぶっちぎりの constexpr でした。
長いなぁとは思ってましたが、そんなにオーバーしていたとは(^^
constexpr は使ってみた程度にしかやってないのですが、
C++14 で constexpr の制限も緩くなるので、楽しみです。

iutest でも新しい C++ の機能は積極的に取り込んでいるので、
constexpr でも何かできないかなぁと考えてます。
まぁ、14 も 1y も気になりますが、個人的には VC++ が気になります。VS2013…

Allocators@C++11
Allocators@C++11: @Cryolite

ネタが満載でとてもおもしろかったですw
以上!

…ではなく、色々なテクニックが紹介されていてとても勉強になりました。
特に、pointer 型のカスタマイズや Scoped Allocator Model が気になりました。

実は一番実践的なセッションだったのではないでしょうか。
しかし、これが C++03 でできないのは残念です。。。

君はまだ、本当のプリプロセスを知らない
君はまだ、本当のプリプロセスを知らない: @decimalbloat

去年の Boost.勉強会 #8 大阪 ではついていくのがやっとな感じでしたが、
去年のスライドを読み、 魔クロ 力を上げたおかげか、
今回は話がちゃんと理解できました。

私の魔クロ力はともかく、今回はとても分かりやすい発表だったと思います。
ちょっと難しいなぁと思った方は、こちらのスライドを合わせて読むことをオススメします。
[Boost.勉強会 #8 大阪 - プリプロセッサこわい]

C++コミュニティを作る
C++コミュニティを作る: @cpp_akira

最後は @cpp_akira 氏のセッション。
今まで Twitter やブログ経由でしか知らない、よく分からないけどスゴイ人という認識でしたが、
今回のお話でどのような経歴を経て、どのような考え方を持っているのか分かり、改めてスゴイ人だなと思いました。

今の自分にできること
とりあえず、できるところからということで、Google Test の Google Group に「[gtest] ASSERT_* マクロで void 以外を return できるようにする」のバッチを送ってみました。
(送ってみたけど反応なくて、チンプンカンなことしてないかとても不安…)

また、このブログは週1回は更新していますので、こちらは継続していきたいです。

最後に
今回も充実した一日でした!
(遠方の会場だと厳しいですが)また、Boost.勉強会に参加したいです。

2012年12月27日木曜日

Google Test ユーザーが Boost.Test を使ってみた (補足)

この記事は、C++ Advent Calendar 2012: 17日目「Google Test ユーザーが Boost.Test を使ってみた」の補足記事になります。

本編では書ききれなかったことや、訂正なども含めて補足をしたいと思います。

値をパラメータ化したテスト
Boost.Test でも使えます。リファレンスはこちらです。

(Boost.Test Example)
#include <boost/test/included/unit_test.hpp>
#include <boost/test/parameterized_test.hpp>
using namespace boost::unit_test;

//____________________________________________________________________________//

void free_test_function( int i )
{
    BOOST_CHECK( i < 4 /* test assertion */ );
}

//____________________________________________________________________________//

test_suite*
init_unit_test_suite( int argc, char* argv[] )
{
    int params[] = { 1, 2, 3, 4, 5 };

    framework::master_test_suite().
        add( BOOST_PARAM_TEST_CASE( &free_test_function, params, params+5 ) );

    return 0;
}

//____________________________________________________________________________//

こちらですが、自動でテストケースを認識・実行してくれる AUTO マクロがありません。
そのため、明示的に add する必要があります。

それはちょっと面倒臭い

ということで、簡単にマクロを用意してみました。
#include <boost/test/included/unit_test.hpp>
#include <boost/test/parameterized_test.hpp>
 
#define BOOST_AUTO_TEST_CASE_PARAM( function, begin, end )    \
BOOST_AUTO_TU_REGISTRAR( BOOST_JOIN(registrar, __LINE__)  )(  \
    BOOST_PARAM_TEST_CASE(function, begin, end) )
 
void free_test_function( int i )
{
    BOOST_CHECK( i < 4 /* test assertion */ );
}
 
int params[] = { 1, 2, 3, 4, 5 };
BOOST_AUTO_TEST_CASE_PARAM( &free_test_function, params, params+5 );
(ideone)



テストの中断
Boost.Test では REQUIRE 、Google Test では ASSERT フレーバーが失敗時にテストの中断をします。
ただ、両者でこの中断の挙動が違うので補足をしておきます。

Boost.Test
void SubFunc(void)
{
    BOOST_REQUIRE_EQUAL(0, 1);
    BOOST_REQUIRE_EQUAL(1, 2);
}

BOOST_AUTO_TEST_CASE(Sub)
{
    SubFunc();
    BOOST_REQUIRE_EQUAL(2, 3);
}
Google Test
void SubFunc(void)
{
    ASSERT_EQ(0, 1);
    ASSERT_EQ(1, 2);
}

TEST(Test, Sub)
{
    SubFunc();
    ASSERT_EQ(2, 3);
}
どちらも、アサーションを記述したサブ関数をテストから呼び出しています。
さて、ここで SubFunc(); 後のアサーションに到達するかが、ここでのお話です。
実際の出力を見てみましょう。



Boost.Test
Google Test
ご覧のように、Boost.Test は最初のアサーションの失敗で中断されていますが、Google Test は ASSERT_EQ(2,3); まで到達しています。
これは Google Test の中断が return 文を使った仕組みになっているからです。
なぜこのような実装になっているかは、こちらをご覧ください。

もし、SubFunc 内で失敗したときに、以降の処理を中断したい場合は以下のように書くことができます。
void SubFunc(void)
{
    ASSERT_EQ(0, 1);
    ASSERT_EQ(1, 2);
}

TEST(Test, Sub)
{
    ASSERT_NO_FATAL_FAILURE( SubFunc() );
    ASSERT_EQ(2, 3);
}
ASSERT_NO_FATAL_FAILURE(statement); は、statement で失敗があったかどうかを検証するアサーションです。


このように、SubFunc の失敗によりテストが中断します。

テスト以外での利用
Boost.Test や Google Test などテスティングフレームワークのアサーションは、ログに詳細な情報が出力されるので、普通の assert よりもとても有益な検証になります。
通常、テストの検証として使用するこれらアサーションを、テスト以外の場所でも使おうという話です。

Boost.Test の場合
#define BOOST_TEST_NO_MAIN
#include <boost/test/included/unit_test.hpp>

int main(int , char**)
{
    BOOST_CHECK_EQUAL(2, 3);
    BOOST_REQUIRE_EQUAL(1, 2);
    return 0;
}

特にログも出ずに死亡。うーん、残念。

Google Test の場合
#include <gtest/gtest.h>

void f(void)
{
    ASSERT_EQ(1, 2);
}

int main(int, char**)
{
    EXPECT_EQ(2, 3);
    f();
    return 0;
}


ログが出力されて正常終了しました。
また、GTEST_FLAG マクロを使って失敗時ブレークポイントを有効にすれば、より使い勝手が上がります。
int main(int, char**)
{
    ::testing::GTEST_FLAG(break_on_failure) = true; // break point を有効にする
    EXPECT_EQ(2, 3);
    f();
    return 0;
}

終わり
ちょっとしたことでしたが、補足はこれで終わりです。
また機会があればテスティングフレームワークについて書きたいと思います。

それでは、また来年!

2012年12月17日月曜日

Google Test ユーザーが Boost.Test を使ってみた

この記事は、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 TestBoost.Testエラーカウントテストの実行
ASSERTREQUIRE増加中断
EXPECTCHECK増加継続
---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 TestBoost.Test
グローバル::testing::EnvironmentBOOST_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 TestBoost.Test
テストのリスト表示gtest_list_tests
テストの選択gtest_filter=[filter]run_test=[filter]
無効テストの実行gtest_also_run_disabled_tests
シャッフルテストgtest_shufflerandom=[0|seed]
乱数シードgtest_random_seed=[seed]random=[seed]
繰り返しテストgtest_repeat=[count]
失敗時ブレークポイント停止gtest_break_on_failure
失敗時例外throwgtest_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]
ヘルプhelphelp
(※ 各オプションの前に "--" が必要です。)

ぱっと見ただけでも、Google Test と Boost.Test でできることが違うのがわかります。
テストの選択シャッフルテストはどちらも使えます。これは欠かせない機能ですね。
使い方がよくわからなかったオプションについては「?」としています。これは Boost.Test をもっと使っていけば分かるようになるんでしょう(きっと)。

Boost.Test ではログの出力レベルを細かく設定できます。その詳細です。
--log_level=test_suite はテストスイートの情報が表示されます。↑のサンプル画像を撮る際に使いました。
Value内容
allすべて表示
successall と同じ
test_suiteTestSuite のメッセージを表示
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インクルードライブラリ
1000:00:16.8800:00:05.44
10000:00:20.3300:00:11.00
100000:04:04.3100:03:52.23
5000error C1060:
ヒープの領域を使い果たしました。
error C1001:
コンパイラで内部エラーが発生しました。
02:04:23.22
10000error 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!

Visual Studio Express 2012 for Windows Desktop で boost をビルド

自分用備忘録です。

ビルド済みバイナリという選択
BoostPro Binary Installer for Visual C++
こちらからビルド済みバイナリが取得できるので、まずはこちらを確認してもいいのかも。

ダウンロード
Boost 公式からダウンロードします。
今回は、boost 1.52.0 をダウンロードしました。

ダウンロードしたら任意の場所に展開します。

ビルド
  1. [スタートメニュー]->[Microsoft Visual Studio 2012]->[Visual Studio Tools]->[VS2012 の開発者コマンド プロンプト] を実行する
  2. 展開したフォルダに移動する
  3. 「bootstrap.bat && b2.exe」と入力し実行
  4. 待つ
  5. 完了

リンク
ビルドしたライブラリは、 /stage/lib/ 以下に出力されます。
プロジェクトのプロパティのから、 /stage/lib を[追加のライブラリ ディレクトリ]に追加します。
また、[追加のインクルード ディレクトリ]に boost のパスを追加しておきます。

あとは、普通に使うだけです。


boostjp に書いてあった
ここまで書いて気づいたけど、boostjp に書いてありました f(^^;

2012年2月18日土曜日

Google Test で Boost.Test の WARN flavor を実現する

Boost.Test には flavors というものがあり、アサーションのレベルがそれぞれ違います。
もちろん、Google Test にも同等のものが存在します。

エラー:ならない
テストの実行:継続
エラー:なる
テストの実行:継続
エラー:なる
テストの実行:中断
Google Test---EXPECTASSERT
Boost.TestWARNCHECKREQUIRE

ご覧のように Google Test には、"エラーにならないアサーション" がありません。
そこで、必要かどうかはさておき Google Test にもエラーにならないアサーションを提供したいと思います。

ベースとなるマクロ
まずは、アサーションで失敗が発生したときに失敗したことにならないようにします。
EXPECT をベースに作っていきましょう。
// GTEST_EXPECT_FAILURE_ をベースに作成
#define GTEST_INFORM_FAILURE_(message) \
 GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess)
                                // kSuccess とし成功を指定する

アサーションは最終的に GTEST_<level>_FAILURE_ を呼ぶので、エラーにならないアサーション用に GTEST_INFORM_FAILURE_ を定義します。(エラーにならないアサーションを INFORM としています。)

述語アサーション
次に、ほとんどのアサーションが述語アサーションを利用しているので、そちらを用意します。
#define INFORM_PRED_FORMAT1(pred_format, v1) \
  GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_INFORM_FAILURE_)
#define INFORM_PRED1(pred, v1) \
  GTEST_PRED1_(pred, v1, GTEST_INFORM_FAILURE_)

#define INFORM_PRED_FORMAT2(pred_format, v1, v2) \
  GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_INFORM_FAILURE_)
#define INFORM_PRED2(pred, v1, v2) \
  GTEST_PRED2_(pred, v1, v2, GTEST_INFORM_FAILURE_)
GTEST_INFORM_FAILURE_ を使うように書き換えただけです。
(述語アサーションは "5" まであるので、すべて用意します。)

各アサーションの定義
最後に、INFORM_TRUE とか INFORM_EQ マクロを定義します。
先に定義した述語アサーションを使うようにするだけです。
#define INFORM_TRUE(condition) GTEST_TEST_BOOLEAN_(condition, #condition \
    , false, true, GTEST_INFORM_FAILURE_)

#define INFORM_EQ(expected, actual) \
  INFORM_PRED_FORMAT2(::testing::internal:: \
   EqHelper<GTEST_IS_NULL_LITERAL_(expected)>::Compare, \
   expected, actual)
#define INFORM_NE(expected, actual) \
  INFORM_PRED_FORMAT2(::testing::internal::CmpHelperNE, expected, actual)
#define INFORM_LE(val1, val2) \
  INFORM_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2)
#define INFORM_LT(val1, val2) \
  INFORM_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2)
// 他にもありますが、ここでは省略します。

使い方
他のアサーションと同様に使います。
TEST(InfromTest, Sample)
{
    INFORM_TRUE(rand() != 0);
    INFORM_EQ(5, rand());
}

出力イベントリスナー
さて、ここまで順調にきましたが 問題 が発生します。
Google Test デフォルトの出力イベントリスナーだと何も出力されません。
TestPartResult::kSuccess のときは、何もせず return するようにコーディングされているからです。

そこで、ユーザー定義のイベントリスナーを追加してやります。

class InformPrinter : public ::testing::EmptyTestEventListener
{
private:
 virtual void OnTestPartResult(const TestPartResult& test_part_result)
 {
  // 成功のときに出力
  if( test_part_result.type() == TestPartResult::kSuccess )
  {
   // 出力してもらえるようにダミーのタイプを設定する
   TestPartResult tmp(TestPartResult::kNonFatalFailure
    , test_part_result.file_name()
    , test_part_result.line_number()
    , test_part_result.message());
   ::testing::UnitTest::GetInstance()->
    listeners().default_result_printer()->OnTestPartResult(tmp);
  }
 }
};

int main(int argc, char **argv) {
 testing::InitGoogleTest(&argc, argv);
 // InformPrinter をイベントリスナーに追加
 ::testing::UnitTest::GetInstance()->
  listeners().Append( new InformPrinter() );
 return RUN_ALL_TESTS();
}

iutest でできるようにしました
ここまで紹介してきた INFORM マクロを iutest で使えるようにしました。(v0.18.1.0 以降)
iutest は自作の C++ テストフレームワーク です。
Boost.Test には、include するだけ使える方法もあるようですが、
iutest を使えば、include するだけで Google Test の記法を使うことができます。
詳しくは、 SourceForge.Jp の プロジェクトページ を見てください。