別のクラスの静的関数に依存するクラスのテスト
-
28-09-2019 - |
質問
私は現在、静的関数のみを持つ別のクラスを使用するクラスに取り組んでいます。
クラスをテストしようとするまで、すべてが正常に機能しました。
これが問題の簡単なコード例です。
class A {
static String getSometing() {
return String("Something: ") + heavyCalculation().asString();
}
}
class B {
B() {}
~B() {}
String runSomething(const String& name) {
if(name.equals("something")) {
return A::getSomething();
} else {
return "Invalid name!";
}
}
}
クラスAが正しく機能していると仮定します(およびその単体テストによってテストされています)、私はチェックしたいと思います 何をしてください クラスBの機能B
私の最初のオプションは、内部クラスのモックを作成することです(このサンプル - クラスA)が、その場合、静的関数のみがあるため、Aから継承するものは何も与えません。
私の2番目のオプションは、B内部のプライベート機能のクラスの呼び出しをカプセル化することです。そうすれば、戻り値を制御できます(ただし、このオプションを選択すると、もう少し複雑になります)。
あなたへの私の質問は: 現在のオプションよりも静的クラス/関数に依存するC ++クラスをテストするより良い方法がありますか?
前もって感謝します、
タル。
解決
私は、静的関数(私の場合は外部依存関係)への参照を新しいプライベート関数にリファクタリングし、テストしたスタブでそれらを上書きすることで、同様の状況で単体試験に成功しました。
リファクタル化された関数がプライベートのままである場合、それらは設計の複雑さに大きく影響を与えるべきではなく、コードの読みやすさに悪影響を与えないほど小さいはずです。
他のヒント
モノリシックテストスイートを使用していない場合は、簡単です。 a.cppにはクラスAとb.cppにクラスBがあり、Bのテストはb_test.cppにあると思います。
a_mock.cppというファイルを作成します
class A
{
static String getSometing() {
return String("Expected Something");
}
};
次に、b_testファイルをコンパイルするときは、aoではなくa_mock.oにリンクするだけです。
g++ -Wall B_test.cpp B.cpp A_mock.cpp
関数へのポインターをクラスAのコンストラクターに渡すことができます。テストのために、必要なことをすることができる模擬関数にポインターを渡すことができます。
なぜ静的関数?静的にしないことをお勧めします。
次に、インターフェイスという名前のクラスA(C ++では、純粋な仮想関数ヘッダーのみを持つクラスを意味します)のインターフェイスを作成できます。クラスAはインターフェイスを実装(継承)し、この仮想関数を実装します。
次に、このインターフェイスへのポインターをクラスBのコンストラクターに渡し、M_Aという名前のメンバー変数に保存します。次に、テストで、インターフェイスを実装するモッククラッサを作成します。モッククラッサをクラスBコンストラクターに渡し、M_Aを入力に設定します。
class AInterface
{
virtual String getSomething() = 0;
}
class A : public AInterface
{
String getSometing() {
return String("Something: ") + heavyCalculation().asString();
}
}
class B
{
B(AInterface A) : { m_A = A; }
~B() {}
String runSomething(const String& name) {
if(name.equals("something")) {
return m_A.getSomething();
} else {
return "Invalid name!";
}
}
AInterface m_A;
}
テストコード
class MockClassA : public AInterface
{
String getSometing() {
return String("Whatever I want. This is a test");
}
}
void test ()
{
// "MockClassA" would just be "A" in regular code
auto instanceOfB = B(MockClassA());
String returnValue = instanceOfB.runSomething("something");
:
:
}
「男、一部の人々はユニットテストをやりすぎています!」と言います。
2つのクラスを単一のユニットとしてテストするだけです。とにかく、クラスAはクラスBにハードコード化されています。
テンプレートを介してクラスを取得し、そのインスタンス化を明示的にエクスポートする必要があります(B<A>
)リンカーの問題は、以前にすべてのインラインで掲載されていなかった場合に避けるため。これにより、必要に応じてテストを目的として他のクラスを挿入でき、とにかく良い練習です。また、あなたの例がJavaのように見える理由についても興味があります。 だった 記載されているC ++。
template<typename T> class BImpl {
String runSomething(const String& name) {
if(name.equals("something")) {
return T::getSomething();
} else {
return "Invalid name!";
}
}
};
typedef BImpl<A> B; // Just plugs in to existing code.
これで、モッククラスをAに置き換えることができます。実際、これは別の方法で拡張可能です-CRTP。
class A : public BImpl<A> {
String getSomething() {
// Now it's non-static! IT'S A MIRACLE!
}
}
テンプレートの不思議は、決して私を驚かせることをやめません。