私はレガシー C++ コードベースで作業しており、ファイルシステムなどに多くの外部依存関係があるDependsOnUgly
大きなクラス (既存のコードをできるだけ変更せずに、テスト中Ugly
の少なくともいくつかのメソッド。DependsOnUgly
コードを大幅に変更しない限り、ファクトリ メソッド、メソッド パラメータ、またはコンストラクタ パラメータによってシームを作成する方法はありません。Ugly
は具体的なクラスであり、抽象基本クラスを一切使用せずに直接依存しており、非常に多くのメソッドがあり、その中にはほとんどまたはまったくマークされていないvirtual
ため、完全にモックするのは非常に骨の折れる作業です。利用可能なモック フレームワークはありませんが、取得したいDependsOnUgly
テスト中なので、変更を加えることができます。Ugly
のメソッドを単体テストするために の外部依存関係を壊すにはどうすればよいDependsOnUgly
ですか?
1 に答える
私がプリプロセッサ モックと呼んでいるもの、つまりプリプロセッサ シームを介して挿入されたモックを使用します。
この概念を最初に 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: Ugly
usedのすべての動作を置き換える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: Ugly
used 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
これらの方法はどちらも少し不安定であり、注意して使用する必要があることに注意してください。より多くのコードベースがテスト中であり、可能であれば依存関係を壊すためのより標準的な手段に置き換える必要があるため、それらを遠ざける必要があります。従来のコードベースのインクルード ディレクティブが非常に乱雑である場合、両方が無効になる可能性があることに注意してください。ただし、実際のレガシーシステムでは両方ともうまく使用しているので、機能することはわかっています。