8

私はレガシー C++ コードベースで作業しており、ファイルシステムなどに多くの外部依存関係があるDependsOnUgly大きなクラス (既存のコードをできるだけ変更せずに、テスト中Uglyの少なくともいくつかのメソッド。DependsOnUglyコードを大幅に変更しない限り、ファクトリ メソッド、メソッド パラメータ、またはコンストラクタ パラメータによってシームを作成する方法はありません。Uglyは具体的なクラスであり、抽象基本クラスを一切使用せずに直接依存しており、非常に多くのメソッドがあり、その中にはほとんどまたはまったくマークされていないvirtualため、完全にモックするのは非常に骨の折れる作業です。利用可能なモック フレームワークはありませんが、取得したいDependsOnUglyテスト中なので、変更を加えることができます。Uglyのメソッドを単体テストするために の外部依存関係を壊すにはどうすればよいDependsOnUglyですか?

4

1 に答える 1

10

私がプリプロセッサ モックと呼んでいるもの、つまりプリプロセッサ シームを介して挿入されたモックを使用します。

この概念を最初に Programmers.SE のthis questionに投稿しましたが、その回答から、これはあまり知られていないパターンであると判断したので、共有する必要があると考えました。これまで誰もこのようなことをしたことがないとは信じがたいですが、文書化されていないので、コミュニティと共有しようと思いました.

Ugly例として、との概念的な実装をNotAsUgly次に示します。

DependsOnUgly.hpp

#ifndef _DEPENDS_ON_UGLY_HPP_
#define _DEPENDS_ON_UGLY_HPP_
#include <string>
#include "Ugly.hpp"
class DependsOnUgly {
public:
    std::string getDescription() {
        return "Depends on " + Ugly().getName();
    }
};
#endif

醜い.hpp

#ifndef _UGLY_HPP_
#define _UGLY_HPP_
struct Ugly {
    double a, b, ..., z;
    void extraneousFunction { ... }
    std::string getName() { return "Ugly"; }
};
#endif

2 つの基本的なバリエーションがあります。1 つ目は、 によって の特定のメソッドのみUglyが呼び出されDependsOnUgly、それらのメソッドを既にモックしたい場合です。2つ目は

テクニック 1: Uglyusedのすべての動作を置き換えるDependsOnUgly

モックはモックされるクラスのインターフェースの必要な部分のみを実装するため、私はこの手法をプリプロセッサ部分モックと呼んでいます。モック クラスのヘッダー ファイルでプロダクション クラスと同じ名前のインクルード ガードを使用して、プロダクション クラスを定義せずにモックを定義します。の前に必ずモックを含めてくださいDependsOnUgly.hpp

(私のテスト ファイルの例は自己検証型ではないことに注意してください。これは単純にするためであり、単体テスト フレームワークに依存しないようにするためです。焦点は、実際のテスト メソッド自体ではなく、ファイルの上部にあるディレクティブにあります。 .)

test.cpp

#include <iostream>
#include "NotAsUgly.hpp"
#include "DependsOnUgly.hpp"
int main() {
    std::cout << DependsOnUgly().getDescription() << std::endl;
}

NotAsUgly.hpp

#ifndef _UGLY_HPP_ // Same name as in Ugly.hpp---deliberately!
#define _UGLY_HPP_
struct Ugly { // Once again, duplicate name is deliberate
    std::string getName() { return "not as ugly"; } // All that DependsOnUgly depends on
};
#endif

テクニック 2: Uglyused byの動作の一部を置き換えるDependsOnUgly

これをSubclassed-in-Place Mockと呼んでいます。この場合、Uglyはサブクラス化され、必要なメソッドはオーバーライドされますが、他のメソッドは引き続き使用できますが、サブクラスの名前はUgly. define ディレクティブは、名前を に変更Uglyするために使用されBaseUglyます。次に undefine ディレクティブが使用され、モックUglyサブクラスが使用されBaseUglyます。Ugly正確な状況によっては、何かを仮想としてマークする必要がある場合があることに注意してください。

test.cpp

#include <iostream>
#define Ugly BaseUgly
#include "Ugly.hpp"
#undef Ugly
#include "NotAsUgly.hpp"
#include "DependsOnUgly.hpp"
int main() {
    std::cout << DependsOnUgly().getDescription() << std::endl;
}

NotAsUgly.hpp

#ifndef _UGLY_HPP_ // Same name as in Ugly.hpp---deliberately!
#define _UGLY_HPP_
struct Ugly: public BaseUgly { // Once again, duplicate name is deliberate
    std::string getName() { return "not as ugly"; }
};
#endif

これらの方法はどちらも少し不安定であり、注意して使用する必要があることに注意してください。より多くのコードベースがテスト中であり、可能であれば依存関係を壊すためのより標準的な手段に置き換える必要があるため、それらを遠ざける必要があります。従来のコードベースのインクルード ディレクティブが非常に乱雑である場合、両方が無効になる可能性があることに注意してください。ただし、実際のレガシーシステムでは両方ともうまく使用しているので、機能することはわかっています。

于 2013-04-15T19:44:11.247 に答える