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 を使おうと思います。

2 件のコメント:

  1. 17日目からリンク貼らせて頂きました、すげえ滑り込みで投稿してから気が付いて慌ててリンク直しました。

    ・・・最近の初心者はBoost使えるのか、怖い(違う、そうじゃない)
    http://qiita.com/yumetodo/items/e49a673afd9a3ecb81a8

    返信削除
    返信
    1. リンクありがとうございます。
      ちょっと初心者向けかな?と迷いつつも、滑り込みましたー

      削除