Google Mock の Matcher の使い方のサンプル
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 でポイント先の値を検証できます。
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
メンバ関数の引数の型と一致させます:
-
Eq(100)
:int
-
Pointee(Eq(100))
:int *
-
SafeMatcherCast<const int *>(Pointee(Eq(100)))
:const int *
-
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));
ElementsAre
と ElementsAreArray
の違いはご覧のとおりです。 配列の各要素の型がクラスのような場合は ElementsAreArray
の方が書きやすいと思います。 当然、そのような場合にはそのクラスが ==
で比較できる必要があります。
参考資料
Google Mock を使う上でわからないことが出てきたらこれらの情報を参照しています。 たいていのことは解決すると思います:
Discussion