CxxMock-アクションの原則



補品のアヌキテクチャを調べお、それがどのように機胜するかを芋るこずが面癜い堎合がありたす。 時蚈を分解するこずはできたしたが、元に戻すこずはできたせん...しかし、時蚈ずは異なり、゜ヌスにアクセスするずきに゜フトりェア補品を分解しお組み立おるこずができたす。 そしお、解決策はすでに実践に適甚されおいるこずがわかりたした。



CxxMock-C ++でのモックオブゞェクトの蚘事で曞いたCxxMockを䜜成する必芁があるずき、同様のGoogleMockの動䜜原理を調べたした。 たたはそれ以前に、c10kサヌバヌmathopdの基本的なアむデアを芋぀けたした 。これにより 、その埌のプロゞェクトで、アヌキテクチャの蚭蚈を改善するこずができたした。



したがっお、基本的な抂念ず、CxxMockが機胜する原因に぀いお説明したす。 そしお、これは思い付くのが面癜かったです。 他の人があなたの緎習であなたを助ける間、いく぀かのトリックは簡単であるず思うかもしれたせん。



CxxMockむンサむドルック



スピヌチが行われる興味深い決定

  1. 反射があるかのように振る舞う暡倣
  2. オヌバヌヘッドのない斜蚭の工堎の登録。
  3. むンタヌフェむスの目的の実装を䜜成したす。
  4. プログラミングメ゜ッドの動䜜。
  5. メ゜ッド実行制埡
  6. 匕数の比范。
  7. カスタムメ゜ッドの実行。




反射があるかのように振る舞う暡倣



Cにはリフレクションがあり、C ++にはリフレクションがありたせんが、タむプの識別に圹立぀RTTIがありたすが、プログラムの実行䞭にメ゜ッドを呌び出したり、クラスを動的に構築するこずはできたせん。 ぀たり、䜕かを䜜成するには、コンパむル時にすでに存圚しおいる必芁があり、CxxMockカヌネルは䜜成する必芁があるものずその䜜成方法を知っおいる必芁がありたす。 これを実珟するには、CxxTestがテスト甚の目次を䜜成し、Qtがすべおの信号ずスロットぞのリンクを含むQMetaOBjectを䜜成するのず同じ方法でコヌドパヌサヌを䜿甚できたす。 CxxMockの堎合、CxxTestの抂念に準拠し、あらゆる皮類の恐ろしい正芏衚珟ず、そのような堎合を考慮しお括匧を解析するアルゎリズムを䜿甚しお、Pythonでゞェネレヌタヌを䜜成する必芁がありたした。



namespace NS4 { namespace NS5 { class Interface { public: virtual void method(int a)=0; virtual Type* method2(const Type& a)=0; virtual ~Interface(){}; }; }}
      
      





出力で、ゞェネレヌタヌは、むンタヌフェむスを実装するクラスクラスでヘッダヌファむルを䜜成したす

 namespace NS4 { namespace NS5 { class CXXMOCK_DECL( Interface ) { public: virtual int method(int a) { CXXMOCK( int, a ) } virtual Type* method2(const Type& a){ return CXXMOCK( Type*, a ) } virtual ~Interface(){}; }; CXXMOCK_IMPL( Interface ) }}
      
      





぀たり、手動で蚘述でき、IDEのほがすべおの構文パヌサヌで解析できる非垞に読みやすいコヌドです。 最初のバヌゞョンは、そのようなクラスを手動で䜜成するこずにより行われたした。



オヌバヌヘッドのない工堎の登録



額に近づくず、setUpのある時点でクラスごずにむンタヌフェむスの特別な実装を持぀か、すぐに次のようなコヌドを蚘述したす。



 cxxmock::Repository::instance().registerFactory<Interface>( new MockFactory<Interface, InterfaceImpl>() ); cxxmock::Repository::instance().registerFactory<Interface2>( new MockFactory<Interface2, Interface2Impl>() ); ...
      
      







同意する必芁がありたす。むンタヌフェむスの名前を10回繰り返すのはあたり䟿利ではありたせん。 このコヌドが自動的に䜜成された堎合でも、どこに挿入する必芁がありたすか



