google mockで快適テスト生活
googleにより公開されているgoogle mockが使いやすかったので使い方紹介。
http://code.google.com/p/googlemock/
から落としてペペっと入れましょう。
使いたいシーンとしては、充分モジュール化されたオブジェクト指向プログラム中で
int main(){ A a; B b; func(&a, &b); // a->hoge(1) と b->fuga(2,3) が呼ばれて欲しい! }
この様に特定の関数が狙った通りの動作をして欲しい(というか、その動作をすることを仕様化したい場合)
#include <gtest/gtest.h> #include <gmock/gmock.h> class mock_A{ public: MOCK_METHOD1(hoge,void(int)); // void hoge(int);と同義 }; class mock_B{ public: MOCK_METHOD2(fuga,void(int,int)); // void fuga(int,int);と同義 };
と書いて予め呼び出すシグネチャを定義しておき、gtestのテスト本体で
TEST(func, will_call_hoge_and_fuga){ mock_A a; mock_B b; // 呼ばれるべき関数を規定 EXPECT_CALL(a, hoge(1)); EXPECT_CALL(b, fuga(1,2)); // 実際に関数を呼んでみる func(&a, &b); // a->hoge(1), b->fuga(1,2)の両方が呼ばれたらテスト成功 }
のように書く事でテストを作れます。
ノリとしてはジョジョ2部の「次にお前は○○と言う!!」みたいな感じですね。
関数がそもそもA,Bに対応しているのに対し、mock_Aとmock_Bを引数に取らせてテストをすることになるため
funcはtemplateで記述する必要があります。
template <typename A, typename B> void func(A* a, B* b){ ... }
templateだとコンパイル時間が膨れ上がるようになるので継承でどうにかすることもできます。
// Aもmock_Aもこれらのクラスを継承する。 struct base_A{ virtual void hoge(int) = 0; ...... }; struct base_B{ virtual void fuga(int,int) = 0; ...... }; // baseをターゲットにする void func(base_A* a, base_B* b);
動的にディスパッチされるのでコンパイル時の負担が減る…はずです。
実際にそれなりに便利に使ってるコードは
http://github.com/kumagi/skipgraph/blob/master/logic_test.cc
で公開しています。
skip graphの各ノードの挙動を規定してその動作の通りになっている事をチェックさせています。
まったく同一のコードを実際のノード上で別のtemplate引数で展開するので安心です。
以下、細かいTips
http://code.google.com/p/googlemock/wiki/CookBook
から、現在便利に使ってる物を抜粋して紹介
動作をもっと厳しくor緩やかに規定
決めた動作以外が呼ばれると気持ち悪いなーってときはStrictMockを使います。
一応規定したメソッド以外が呼び出された場合、通常は「uninteresting call」と出るだけでエラーにはなりませんが、それをエラーとして表示できます。
using ::testing::StrictMock; TEST(func, will_call_hoge_and_fuga){ StrictMock<mock_A> a; // StrictMock<T>の形で使える StrictMock<mock_B> b; 場合 // 呼ばれるべき関数を規定 EXPECT_CALL(a, hoge(1)); EXPECT_CALL(b, fuga(1,2)); // 実際に関数を呼んでみる func(&a, &b); }
縛りたい対象のMockをtemplateのStrictMockに与えてやるだけです。
逆に呼ばれても構わない(もしくは呼ばれるか定かでない事も仕様のうち)場合はStrictを全部Niceに書き換えたバージョンを使う事で達成できます
モック関数で戻り値も渡したい場合
戻り値を利用して、戻り値に対して更なる動作を行う場面は多々あります。
using ::testing::Return; struct mock_C{ MOCK_METHOD1(hige, std::string(int)); }; TEST(another_func, will_call){ mock_C c; EXPECT_CALL(c, hige(12)) // 12を与えられたらstringの"hello"を返すよう規定 .WillRepeatedly(Return(std::string("hello-"))); // Returnマクロを使用 another_func(&c); }
WillRepeatedlyの他に一度しか返さないバージョンのWillOnceもあります。
モック関数の戻り値を有効利用する場合
おそらくmockの中で一番便利な利用法がこれ
mockのオブジェクトはnon-copyableなので*1参照で返してやる必要があります
using ::testing::ReturnRef; struct moock_child{ MOCK_METHOD1(good_night, void(std::string)); }; strict mock_mother{ MOCK_METHOD1(call_child, mock_child&(int));// 参照返し }; TEST(father, good_night){ mock_mother mom; mock_child child1,child2; // 子供は二人 EXPECT_CALL(mom, call_child(1)).WillRepeatedly(ReturnRef(child1)); // ReturnRefマクロにより参照返し EXPECT_CALL(mom, call_child(2)).WillRepeatedly(ReturnRef(child2)); // 対応する子供ごとに言動が変わるはず EXPECT_CALL(child1, good_night("おやちゅみ〜")); EXPECT_CALL(child2, good_night("おやすみなさい")); // 実際に呼ぶ father(&mom); } // 関数の中身はこんな感じ template <typename mother> void father(mother* m){ // motherにchildを問い合わせて、それぞれにおやすみなさいと言う m->call_child(1).good_night("おやちゅみ〜"); m->call_child(2).good_night("おやすみなさい"); }
call_childの戻り値を受け取ろうとした場合、コピー不可の物を参照渡しで受け取っているため、参照受けするべきで、でも実コードの方では実体が戻り値になってるのでそちらに揃えたくて、だけど実体での戻り値は参照で受け取る事はできなくて、代わりにconst参照で受け取れば実体も参照も画一的に受け取れるじゃないかと言い出すと今度はgood_night()がconstメソッドである必要があったりして…と面倒ですが良い案が浮かばないので直接繋げて記述することでお茶を濁しています。良い方法は無いでしょうか…。
mockの場合と実物の場合とでchildの型名が違いますが簡単なメタプログラミングによりtemplate引数が無駄に増える事を回避できます。
struct mock_mother{ typedef mock_child child; // あらかじめ実オブジェクトの方にも同様のtypedefをしておく MOCK_METHOD1(call_child, child&(int)); }; template <typename mother> void father(mother* m){ const mother::child& child2 = m->call_child(2); // mother型が分かればchild型が付随してくる child2.good_night("おやすみなさい"); };
ほんのすこしだけメタっぽくなりました。
引数のチェックを緩くする
ワイルドカードのように、あらゆる呼び出し引数とマッチするのが _ (アンダーバー)。
using ::testing::_; TEST(func, will_call_with_something){ mock_A a; EXPECT_CALL(a, hoge(_)); // hogeが何でもいいから呼ばれるに違いない! func(a); }
Gt(=greater than) Le(=less or equal) Ne(=not equal)といった大小比較も出来ます*2
using ::testing::Ge; TEST(func, will_call_with_something){ mock_A a; EXPECT_CALL(a, hoge(Ge(2))); // 2以上の引数で呼ばれるに違いない! func(a); }
呼び出し回数を指定する
EXPECT_CALLのあとにメンバーのように要素を追加していくことで指定できます。上述のWillRepeatedlyと併用できます
TEST(func, will_call_hoge_4_times){ hoge_A a; EXPECT_CALL(a, hoge(_)) .Times(5); // 5回hoge()が呼ばれるに違いない! func(a); }