Google Mockの解説マダー?と、先日書いたら、さっそく書いてくれました!
感謝です!
この記事で、KINECTから来ると想定されるデータをモックで実装して、以降のロジックをテストする方法を解説してもらいました。
#モックといえば、OpenNI自体にもジェネレータごとにモックがあるので、それを有効に使えないのかなぁ。
で、次にわからなかったのがコレで、akiraさんとtosikawaさんからアドバイスをもらいました
@kaorun55 mockに何回呼ばれたら成功とか設定できますよね。くる条件でのテストでは期待する回数を、こない条件のテストでは0回を設定する感じですかね。
2011-09-09 16:07:09 via YoruFukurou to @kaorun55
こんな感じ
お題
- OpenNIのPlayerが持っている機能である、ファイルの終端(記録の終わり)を通知するコールバックが来たかテストする
- ソースはこちら
Player.h
#pragma once #include <XnCppWrapper.h> namespace openni { // 再生のコールバック class PlayerCallback { friend class Player; public: virtual ~PlayerCallback(){} protected: // ファイルの終端 static void XN_CALLBACK_TYPE EndOfFileReached( xn::ProductionNode& node, void* pCookie) { ((PlayerCallback*)pCookie)->EndOfFileReached( node ); } // ファイルの終端 virtual void EndOfFileReached( xn::ProductionNode& node ) { } }; // 再生 class Player { public: Player() : callback_( 0 ) { } ~Player() { if ( player_.IsValid() && (callback_ != 0) ) { player_.UnregisterFromEndOfFileReached( callback_ ); } } xn::Player& GetPlayer() { return player_; } void RegisterCallback( PlayerCallback* callback ) { assert( player_.IsValid() ); XnStatus rc = player_.RegisterToEndOfFileReached( &PlayerCallback::EndOfFileReached, callback, callback_ ); if ( rc != XN_STATUS_OK ) { throw std::runtime_error( xnGetStatusString( rc ) ); } } protected: xn::Player player_; XnCallbackHandle callback_; }; }
テスト
#include <gtest\gtest.h> #include <gmock\gmock.h> #include "openni\Player.h" namespace { const std::string RECORD_FILE_NAME = "EndOfFileReached.oni"; // アプリケーションクラス class App : public ::openni::PlayerCallback { public: App() { // プレーヤーの作成と設定 context.Init(); context.OpenFileRecording( RECORD_FILE_NAME.c_str() ); context.FindExistingNode( XN_NODE_TYPE_PLAYER, player.GetPlayer() ); player.RegisterCallback( this ); player.GetPlayer().SetRepeat( false ); } void Run() { // EOFまで回す while ( !player.GetPlayer().IsEOF() ) { context.WaitAndUpdateAll(); } } // ファイルの終端 virtual void EndOfFileReached( xn::ProductionNode& node ) { std::cout << "ファイルの終端" << std::endl; } private: xn::Context context; openni::Player player; }; // アプリケーションクラスのモック class MockApp : public App { public: MOCK_METHOD1( EndOfFileReached, void( xn::ProductionNode& node ) ); }; } TEST( PlayerTest, EndOfFileReached ) { using ::testing::_; MockApp app; // モックの設定 EXPECT_CALL( app, EndOfFileReached(_) ) .Times( 1 ); // ファイルの終端検出は1回呼び出される // .Times( 2 ); // 2回はNG app.Run(); }
解説
Player
モックでコールバック関数に対する設定を行うためには、コールバック部分をメンバ関数する必要があります。そのため、もともとのOpenNIにあるPlayerのコールバック部分をラップしましてそれを実現しています。
Cの関数をコールバックするには、関数のアドレスが固定(非メンバ関数)である必要があります。非メンバ関数をクラス関数にして、渡したいメンバ関数があるthisポインタをわたし、クラス関数内でメンバ関数を呼び出す方法をとります。
実際には、OpenNIに登録するCの関数をstaticのEndOfFileReachedで用意し、cookieにPlayerCallbackへのポインタを渡すことで、メンバ関数のEndOfFileReachedへ渡すことができます。
class PlayerCallback { ... // ファイルの終端 static void XN_CALLBACK_TYPE EndOfFileReached( xn::ProductionNode& node, void* pCookie) { ((PlayerCallback*)pCookie)->EndOfFileReached( node ); } // ファイルの終端 virtual void EndOfFileReached( xn::ProductionNode& node ) { } ... };
関数の登録側では、クラス関数のEndOfFileReachedにコールバックをかけてもらい、cookieにコールバックしてほしいインスタンスを指定します。
class Player { ... void RegisterCallback( PlayerCallback* callback ) { XnStatus rc = player_.RegisterToEndOfFileReached( &PlayerCallback::EndOfFileReached, callback, callback_ ); ... } ... };
App
次は、コールバックされる側の実装です。
コールバックさせるクラスAppを実装します。このクラスにコールバックしてもらうため、::openni::PlayerCallbackを継承し、EndOfFileReachedを実装します。
class App : public ::openni::PlayerCallback { ... App() { // プレーヤーの作成と設定 ... player.RegisterCallback( this ); ... } ... // ファイルの終端 virtual void EndOfFileReached( xn::ProductionNode& node ) { std::cout << "ファイルの終端" << std::endl; } ... };
また、ファイルのフレームを進めるために、メインループとしてRunを実装し、playerから終端までデータを読むようにします。
class App : public ::openni::PlayerCallback { ... void Run() { // EOFまで回す while ( !player.GetPlayer().IsEOF() ) { context.WaitAndUpdateAll(); } } ... };
MockApp
AppのEndOfFileReachedをテストするために、Appを継承するMockAppを実装し、EndOfFileReachedをモックに指定します
// アプリケーションクラスのモック class MockApp : public App { ... MOCK_METHOD1( EndOfFileReached, void( xn::ProductionNode& node ) ); };
テスト
最後にテストコードです。
モックに指定した「EndOfFileReachedが1回呼び出されること」を確認するテストを書きます。
モック関数の成否判定はMockAppのデストラクト時に行われるようなので、モックの設定をしてメインループを呼び出します。
メインループでは、ファイルの終端までループする(コールバックが呼ばれるハズ)ので、MockAppのデストラクト時にはEndOfFileReachedが一回呼ばれるはずです。
TEST( PlayerTest, EndOfFileReached ) { using ::testing::_; MockApp app; // モックの設定 EXPECT_CALL( app, EndOfFileReached(_) ) .Times( 1 ); // ファイルの終端検出は1回呼び出される // .Times( 2 ); // 2回はNG app.Run(); }
実行
適当なoniファイルを用意し、RECORD_FILE_NAMEにファイル名を指定して実行します。
結果として一件のテストが成功します。
テストがなされているか確認するために、二回呼び出されていいるかのテストに変えると見事に失敗します。
これでコールバックが呼び出されているテストができました。
ユーザー研修や、ポーズの検出、骨格追跡のためのキャリブレーションなども、実データを用意することでテストが可能になるでしょう。
ただし、実データを使用するため、テスト時間が記録ファイルの記録時間に依存します。そのため、スローテストになるので、CIなどの際には分散などの工夫が必要になりそうです。