2015年9月28日月曜日

Google Mock の Matcher を使ってみた

Google Mock とは Google 製の C++モッキングフレームワークです。
Google Test はよく使っているのですが、Google Mock は存在は知りつつも実用したことは今までありませんでした。
モッキングフレームワークですから、モック作成がメイン機能になりますが、Google Mock は Google Test の機能拡張版として、Google Test の書き方をしつつより便利なアサーションを使うことができます。

今回はその便利なアサーションについてお話しようと思います。

THAT!!
THAT。そうそのアサーションは ASSERT_THAT,EXPECT_THAT という名前で定義されています。
THAT は引数に Matcher を取ります。この Matcher にマッチするかどうかを検証します。
Matcher については日本語ドキュメントに詳しくまとまっているので、そちらを参考にしてください。
Google Mock ドキュメント日本語訳
(v1.6.0 当時のものなので注意)

これらの Matcher は組み合わせて使うこともでき、Google Test の ASSERT_EQ や ASSERT_LE など検証内容が固定のアサーションよりも、統一的な記法でより柔軟にテストを書くことができます。その例を以下に示します。
※以下のコードは適切に using されているものとします。

基本的な比較
Eq,Ne,Le,Lt,Ge,Gt,IsNull,NotNull および浮動小数点数比較など、基本的な比較 Matcher は Google Test でも同等のアサーションがありますので、省きます。

OR 条件
AnyOf Matcher を使用することで OR 条件を表現できます。
TEST(Matcher, AnyOf)
{
   int a=1;
   ASSERT_THAT(a, AnyOf(Eq(1), Eq(10)));
}

これは、「a == 1 || a == 10」であることを検証しています。
Google Test ではスマートに書くことができなかった OR 条件が非常に簡単に書くことができます。

AND 条件
今度は逆に AND 条件をどう書くか、です。
Google Test の場合は以下のように書けました。
TEST(Test, And)
{
    int a=1;
    ASSERT_GT(a, 0);
    ASSERT_LT(10, a);
}
もちろん、Google Mock でも上記のような書き方もできますが、AllOf Matcher が用意されています。
Matcher のメリットは組み合わせができることです。特に理由がない限り AllOf Matcher を使うのがよいでしょう。
TEST(Matcher, AllOf)
{
    int a=1;
    ASSERT_THAT(a, AllOf(Gt(0), Lt(10)));
}

これは、「a > 0 && a < 10」であることを検証しています。

コンテナ
Google Test でコンテナの各要素の検証は、できなくはないですがやや面倒です。
Matcher にはコンテナの要素やクラス・構造体メンバーへのアクセスをするものが用意されています。
これを使うことでかなり簡単に要素チェックができるようになります。

すべての要素が指定条件にマッチする
TEST(Matcher, Each)
{
    int a[3] = {1, 2, 3};
    ::std::vector b { 1, 2, 3};
    ASSERT_THAT(a, Each(Gt(0)) );
    ASSERT_THAT(b, Each(Gt(0)) );
}
Each Matcher はコンテナの要素すべてが指定条件にマッチするかを検証します。
上の例だと、コンテナ a/b の要素がすべて、 0 より大きいことを検証しています。

指定条件にマッチする要素を含む
TEST(Matcher, Contains)
{
    int a[3] = {1, 2, 3};
    ::std::vector b { 1, 2, 3};
    ASSERT_THAT(a, Contains(Eq(1)) );
    ASSERT_THAT(b, Contains(Eq(1)) );
}
Contains Matcher は Each とは違い、指定条件にマッチする要素を含むかどうかを検証します。
上の例だと、コンテナ a/b の要素に、 1 を含むかどうかを検証しています。

キーの検証
次はキーの検証です。map のキーを検証したいときなどに使用します。
TEST(Matcher, Key)
{
    ::std::map m = make_map();
    ASSERT_THAT(m, Key(Lt(10)) );
}

上記コードの場合、キーの値がすべて 10 未満であることを検証しています。

ペアで検証する
map の要素(ペア)を両方検証したい場合は、Pair Matcher を使います。
TEST(Matcher, Key)
{
    ::std::map m = make_map();
    ASSERT_THAT(m, Pair(Lt(10), StrNe("hoge")) );
}
上記コードの場合、m は 10 より小さいキーであり、値の文字列が "hoge" でないことを検証しています。


メンバーの検証
Key と Pair は map の要素に対してのアクセス Matcher でした。
今度は、任意のメンバー変数や関数にアクセスする Matcher です。
struct X {
    int a;
};
TEST(Matcher, Field)
{
    X x = {1};
    ASSERT_THAT(x, Field(&X::a, Eq(1)) );
}
上記コードは、ASSERT_THAT(x.a, Eq(1)); と同じ意味です。
なんの役に立つかは、後述の複雑な組合せでします。