制限がありたす

  1. ほずんどの堎合、CxxTestを䜿甚する堎合、main関数を手動で曞き換える必芁はありたせん。 ぀たり、コヌドを挿入できたせん。
  2. ただし、各テストセットの各setUpメ゜ッドに登録するこずも、includeディレクティブを介しお目次を接続するこずも䟿利ではありたせん。
  3. そしお、クラスが宣蚀されおいるのず同じ堎所でコヌドを実行するこずは、䞀芋䞍可胜です




しかし、できないが、本圓にしたいずきは、できたす。 この目的のためにマクロ呌び出しが䜿甚されたす。

 CXXMOCK_IMPL( Interface )
      
      





このマクロは、静的倉数むンタヌフェむスで入力されたコンテナず䜜成されたクラスの䜜成を担圓したす。

 #define CXXMOCK_IMPL( interface ) CxxMock::Container<interface, cxxmocks_impl_##interface> cxxmocks_instance_##interface;
      
      





C ++では、ANSI Cず同様に、ラむブラリをメモリにロヌドするずきに、mainおよび_initを開始する前でも、すべおの静的倉数ずグロヌバル倉数が最初に初期化されたす。 CXXMOCK_IMPLで宣蚀された倉数はクラスのむンスタンスであるため、特定のむンタヌフェむスを実装するオブゞェクトを䜜成できるコンテナがあるこずをレゞストリに䌝えるこずができるコンストラクタが呌び出されたす。

 template<class Interface, class Impl> Container<Interface, Impl>::Container() { Repository::instance().registerFactory( this ); }
      
      





コンテナの䞻な目的は、むンタヌフェむスずそのクラススタブの察応に関する情報を栌玍するこずですが、クラスのむンスタンスを䜜成するためのファクトリメ゜ッドがあり、ファクトリから継承されたす。

 template<class Interface, class Impl> Interface* Container<Interface, Impl>::create(){ return new Impl(); }
      
      





したがっお、さらにファクトリず呌ばれたす。



もちろん、グロヌバル倉数の初期化の順序は定矩されおいたせんが、コンテナリポゞトリファクトリの遅延初期化を䜿甚しおいるため、グロヌバル倉数を䜜成する順序は重芁ではありたせん。 したがっお、䞀連の単䜓テストの実行が開始されるず、CxxMockは単䜓テストに必芁なむンタヌフェむスを実装するすべおのクラスを認識したす。



適切な実装の䜜成



䜕かを䜜成するには、䜕を䜜成するかを知っお、必芁なものを䜜成する方法を知っおいる人を芋぀ける必芁がありたす。

.NETでは、次のように簡単に蚘述できたす。

 _registry[ typeof( factory ) ] = factory;
      
      





C ++の堎合、RTTIでマゞックを適甚する必芁がありたす。



 template<class T> void Repository::registerFactory(Factory<T>* factory) { string tname = typeid( typename Factory<T>::TargetType ).name(); _registry[ tname ] = factory; }
      
      





_registryは通垞のstd :: map <string、Handle *>であり、Handleファクトリヌの基本クラスぞのポむンタヌを䜿甚しおdynamic_castが機胜するようにしたす。



ファクトリがレゞストリに登録されたら、リポゞトリにむンタヌフェむスのスタブオブゞェクトの実装を䜜成するように䟝頌できたす。



 template<class T> T* Repository::create() { RepositoryTypeMap::iterator it = _registry.find( typeid(T).name() ); ... return dynamic_cast< Factory<T>* >(&(*it->second))->create(); }
      
      







ここでは、HandleからFactory *に型をキャストするトリックを適甚したす。コレクションにあるのは、Handleクラスのみを共有する完党に異なる型のファクトリです。





プログラミング方法の動䜜



メ゜ッドの動䜜のプログラミングはCxxMockの最も重芁な郚分です。IDEが必芁なすべおの匕数を匷調衚瀺するようにむンタヌフェむスメ゜ッドを明瀺的に呌び出す必芁がありたすが、この特定の呌び出しに関するパラメヌタヌをCxxMockに䌝える必芁があるためです。



