🔰

Google Mock の Matcher の使い方のサンプル

2021/04/30に公開

Google Mock を使ったテストを書くときの、 Matcher の使い方のサンプルです。
ちょっと間を置くと忘れてしまう自分のための備忘録なので、網羅していない上に偏っていると思います。
記事の内容は、ある程度 Google Mock を使い慣れている前提です。


はじめに

Expectation と Matcher

Google Mock を使ったテストプログラムでは、テスト対象を呼び出す前に Expectation を書きます。 Expectation とは、モック化した関数が「このように呼ばれることを期待している」という表明で、 EXPECT_CALL マクロを使って書きます。
Expectation において「このように呼ばれることを期待している」の「このように」を指定するために使うのが Matcher です。 Matcher はあらかじめ用意されているもののほかに自分で作ることもできるので、本記事のタイトルは「書き方」ではなく「使い方」としました。

おことわり

あらかじめ定義されている Matcher をはじめ、 Google Mock のたいていのクラスや関数は testing 名前空間内で宣言されています。
以降ではこの testing 名前空間名の指定は省略しているのでご注意ください。

Matcher の使い方のサンプル

何でもマッチ

_ (アンダースコア 1 文字) を指定するとあらゆる引数にマッチします。

テストコード
EXPECT_CALL(mock, SetRandomSeed(_));

この例は、 SetRandomSeed(<何らかの型>) というメンバ関数が、何らかの値を引数として呼ばれることを期待しているという Expectation です。
これを使うのは、次のどちらかの場合だと思います:

  • どのような値が渡されるのかまったく想定できない (あらゆる値が渡される可能性がある)
  • テスト (の作成者) が、渡される値に興味がない (呼ばれさえすれば、引数は何でもよい)

なお、 testing::_ は、 testing::internal::AnythingMatcher という Matcher クラスのインスタンスです。 テストコードのコンパイルエラーに "AnythingMatcher" という名前が含まれていたらこれのことです。 (将来変更される可能性はありますが)

文字列

文字列をマッチさせるには StrEq Matcher を使います。

テストコード
EXPECT_CALL(mock, SetCaption(StrEq("new caption")));

この例は、 SetCaption(const char *) というメンバ関数が、 "new caption" という文字列へのポインタを引数として呼ばれることを期待しているという Expectation です。

StrEq を忘れると、おそらく先頭文字のアドレス同士が比較されてしまうと思います。

なお、 StrEq は、 C 文字列だけでなく、 std::basic_string オブジェクトにも使えるようです。

ポインタ

ポインタ引数自体を検証したい場合は整数型引数などと同様です。
ここでは、それ以外の場合の例を示します。

ヌルではない場合にマッチさせたい

引数がヌルポインタではないことを指定したい場合は NotNull という専用の Matcher が用意されています。

テストコード
EXPECT_CALL(mock, ReadBytes(NotNull()));

この例は、 ReadBytes(unsigned char *) というメンバ関数が、ヌルではないポインタを引数として呼ばれることを期待しているという Expectation です。
同メンバ関数の呼び出し側 (つまり、テスト対象) は適当なサイズのバッファを確保して、そのバッファへのポインタを ReadBytes 関数に渡してくるでしょう。 このように:

テスト対象
unsigned char buffer[bufferSize];
xxx.ReadBytes(buffer);

そのアドレスが具体的に何番地になるかはわかりませんし、何番地でもかまわないわけですが、 ReadBytes メンバ関数としてはヌルポインタは受け入れられません。
そのような場合にちょうどよい Matcher です。

ポインタが指している先の値を検証したい

ポインタ引数自体 (つまり、アドレス) ではなく、ポインタが指している先のオブジェクトの値を指定したい場合は Pointee という Matcher を使います。

テストコード
EXPECT_CALL(mock, SetPointerOfValue(Pointee(Eq(100))));

だいぶ作為的な例ですが、 SetPointerOfValue(const int *) というメンバ関数が、何らかのポインタを引数として呼ばれ、かつ、そのポインタ引数が指している値が 100 であることを期待しているという Expectation です。

つまり、テスト対象はこのようなコードであるという想定です:

テスト対象
int value{100};
xxx.SetPointerOfValue(&value);

