2011-06-30 10 views
9

私が持っている人のような単純な、ほとんど価値のようなクラス:スタティック/グローバル機能をモックする最も簡単な方法は?

class Person 
{ 
public: 
    Person(ThirdPartyClass *object); 
    virtual ~Person(void); 

    virtual std::string GetFullName() const; 
    virtual int GetAge() const; 
    virtual int GetNumberOfDaysTillBirthday() const; 
}; 

私はDestroy(サードパーティのライブラリの一部)と呼ばれるグローバル/静的な機能を持っているサードパーティのライブラリとThirdPartyClassニーズを使用していますそれを破壊するためにそれを求めた。このDestroy関数は、Personデストラクターで呼び出されます。

私はPersonクラスを単体テストしようとしていますが、私はDestroyメソッドを模擬/スタブする方法が必要です。私は静的Destroy関数の周りにラッパークラスを書くことができ、次に、このラッパーをPersonクラスに挿入するために依存関係注入を使用することができると思うが、この単純なクラスでこの1つの関数を呼び出すためには、これを行う簡単な方法は何ですか?それとも、依存性注入は本当にこれを行うための最良の方法ですか?

更新

最終的に私はすべてのサードパーティのライブラリのグローバル関数をラップするクラスを作成し、私のPersonクラスのコンストラクタにこのクラスを渡すために、依存性注入を使用して行くことにしました。この方法で、私はDestroyメソッドをスタブすることができます。 Personクラスはただ1つの関数しか使用していませんが、ライブラリの他の関数はコード内の他のポイントで呼び出され、それらをテストする必要があるため同じ問題に直面します。

メインのアプリケーションコードでこのラッパークラスのインスタンスを1つ作成し、必要に応じて注入します。私はそれがはっきりしていると思うので、私はこのルートに行くことを選んだ。私はBilly ONealのソリューションが好きで、私の質問に答えると思いますが、コードを数ヶ月間残しておけば、依存性注入と比較して何が起こっているか把握するのに時間がかかります。私はPythonの嫌疑の禅を思い出させている "明示的な暗黙のより良いです。私は依存性注入が起こっていることをもう少し明示していると感じています。

+0

静的/グローバル関数をスタブとして作成して呼び出すだけで何が問題になりますか? – littleadv

+0

@ littadadv:単なる単体テストになっていますが、私が理解していることは、あなたがテストするクラスをテストするためだけに変更したくないということです。だから私が正しく理解したら、スタブを作成してメソッドを破棄し、それを私のPersonクラスで使用して、自分のPersonクラスを変更して、テストバージョンとプロダクションバージョンを何とか切り替える必要があります。 – User

+0

@User - あなたがテストしているクラスを変更しないで、スタブとして独自の 'ThirdPartyClass'を実装してください。 – littleadv

答えて

8

リンクシームを作成します。

// Destroy.cpp 
void Destroy() 
{ 
    //Code that really does destruction 
} 

とテストのために:あなたのヘッダーで宣言を破壊した後、2つの実装ファイルを持って入れ

// DestroyFake.cpp 
void Destroy() 
{ 
    //Code that does fake destruction 
} 

次に、あなたのメインバイナリ、そしてあなたのテストの2番目のファイルへの最初のファイルをリンクバイナリ。

これ以外にも、あなたが求めるものは不可能です。リンカーは、グローバル関数と静的メソッドの呼び出しを呼び出して、呼び出し先コードに直接ジャンプさせます。探しているもののようなオーバーロードを作成するためのルックアップや決定プロセスはありません(Destroy以上簡単に嘲笑)。

+0

Destroyの2つのバージョンの2つの独立した静的なlibプロジェクトを作成することを意味しますか?それは私がリンクする方法を知っているすべてです..またはあなたはファイルをリンクするための別のアイデアを念頭に置いていましたか? – User

+0

@ユーザー:共通のコードを静的なlibに入れます。最初のものをメインのEXEプロジェクトに入れ、第2のものをテストEXEプロジェクトに入れます。これらの2つを静的ライブラリに分割する必要はありません。 –

+0

そして、Destroyヘッダーファイルはどこに行きますか? – User

0

私はちょうどここで遊んだけど、あなたのために働くかもしれない一つのアプローチは、あなた自身のdestroy機能を提供し、Third_Party_Libポインタの周りにラッパークラスを追加することによって、それを支持して呼び出しを明確にすることです...

#include <iostream> 

namespace Third_Party_Lib 
{ 
    struct X { }; 
    void destroy(X*) { std::cout << "Third_Party_Lib::destroy()\n"; } 
} 

template <typename T> 
struct Wrap 
{ 
    Wrap(const T& t) : t_(t) { } 
    operator T&() { return t_; } 
    operator const T&() const { return t_; } 
    T t_; 
}; 

namespace Mine 
{ 

#if TEST_MODE 
    // this destroy will be called because it's a better match 
    // not needing Wrap::operator T&... 
    void destroy(Wrap<Third_Party_Lib::X*>) { std::cout << "Mine::destroy()\n"; } 
#endif 

    struct Q 
    { 
     Q(Third_Party_Lib::X* p) : p_(p) { } 
     ~Q() { destroy(Wrap<Third_Party_Lib::X*>(p_)); } 
     Third_Party_Lib::X* p_; 
    }; 
} 

#if TEST_MODE  
int main() 
{ 
    Mine::Q q(new Third_Party_Lib::X); 
} 
#endif 
0

関数ポインタは、別の実装を置換する方法を作成します。さらに、発信者には透過的です(sizeof(funcname)のように何かをやっていない限り)。

2

シンプル、Typemock Isolator++(免責事項 - 私はそこに働く)を使用Destroy

グローバル使用モック/偽のテストで1行を追加します

を:

FAKE_GLOBAL(Destroy); 

公共静使用:

WHEN_CALLED(Third_Party::Destroy()).Ignore(); 

私的使用統計:

PRIVATE_WHEN_CALLED(Third_Party::Destroy).Ignore(); 

条件コードなし/異なるリンクなし。

関連する問題