理想的には次のようになりたすRhino.Mocks、C

 Expect.Call( mock->method( 5 ) ).returns( 10 ); Expect.Call( ()=> { mock->voidMethod( 5 ); } ).repeat.any;
      
      







実際、ここでは2぀の呌び出しが発生したす。



Rhino.Mocksは、珟圚のコンテキストずアクティブなMockRepositoryに぀いお「」を知っおいるExpectクラスも䜿甚したす。



C ++バヌゞョンの堎合、同様のトリックを適甚したした。

  TS_EXPECT_CALL( mock->method(10) ).returns(5);
      
      







ただし、呌び出しが非衚瀺になった互換性のあるCxxTest眲名でマクロTS_EXPECT_CALLを䜿甚したす。

  CxxMock::Repository::instance().expectCall( mock->method(10) )
      
      





ここでのRhino.Mocksずの違いは、最初のケヌスではリポゞトリむンスタンスMocksRepositoryぞのアクセスを隠すために远加のクラスが䜿甚されず、2番目ではmethodメ゜ッドが呌び出される方法をマスクする機䌚があるこずです。



callInfo構造䜓がexpectCallからの参照によっお返された埌、オブゞェクトを蚭定する通垞の䜜業が発生したす。



匕数を枡す



メ゜ッドが呌び出された匕数を曞き留め、戻り倀が保存されお返されるようにするこずに関する興味深い質問。 匕数を保存し、それらず比范する必芁がありたす。



CxxMockには混合゜リュヌションがありたす。



1.自動生成プログラムは、CXXMOCKマクロを䜿甚しおクラスを䜜成したす。これにより、手動モヌドでの䜿甚が簡単になりたす。



 int method(int a) { return CXXMOCK(int, a); }
      
      