なお、 Pointee Matcher はポイント先の値を見に行く前にポインタがヌルでないことを確認してくれるので、万が一モック関数にヌルポインタが渡された場合でも、 UB になることはなく、 Expectation の不一致となります。

クラスのポインタ

組み込み型以外の、クラスのようなユーザー定義型へのポインタでも同様です。
==> などの演算子でオブジェクトを比較できれば Pointee + Eq / Gt などの Matcher でポイント先の値を検証できます。

Data.hpp
struct Data {
  std::string Name;
  int         Value;
};

constexpr bool operator==(const Data &lhs, const Data &rhs) noexcept {
  return (lhs.Name == rhs.Name) && (lhs.Value == rhs.Value);
}
テストコード
Data data{"xxx", 100};
EXPECT_CALL(mock, SetPointerOfData(Pointee(Eq(data))));

ただ、実際には、すべてのクラスが == で比較できるようにはなっていなくて苦戦すると思います。 上記の例では Data クラス用の operator== が提供されていなければ、テストのためだけにそれを書くことになるでしょう。 それに加えて、そのような operator==Data クラスと同じ名前空間にいないといけないのでなおさら嫌ですよね。
Boost.PFR というライブラリを使うと集成体 (aggregate) であるようなクラスのインスタンス同士を比較する演算子を自動で生成することができるそうですが...

キャスト

関数があらゆる型のオブジェクトを受け取りたい場合に void ポインタが使われていることがあります。 特に、 C からも呼べるように書かれた関数の場合はほかに方法がないと思いますので。
そのような場合にポイント先の値を検証するにはキャストが必要となります。

次の例の SetData メンバ関数がそのような関数だとします。 引数の型は const void * です。

テスト対象のコードはこんな感じです:

テスト対象
int data{100};
xxx.SetData(&data);

実際に渡されるポインタは int 型のオブジェクトを指していて、そのオブジェクトの値が 100 であることを検証したいときにはこのように書きます:

テストコード
EXPECT_CALL(mock, SetData(MatcherCast<const void *>(SafeMatcherCast<const int *>(Pointee(Eq(100))))));

長いので改行します:

テストコード
EXPECT_CALL(mock,
  SetData(
    MatcherCast<const void *>(
      SafeMatcherCast<const int *>(
        Pointee(
          Eq(100)
        )
      )
    )
  )
);

内側から見ていくと、次のように比較対象の型を変化させて SetData メンバ関数の引数の型と一致させます:

  1. Eq(100): int
  2. Pointee(Eq(100)): int *
  3. SafeMatcherCast<const int *>(Pointee(Eq(100))): const int *
  4. MatcherCast<const void *>(SafeMatcherCast<const int *>(Pointee(Eq(100)))): const void *

配列

配列 (配列へのポインタ) が引数として渡される場合の検証方法です。
With 節と Args および ElementsAre / ElementsAreArray Matcher を組み合わせて使います。

SetArrayData(const int *, std::size_t) というメンバ関数を例に見てみます。

テスト対象のコードはこんな感じです:

テスト対象
const std::size_t count{2};
int array[count]{100, 200};
xxx.SetArrayData(array, count);

これにマッチさせるにはこのように書きます:

テストコード
EXPECT_CALL(mock, SetArrayData(NotNull(), 2))
  .With(Args<0, 2>(ElementsAre(100, 200)));

Args Matcher のテンプレート引数は次のとおりです:

  • 第 1 引数: モック関数 (SetArrayData) の引数のうち、マッチさせたいもののインデックス (0 始まり)
  • 第 2 引数: 配列の要素数

あるいは:

テストコード
int array[]{
  100,
  200,
};
EXPECT_CALL(mock, SetArrayData(NotNull(), 2))
  .With(Args<0, 2>(ElementsAreArray(array));

ElementsAreElementsAreArray の違いはご覧のとおりです。 配列の各要素の型がクラスのような場合は ElementsAreArray の方が書きやすいと思います。 当然、そのような場合にはそのクラスが == で比較できる必要があります。

参考資料

Google Mock を使う上でわからないことが出てきたらこれらの情報を参照しています。 たいていのことは解決すると思います:

http://opencv.jp/googlemockdocs/fordummies.html
http://opencv.jp/googlemockdocs/cookbook.html
http://opencv.jp/googlemockdocs/cheatsheet.html

Discussion