コンテナの各要素それぞれに Matcher を指定する
Each や Contains には各要素に対し1つの Matcher しか指定できませんでした。
しかし、コンテナの先頭は条件X、残りの要素は条件Yで検証したい場合などもあると思います。
そのような場合に使用するのが、ElementsAre Matcher です。
IUTEST(Matcher, ElementsAre)
{
    int a[3] ={ 0, -1, 3 };
    ASSERT_THAT(a, ElementsAre(Ge(0), Lt(0), Gt(0)));
}

こちらは、下記のコードと同じです。
TEST(Matcher, ElementsAre)
{
    int a[3] ={ 0, -1, 3 };
    ASSERT_THAT(a[0], Ge(0))
    ASSERT_THAT(a[1], Lt(0))
    ASSERT_THAT(a[2], Ge(0))
}

また、配列版の ElementsAreArray もあります。
TEST(Matcher, ElementsAreArray)
{
    int a[3] ={ 0, 1, 3 };
    int b[3] ={ 0, 1, 3 };
    EXPECT_THAT(a, ElementsAreArray(b));
}

ワイルドカード
任意の数値にマッチするワイルドカードも用意されています。
この Matcher は常にマッチする Matcher で一部分だけ検証を無視したい場合などに使えます。
ワイルドカードには2種類あり、常にマッチする '_' と 指定の型にマッチする 'A<type>' があります。

TEST(Matcher, Wildcard)
{
    EXPECT_THAT(42, _);
    EXPECT_THAT(42, A<int>());
}

複雑な組合せ
ここまで紹介した Matcher を組み合わせることで、複雑な条件も簡単に記述できるようになります。

struct X { int a, b; }

TEST(Matcher, Member)
{
    std::map<int, X> m;
    for( int i=0; i < 10; ++i )
    {
        m.insert(::std::pair<int, X>(i, X{i, 100}));
    }
    EXPECT_THAT(m, Each(Pair(Le(10), Field(&X::b, Ge(0)))));
}

こちらは、intをキーとする構造体のマップから、各マップ要素の
キーが10以下、構造体のメンバー b が 0 以上であることを検証しています。
これらの検証を1つずつ記述するのは面倒ですが、Each や Pair などを組み合わせることによって上記のように簡潔に記述ができます。

また、ワイルドカードを使えば検証したくない所を無視することができます。
TEST(Matcher, Member)
{
    std::map<int, X> m;
    for( int i=0; i < 10; ++i )
    {
        m.insert(::std::pair<int, X>(i, X{i, 100}));
    }
    EXPECT_THAT(m, Each(Pair(_, Field(&X::b, Ge(0)))));
}

iutest では?
恒例の iutest の対応状況を書いておきます。
iutest は自作の C++ テスティングフレームワークです。
https://github.com/srz-zumix/iutest

iutest でも THAT アサーションと Matcher が使えます。
Matcher は Google Mock v1.7.0 相当のものが使えるようになってます。
詳しくはドキュメントを参照してください。

http://iutest.osdn.jp/doc/html/modules.html
http://iutest.osdn.jp/doc/html/d4/d14/group___m_a_t_c_h_e_r_s.html

最後に
Google Mock はモッキングフレームワークですが、アサーションの拡張としてだけでも使う価値がありそうです。
(iutest ではそれ単体で Matcher が使えますので、こちらもよろしくお願いします)

2015年9月24日木曜日

[iutest] コンパイルエラーになることをテストしてみた

iutest v1.13.0 をリリースしましたが、
iutest v1.12.0 あたりからコンパイルエラーになることをテストするようにしていました。

手法としては、コンパイラ出力を解析してエラーが発生したことを確認するシンプルな方法です。
この解析周りも含め Python で書いてます。
https://github.com/srz-zumix/iutest/blob/master/tools/python/iutest_compile_error_test.py

さて、テストですので期待値を書くことになります。
これはソースコードに直接書ける仕組みにしました。

以下のように書きます。
IUTEST(StaticAssertTypeEqTest, Fail)
{
    IUTEST_TEST_COMPILEERROR("static_assert_typeeq")
    ::iutest::StaticAssertTypeEq<float, int>();
}

IUTEST_TEST_COMPILEERROR の引数にはエラーメッセージを記述します。
このマクロの直後の行にエラーがあり、エラーメッセージに引数の内容が含まれているとテスト成功です。
(エラーメッセージはコンパイラーごとに異なるので注意が必要ですが…)