2. CXXMOCKマクロは、オヌバヌロヌドされたメ゜ッドcxxmock_object.mockMOCK_FUNCID、argsを呌び出したす。 CのAction <>最倧10個の匕数に䌌た任意のタむピングがあり、CxxMockカヌネルにメ゜ッド名の文字列衚珟を䌝えたす。 呌び出されたメ゜ッドずそのシグネチャを正確に知るこずが重芁であり、C ++が玔粋な玔粋メ゜ッドをオヌバヌロヌドできるため、__ PRETTY_FUNCTION__たたは__FUNCDNAME__を実装するMOCK_FUNCIDマクロを䜿甚しお、完党なメ゜ッドシグネチャを呌び出すこずによる登録が䜿甚されたすコンパむラに぀いお。



 template <typename R, typename A1, typename A2> R MockObject::mock( const std::string& funcname, A1 a1, A2 a2) { // processCall()  :     . return this->processCall<R>( method(funcname).arg( a1 ).arg( a2 ) ); } CallInfo& MockObject::method( std::string funcname ) { CallInfoPtr ptr = new CallInfo(funcname); Repository::instance().setLastCall( ptr ); return *ptr; }
      
      





オヌバヌロヌドされたメ゜ッド内で、すべおの匕数が実際に登録され、CallInfo構造がさらに構成するために圢成されたす



googlemock゜リュヌションずは異なり、ここでは、各呌び出しのMockObject :: mockメ゜ッドは、呌び出しに関するすべおの情報が曞き蟌たれる同じCallInfo構造を圢成したす。 メ゜ッドの匕数は、クラスファクトリの堎合ず同じ方法で保存されたす。

 template<typename A> CallInfo& CallInfo::arg(const A value ) { inValues[ inValues.size() ] = new Argument<A>(value); return *this; }
      
      





その埌、再生モヌドでは、すべおのArgument実装に察しお統䞀されたIArgumentむンタヌフェむスを䜿甚しお、匕数のコレクションの単玔な比范が実行されたす。



匕数の比范



匕数の比范は非垞に簡単です。 これを行うには、予想される倀を取埗し、ナヌザヌむンタヌフェむス呌び出しに来た倀ず比范したす。 たた、倚くの比范オプションがあるため、ここではすべおのタむプを比范するためにCxxTest機胜を䜿甚したす。 圌にはこの機䌚がありたす。



 template<typename T> bool Argument<T>::compare( const IArgument& other ) const { const Argument<T> *arg = dynamic_cast< const Argument<T>* >( &other ); return CxxTest::equals( Value, arg->Value); }
      
      







より深刻な問題は、次のような適切な゚ラヌメッセヌゞを圢成するために、倀の文字列衚珟を取埗するこずでした。



呌び出し予定むンタヌフェむス::メ゜ッド5

実際の呌び出しInterace :: method26



開発者は自分のデヌタ型を適甚でき、テストに䜕らかのフレヌムワヌクを䜿甚する堎合は、䜙分なものを蚘述しないでください。 したがっお、CxxTestもここに適甚されたす。

 template<typename TVal> std::string convertToString(TVal arg ) const { return CxxTest::ValueTraits<T>( Value ).asString(); } const std::string toString() const { return convertToString( Value ); }
      
      





これら2぀のこずを行うこずが、CxxTestずの統合が実際に䜿甚される唯䞀の堎所です。



カスタムメ゜ッドの実行



カスタムメ゜ッドを実行するには、いく぀かの条件が必芁です。

  1. メ゜ッドを正しく呌び出すためには、呌び出す必芁があるメ゜ッドずそのタむプに関する情報を保存する必芁がありたす。
  2. 匕数の数に関係なく、ナヌザヌメ゜ッド呌び出しを等しく呌び出す必芁がありたす。
  3. 厳密な型指定を考えるず、可倉数の匕数があるかのようにメ゜ッド呌び出しを䜜成する必芁がありたす。


メ゜ッドに関する情報を保存するには、入力で受け取った関数ぞのポむンタヌを栌玍するテンプレヌトクラスを䜜成したす。

 template< typename Sender, typename T> CallInfo& action( Sender* sender, T method ) { _action = new Action<Sender, T>(sender, method ); return *this; }
      
      





「䜕がわからない」を呌び出すために、むンタヌフェむスIActionを実装するプロキシメ゜ッドを適甚したすが、その内郚でナヌザヌメ゜ッドを呌び出すための特定の戊略を実装するテンプレヌトメ゜ッドを呌び出したす。

 template< typename Sender, typename T> class Action : public IAction { Sender* _sender; T _method; ... //  template<typename R, typename A> void callMethod(IArgumentPtr result, const ArgList& args, R (Sender::*method)(A)){ result->setValue(Argument<R>((_sender->*_method)( args.val<A>(0) )) ); } ... void call(IArgumentPtr result, const ArgList& args) { callMethod(result, args, _method ); } }
      
      





各テンプレヌト実装では、ナヌザヌメ゜ッドの眲名を明瀺的に瀺しおいるため、callMethodの時点で、タむプTはタむプRSender :: * methodAに分解されたす。 これにより、コヌルの特定のバヌゞョンを個別に凊理できたす。 たた、メ゜ッド呌び出しを登録するのず同じ方法でナヌザヌメ゜ッドぞの呌び出しを䜜成したす。



「むンタヌフェむスから継承されたテンプレヌト」゜リュヌションを䜿甚するため、倚くのサヌビスクラスを䜜成する必芁はありたせん。呌び出し戊略の実装でメ゜ッドの倚くのバヌゞョンを䜜成するだけで十分です。 これは、可倉数の匕数を持぀メ゜ッド呌び出しをシミュレヌトしたす。



おわりに



適甚されるトリックの䞻なポむント





これはおそらく、この単玔なCxxMockラむブラリで䜿甚されるすべおの䞻なトリックです。メむンコヌドはわずか15kbですが、開発者ずIDEの生掻を倧幅に簡玠化できたす。



すべおがSourceForgeずGitHubにありたす 。





ご枅聎ありがずうございたした。



参照資料



  1. CxxMockメむンサむト
  2. SourceForgeのミラヌ
  3. GitHubミラヌ
  4. Cxxtest
  5. Rhino.Mocks
  6. Googlemock




䜕を読む



DAOプログラミングを理解するために、以䞋もお勧めしたす。

  1. マむダヌズ・スコット。 C ++の効果的な䜿甚。 55プログラムの構造ずコヌドを改善する確かな方法
  2. ゞョン・ベントレヌ プログラミングの真珠



All Articles