条件付きコンパイルは単体テストの有効なモック/スタブ戦略ですか?
-
01-07-2019 - |
質問
スタブに関する最近の質問では、多くの回答がスタブを実装するための C# インターフェイスまたはデリゲートを示唆していましたが、 一つの答え 条件付きコンパイルを使用して、実稼働コードで静的バインディングを保持することを提案します。この回答は読んだ時点で -2 変更されたため、少なくとも 2 人がこれは本当に問題だと考えていました。 間違っている 答え。おそらく DEBUG の誤用が原因か、より広範な検証ではなく固定値の使用が原因である可能性があります。しかし、私は次のような疑問を抱かずにはいられません。
条件付きコンパイルの使用は、単体テスト スタブを実装するための不適切な手法ですか?時々?いつも?
ありがとう。
編集-追加: 実験として例を追加したいと思います。
class Foo {
public Foo() { .. }
private DateTime Now {
get {
#if UNITTEST_Foo
return Stub_DateTime.Now;
#else
return DateTime.Now;
#endif
}
}
// .. rest of Foo members
}
と比較して
interface IDateTimeStrategy {
DateTime Now { get; }
}
class ProductionDateTimeStrategy : IDateTimeStrategy {
public DateTime Now { get { return DateTime.Now; } }
}
class Foo {
public Foo() : Foo(new ProductionDateTimeStrategy()) {}
public Foo(IDateTimeStrategy s) { datetimeStrategy = s; .. }
private IDateTime_Strategy datetimeStrategy;
private DateTime Now { get { return datetimeStrategy.Now; } }
}
これにより、「DateTime.Now」への送信依存関係を C# インターフェイス経由でスタブ化できるようになります。ただし、静的で十分な動的ディスパッチ呼び出しを追加し、実稼働バージョンでもオブジェクトが大きくなり、Foo のコンストラクターに新しい失敗パス (割り当てが失敗する可能性がある) を追加しました。
ここでは何も心配していませんか?これまでのフィードバックありがとうございます!
解決
実稼働コードをテストコードから分離するようにしてください。異なるフォルダー階層を維持します。さまざまなソリューション/プロジェクト。
ない限り..あなたはレガシー C++ コードの世界にいます。ここでは何でもありです。条件付きブロックがコードの一部をテスト可能にするのに役立ち、メリットがある場合。ぜひやってみてください。ただし、初期状態よりも汚くならないように注意してください。条件ブロックを明確にコメントし、区切ります。慎重に作業を進めてください。これは、テスト ハーネスでレガシー コードを取得するための有効な手法です。
他のヒント
コードをレビューする人にとってはわかりやすさが損なわれると思います。コンテキストを理解するために、特定のコードの周囲にコンディショナル タグがあることを覚えておく必要はありません。
いや、これはひどいですね。本番コードにテストが漏れます (条件がオフになっている場合でも)
悪い悪い。
テスト コードは明白である必要があり、テスト コードと同じブロック内に混在してはなりません。
これは書いてはいけない理由とほぼ同じです
if (globals.isTest)
これがひどいことになる別の理由を考えてみました。
何かをモック/スタブする場合は、テスト内容に応じてそのメソッドが異なる結果を返すようにすることがよくあります。これはそれを妨げるか、まったく厄介なものにしてしまいます。
これは、大規模なコード ベースでのテスト容易性をリファクタリングするときに頼れるツールとして役立つ可能性があります。このようなテクニックを使用して、小さな変更を可能にし、「ビッグバン」リファクタリングを回避する方法がわかります。ただし、私はそのようなテクニックに頼りすぎることを心配し、そのようなトリックがコード ベース内であまり長く存続しないように努めます。そうしないと、アプリケーション コードが非常に複雑で理解しにくくなる危険があります。