実際にテストするときは、以下のようにします。
g++ -I../include -g -Wall -Wextra   -std=c++1y -Werror=undef -o static_assertion_failure_tests static_assertion_failure_tests.cpp   2>&1 | python ../tools/python/iutest_compile_error_test.py -c g++
コンパイラーの標準エラー出力を iutest_compile_error_test.py に食わせてあげます。今回は g++ の出力なので、 -c オプションで指定します。
対応しているコンパイラーは g++,clang,cl(Visual Studio) の3つです。
(この機能自体をちゃんとテストしていないので対応できてないケースがあるかもしれませんが…)

実行するとテスト結果が出力されます。
g++ -I../include -g -Wall -Wextra   -std=c++1y -Werror=undef -o static_assertion_failure_tests static_assertion_failure_tests.cpp   2>&1 | python ../tools/python/iutest_compile_error_test.py -c g++
[OK] IUTEST_TEST_COMPILEERROR( "static_assert_typeeq" ): static_assertion_failure_tests.cpp: 24
[OK] IUTEST_TEST_COMPILEERROR( "static_assert_typeeq" ): static_assertion_failure_tests.cpp: 29
[OK] IUTEST_TEST_COMPILEERROR( "static_assert" ): static_assertion_failure_tests.cpp: 35
[OK] IUTEST_TEST_COMPILEERROR( "static_assert" ): static_assertion_failure_tests.cpp: 41

失敗した場合はこんな感じ。
[OK] IUTEST_TEST_COMPILEERROR( "static_assert_typeeq" ): static_assertion_failure_tests.cpp: 24
[OK] IUTEST_TEST_COMPILEERROR( "static_assert_typeeq" ): static_assertion_failure_tests.cpp: 29
[OK] IUTEST_TEST_COMPILEERROR( "static_assert" ): static_assertion_failure_tests.cpp: 35
[NG] IUTEST_TEST_COMPILEERROR( "hogehoge" ): static_assertion_failure_tests.cpp: 41

テストが失敗したときのメッセージとかはまだしっかり作りこめてませんが、これでコンパイルエラーになるべきところをテストできるようになりました。
ライブラリとか作ってると、こういうテストをしたくなると思います。
もっとよさげなツールがあれば使っていきたいところですが…

では。

2015年9月14日月曜日

iutest v1.13.0 をリリースしました

C++ テスティングフレームワーク iutest v1.13.0 をリリースしました。
変更点は以下の通りです。

  • 追加
    • --iutest_flagfile コマンドラインオプションに対応
    • MFC コンテナ対応
    • 値のパラメータ化テストのテスト名指定に対応
    • 型付けテストのテスト名に型名をつけるオプションマクロ (IUTEST_TYPED_TEST_APPEND_TYPENAME) を追加
    • 非ヨーダ記法を提供するヘッダー(iutest_util_no_yoda.hpp)を追加
    • Visual Studio 2015 対応
  • 修正
    • tr1/iutest_vc_unittest.hpp を修正
    • バグ修正

今回は Google Test v1.18.0 におそらく入るであろう機能に一部対応しています。
これに関しては以前ブログに書きましたので、そちらを参考にしてください。

そのほかの変更点としては、MFC のコンテナ(CMap とか CList とか)に対応しました。
MFC のコンテナは書き方に統一感がなくてめんどくさかった…
あとは、アサーションの expected と actual の順番を逆にするユーティリティを追加しました。(iutest_util_no_yoda.hpp)

もう1つ、Visual Studio テストエクスプローラーに対応するための iutest_vc_unittest.hpp がメンテナンスされてなかったのを修正しました。 Visual Studio 2015 対応および TestCase での分類に対応しています。


NuGet パッケージも更新してます。ちゃんと Appvayor で自動アップデートされてました。便利ですねー

今回は以上。それでは。

2015年9月9日水曜日

[Jenkins] 手動実行したビルドを優先実行する

Jenkins Pirority Sorter Plugin を使うことで手動実行した場合のみ、実行優先度を上げることができます。
これによって、メンテナンス用のジョブを走らせたいときなどに優先的に実行することができます。





設定の仕方は、サイドバーの Job Priorites を開き、追加ボタンを押します。
Jobs to include など適切な設定をします。
ここではメンテナンスビューにあるジョブを対象にしました。
(手動実行したいのはたいていメンテナンス系のジョブでしょう)

