非常に大規模で非常にレガシーなプロジェクトをテスト可能にしようとしています。
ほとんどのコードで使用する、静的に利用可能なサービスが多数あります。問題は、これらをモックするのが難しいことです。以前はシングルトンでした。現在、それらは疑似シングルトンです。同じ静的インターフェイスですが、関数は切り替え可能なインスタンス オブジェクトに委任されます。このような:
class ServiceEveryoneNeeds
{
public static IImplementation _implementation = new RealImplementation();
public IEnumerable<FooBar> GetAllTheThings() { return _implementation.GetAllTheThings(); }
}
今私の単体テストで:
void MyTest()
{
ServiceEveryoneNeeds._implementation = new MockImplementation();
}
ここまでは順調ですね。製品では、1 つの実装のみが必要です。ただし、テストは並行して実行され、別のモックが必要になる可能性があるため、次のようにしました。
class Dependencies
{
//set this in prod to the real impl
public static IImplementation _realImplementation;
//unit tests set these
[ThreadStatic]
public static IImplementation _mock;
public static IImplementation TheImplementation
{ get {return _realImplementation ?? _mock; } }
public static void Cleanup() { _mock = null; }
}
その後:
class ServiceEveryoneNeeds
{
static IImplementation GetImpl() { return Dependencies.TheImplementation; }
public static IEnumerable<FooBar> GetAllTheThings() {return GetImpl().GetAllTheThings(); }
}
//and
void MyTest()
{
Dependencies._mock = new BestMockEver();
//test
Dependencies.Cleanup();
}
これらのサービスを必要とするすべてのクラスにコンストラクターが注入するのは大規模なプロジェクトであるため、この方法を採用しました。同時に、これらはほとんどの機能が依存するコードベース内のユニバーサル サービスです。
このパターンは、依存関係を明示的にするコンストラクター注入とは対照的に、依存関係を隠すという意味で悪いことを理解しています。
ただし、利点は次のとおりです。
- 3 か月のリファクタリングを行ってから単体テストを行うのに対して、すぐに単体テストを開始できます。
- グローバルはまだありますが、以前よりも確実に改善されているようです。
私たちの依存関係はまだ暗黙的ですが、このアプローチは私たちが持っていたものよりも厳密に優れていると主張します. 隠れた依存関係は別として、これは適切な DI コンテナーを使用するよりも悪いことですか? どのような問題が発生しますか?