MockPPって何? mockpp はプラットフォーム非依存の汎用ユニットテストフレームワークである。ちなみに C++ 用。 プロジェクトの目標は Mock Objects for Java や EasyMock、jMock などのようなユニットテスト開発環境を提供することにある。 モックオブジェクトはあなたの製品コードが必要とするだろう機能動作をエミュレートすることでテストを助ける。 これは例えば、テストするにはとても大変で時間のかかる巨大なデータベースなどの場合である。 とりあえずは、Mock Objects のインターフェースを模したまま C++ へ移植することから始めた。 しかしながら C++ と Java では(簡単なリフレクション機能など)かなりの違いがあったため、詳細な部分は変更せざるを得なかった。 それに加えて EasyMock や jMock の方法論を取り入れた。 私の限られた java の知識からみると、EasyMock は MockObjects に似ており、かつ利用が簡易で C++ への移植も簡単に思えた。 このライブラリの利用方法 注:下記の内容は mockpp の基本的な項目にすぎない。その他の利用法についてはハンドブックやAPIリファレンス、およびチュートリアルによって説明されている。 mockpp は基本用法と拡張用法の2種類に区別されるクラスやテンプレート、マクロなどによって構成される。 基本用法 基本用法は単純な動作をカバーする。 利用者はオブジェクトを作成し、それがどのような動作を許可されているか記述する。 たとえば、メソッドに正しいパラメータ値が渡されているか確認するために、モックを次のように設定することができる。 void MyMockClass::initExpectation() { myIntExpectation.setExpected(123); } ... void MyMockClass::methodUnderTest(unsigned i) { myIntExpectation.setActual(i); } ここへ 123 という数値が渡されれば、それは期待通りの動作である。 他の場合には AssertionFailedError がスローされる。 単純な動作の検証のため、次のようなクラス群が用意されている。 ・値の厳密な照合 ・範囲値による照合(主として数値に対して行う) ・順序立てて一連の値を検証するためのリスト照合 ・集合に含まれている値が最低一度は出現しているかどうか ・部分文字列の照合 ・カウンターのインクリメントチェック 時には渡されるパラメータではなく、返されるオブジェクトについて検証したい場合もある。 この場合には返り値のリストを設定してチェックすることができる。 void MyMockClass::initReturnList() { myStringReturnList.addObjectToReturn(std::string("alice")); myStringReturnList.addObjectToReturn(std::string("bob")); myStringReturnList.addObjectToReturn(std::string("sue")); } ... std::string MyMockClass::methodUnderTest() { return myStringReturnList.nextReturnObject(); } こうすれば設定した通りの順序でオブジェクトが返される。 もし設定した回数より多くの返り値を要求してしまうと AssertinoFailedError がスローされる。 また、設定したよりも要求回数が少なかった場合も同様である。 より詳しい情報については開発ガイドの「basic section」を参照してほしい。 拡張用法 mockpp はモックオブジェクトの作成のために2種類のアプローチを用意している。 一つ目は簡単に理解できるが、より厳密なもの。 二つ目は複雑だが柔軟でユーザ定義のクラスにより拡張が可能なものである。 まあ、この感想は個人によって異なるかもしれない。 これらについての紹介は「common characteristics」の項に載っている。 Visitable Mock Objects この主要な考えは、現実世界のオブジェクトを動作の限られたオブジェクトで模倣しようというものだ。 この目的では VisibleMockObject とそのメソッド群を作成する。 また、その実行パスを記録したり、関数へ食わせるパラメータや返り値、スローする例外も規定できる。 返り値や例外は渡されたパラメータに応じて異なったものを返すこともできる。 期待される呼び出しパラメータは、モックオブジェクトへモックメソッドを定義することで簡単に準備できる。 class MyVisitableMockObject : public VisitableMockObject { public: MyVisitableMockObject(const String &name) : VisitableMockObject(name, 0) // 内部用のモックメソッドを構築する , visitable_mocker("visitable") this) //訳注:閉括弧とカンマの誤植? {} // 呼び出しをモックメソッドへ通知 int visitable(unsigned u) { return visitable_mocker.forward(u); } // int visitable(unsigned) へのモックメソッドを準備 VisitableMockMethod visitable_mocker; }; .... MyVisitableMockObject mvo("mvo"); // 簡単な呼び出しのために関数への参照を準備する VisitableMockMethod &visitor (mvo.visit_mocker); // ここからが記録される visitor.addReturnValue(1); // return "1" the first time visitor.addReturnValue(11); // return "11" the second time visitor.setDefaultReturnValue(123); // return "123" all the other calls visitor.addResponseValue(0, 1); // return "0" when passed "1" visitor.addResponseValue(1, 0); // return "1" when passed "0" visitor.addResponseThrowable(make_throwable(int(1), 1); // throw when passed "1" 訳注:閉括弧不足? visitor.addResponseThrowable(make_throwable(int(0), 0); // throw when passed "0" 訳注:閉括弧不足? // throw std::string("string 1") when called the first time visitor.addThrowable(make_throwable(std::string("string 1"))); // throw std::string("string 2") the next three times visitor.addThrowable(std::string("string 2"), 3); // throw int(123) the rest of the time visitor.setDefaultThrowable(make_throwable(int(123))); mvo.visitable(1); // require "1" as parameter the first time mvo.visitable(2); // require "2" as parameter the second time mvo.activate() // ここからの呼び出しが全て検証される mvo.visitable(1); // verify "1" which is ok, see above mvo.visitable(1); // would fail as "2" is required .... より多くの情報は開発ガイドの「visitable mock objects」の項を参照のこと。 Chainable Mock Objects 上の例と同様の振舞いを、別のアプローチである chainable mockで記述するとオブジェクトは次のように単語で説明される。 例えば ・I _expect_ some method invocation exactly _once_(私はメソッド呼び出しを一回のみ期待する) ・This must happen _after_ another invocation labeled "other-ident"(これは"other-ident"で標識される別メソッドの後で起きる) ・The method must be called _with_ with a value which _is equal_ to 321(このメソッドは321と等しい値で呼び出される) ・Then the method _will_ return 123(その後、メソッドは 123 を返す) ・This invocation has an _id_entifier called "ident"(この呼び出しは"ident"として名付けられる) C++コードではこのようになるだろう chainer.expects(once()) .after("other-ident") .with(new IsEqual(321)) .will(new ReturnStub(123)) .id("ident"); これと同様の内容を前のアプローチで行う場合には、この内容を記述するヘルパーオブジェクトを作成する必要がある。 上記の内容についてはハンドブックの「chainable mock objects」を参照してほしい。 Poor Man's Mock Objects 大半の場合はこのように高機能なモックオブジェクトライブラリを利用する必要は無い。 しばしば、もっと簡単でちょっと汚いアプローチで間に合うだろう。 これらの理由からハンドブックには「simple mock object patterns」の項がある。 もしあなたが他のパターンを知っているのなら、ハンドブックに記載するために私宛にメールしてくれるとありがたい。