次に、「Use additional rules when assigning a priority to a Job」にチェックをいれます。
追加ボタンが出るので押すと「Priority Strategy」の選択が出るので、その中から「Job Triggered by a User」を選びます。
あとは優先度を決めます。優先度は数値が小さいほど高くなります。
(デフォルトでは、1-5 の優先度になってますがシステム設定ページで変更することができます)


設定は以上です。
Priority Sorter はバージョンが上がってからあまり見てなかったのですが、設定も一か所にまとまったし、機能も増えたし、かなり便利になったんではないかと思います。

今回はここまで。それでは。

2015年9月3日木曜日

[iutest] Google Test と袂を分かちつつある

Google Test v1.7 がリリースされて、約2年になろうとしています。
v1.7 がリリースされたのが、2013年9月。
v1.6 がリリースされたのが、2011年4月。
v1.8 はいつになるでしょうか。まだその気配はなさそうですが…


ただ、開発は進んでいて、trunk には v1.7 にない機能が、既に幾つか追加されています。
代表的なものとしては、
  • --gtest_flagfile コマンドラインオプション
  • "名前付き"値のパラメータ化テスト
があります。

flagfile は指定したファイルから、コマンドラインオプションを設定する機能です。
コマンドライン引数には文字数制限があるため、--gtest_filter オプションの条件が長くなると収まらない問題の対応として追加されました。

名前付き値のパラメータ化テストは、値のパラメータ化テストのテスト名を任意に設定できる機能です。
以下のように INSTANTIATE_TEST_CASE_P の最後に命名 Functor を指定して使います。
指定しなかった場合は、これまでどおりのインデックスによる命名になります。
class CustomFunctorNamingTest : public TestWithParam<std::string> {};
TEST_P(CustomFunctorNamingTest, CustomTestNames) {}

struct CustomParamNameFunctor {
  std::string operator()(const ::testing::TestParamInfo<std::string>& info) {
    return info.param;
  }
};

INSTANTIATE_TEST_CASE_P(CustomParamNameFunctor,
                        CustomFunctorNamingTest,
                        Values(std::string("FunctorName")),
                        CustomParamNameFunctor());


iutest の実装
さて、これらの機能ですが、iutest では少し違った実装をしています。

フィルターオプションの拡張
まず、flagfile の前に、このフラグが追加された理由である、コマンドライン引数の長さの問題に対して、フィルターオプションをファイルから読み込める方法を追加しました。

test.exe --iutest_filter=@filter.txt

のように、フィルターの先頭に @ を付けてファイルパスを指定します。
フィルターファイルは行頭 # でコメント、改行で条件区切り(; セミコロン)になります。
# iutest_filter_file_tests testdata
*Run*
# comment
*OK*
上記フィルターファイルは、"--iutest_filter=*Run*:*OK*" と同じです。

flagfile
さて、本題の flagfile ですが、もちろん実装しています。
使い方は、Google Test と一緒で

test.exe --iutest_flagfile=flag.txt

のように使います。
ファイルの書き方ですが、一行ごとにオプションを書きます。
また、iutest の場合フィルターファイルと同じように行頭 # でコメントを書けます。
あと、空行は無視されるようになってます。(Google Test も無視してほしいなぁ)

# iutest_flagfile_tests testdata
--iutest_filter=@testdata/filter.txt
--iutest_color=off

上記は iutest のフラグファイルの例です。
先に説明をしたフィルターオプションをファイル指定することもできます。


名前付き値のパラメータ化テスト
Google Test では、INSTANTIATE_TEST_CASE_P に命名 Functor を指定する方式でしたが、iutest ではテストフィクスチャーに MakeTestName 関数を書くことでカスタマイズできます。

class RenameParamTest : public ::iutest::TestWithParam<bool>
{
public:
    template<typename T>
    static const ::std::string MakeTestName(const char* basename, int, const T& param)
    {
        ::std::string name = basename;
        return name + "/" + ::iutest::StreamableToString(param);
    }
};

Google Test の方法だと、インスタンス化の時に命名方法を変えられるので、INSTANTIATE_TEST_CASE_P ごとに名前を変えることが可能です。iutest は実装の容易さから上記方法で実装しました。(INSTANTIATE_TEST_CASE_P ごとに名前を変える必要があるのか、ちょっと想定できなかったので)
と、言っても iutest は Google Test と互換性を持つようにしていますので、そのうち対応することになるとは思います。

最後に
というわけで、iutest の開発も落ち着いてきて独自の機能というのが増えつつあります。
Google Test との互換性のこともあるため、完全に離れてしまうことはないですが、少し窮屈に感じることも出てきました。

そろそろ新しいテスティングフレームワークを出したいなぁーと思ったりもしてますが…
今日はこの辺で!
では。