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 が使えますので、こちらもよろしくお願いします)

0 件のコメント:

コメントを投稿