私はいくつかのグループに適合するようにクラスを設計したアプリケーションに取り組んでいます:
イミュータブルの単体テストは簡単です。コンストラクターを通じて依存性注入を使用できます。これは、テストクラスを入れ替えて、(統合テストではなく)単体テストであることを確認できることを意味します。ビルダーを使用して、プロダクションオブジェクトを構築できます。私のデザインのこの要素は、テストと製造の点で満足しています。
しかし、私のサービスでは、メソッドの引数を介して依存性注入を使用することによってのみ、これらのクラスをステートレスでユニットテスト可能に保つことができるようです。
私のサービスでは、関数の例が次のように変更されます。
virtual unsigned long foo() const override final;
...に:
virtual unsigned long foo(const ISomeInterface & dependency) const override final;
つまり、サービスクラスはテスト可能ですが、本番環境でコードを使用する場合は、クラス外の依存関係をインスタンス化する必要があります。例えば:
// without dependency injection
Service service;
return service.foo();
// with dependency injection
Service service;
Dependency dependency;
return service.foo(dependency);
これにより、多数のサービスクラスで、各クラスメソッドに少なくとも1つ以上の引数が必要になりました。注-これは私のコードで現在使用しているアプローチです。
私の質問はこれです:
この形式の依存性注入に代わるものはありますか?
注-オブジェクト間の実際の依存関係をテストする統合テストも実行しています。
依存関係の追加のコンストラクタ引数が気に入らない場合は、インスタンスの作成を処理するDIコンテナが必要です。
既存のdi-containerフレームワークを使用するか、自分で貧弱なバージョンを実装することができます
public PoorMansDiContainer : IPoorMansDiContainer {
private IService mService = null;
private IFooService mFooService = null;
public IService getSingletonService() {
if (mService == null) {
mService = new ServiceImpl(getSingletonFooService());
}
return mService;
}
public IFooService getSingletonFooService() {
if (mFooService == null) {
mFooService = new FooServiceImpl();
}
return mFooService;
}
}
テンプレートを使用して、依存関係を直接参照するようにステートレスクラスを構成できます。例として:
// a dependency interface...
class ILogger
{
public:
virtual void logSomething (std::string message);
};
// an interface for a service
class IMyService
{
public:
virtual std::unique_ptr<MyClass> getMyObject () = 0;
};
template <LogProvider>
class MyServiceImpl : public IMyService
{
public:
virtual std::unique_ptr<MyClass> getMyObject ()
{
return LogProvider::logger->logSomething ("creating my object");
return std::make_unique<MyClass> ();
}
};
// at global level
struct GetLogger
{
static std::shared_ptr<ILogger> logger;
}
// initialisation code...
GetLogger::logger = // ... make a concrete logger here
std::unique_ptr<IMyServce> myService =
std::make_unique<MyServiceImpl<GetLogger>> ();
次に、ロガーを含む任意のクラスを使用して、MyServiceImplのインスタンスを作成できます。このアプローチはグローバルデータを作成しますが、間接的に使用するだけなので、問題は発生しません。
とはいえ、私は個人的にはステートレスサービスクラスの概念を放棄し、代わりに不変のサービスクラスに切り替えます。
デフォルトの引数を使用して、ステートレスクラスをクリーンアップできました。例えば、
virtual unsigned long foo(
const ISomeInterface & dependency = ProductionDependency()) const override final;
元の質問の基準をすべて満たしているようです...
単体テストステートレスクラス(依存関係なし)
引数を指定してユニットテストを実行できます(たとえば、偽のテストオブジェクトを渡すなど)。
// unit test snippet
Service service;
TestDependency dependency;
const auto actual = service.foo(dependency);
これらのクラスをステートレスに保つ
ステートレスクラスは依然としてデフォルトのコンストラクターのみを使用し、データメンバーはありません。
依存関係をインスタンス化するコードを減らす/隠す(特にオブジェクトに複数の依存関係がある場合)
次の変更を加える必要がありました。
ただし、これらの依存関係をクライアントコードに含める必要はなくなりました。例えば、
// client code snippet
Service service;
return service.foo();
これは、Serviceクラスに複数の依存関係がある場合でも同じです。デフォルトの引数を使用している限り、上記と同じスニペットを使用できます。クライアントは依存関係について知る必要がなくなりました(より具体的には、デフォルトの引数を使用して依存関係オブジェクトを解放するため、依存関係オブジェクトをインスタンス化する必要があります)。
強力なサービスロケータを使用します。これは、Martin Fowlerが説明するように、DIとほぼ機能的に同等です。次に、テストが実行されているコンテキストで、テストの期間中、任意のモックにサービスを設定します。例えば(C#コード、申し訳ありません)
public class Service()
{
public void Foo()
{
...
IDependency dependency = ServiceLocator.Get<IDependency>();
...
}
}
テスト用
ServiceLocator.SetForThisThread<IDependency>(typeof(MockDependency));
RunTest();
ServiceLocatorへの依存関係を作成していると不平を言いたい場合は、それが問題になる可能性があることを理解したいと思います。