unit-testing
依存性注入
サーチ…
備考
ソフトウェアを書くことに取り組むことができる1つのアプローチは、依存関係を必要に応じて作成することです。これはプログラムを書くのに非常に直感的な方法であり、ほとんどの人が教える傾向がある方法です。このアプローチの問題の1つは、テストするのが難しいということです。現在の日付に基づいて何らかの処理を行うメソッドを考えてみましょう。このメソッドには、次のようなコードが含まれている場合があります。
if (DateTime.Now.Date > processDate)
{
// Do some processing
}
コードは、現在の日付に直接依存しています。この方法は、現在の日付を簡単に操作することができないため、テストするのが難しい場合があります。コードをよりテスト可能にする1つのアプローチは、現在の日付への直接参照を削除し、代わりに処理を行うメソッドに現在の日付を供給(または注入)することです。この依存性注入は、 テストダブルを使用して単体テストのセットアップステップを単純化することによって、コードの側面をテストすることをはるかに簡単にすることができます。
IOCシステム
考慮すべきもう1つの側面は、依存関係の存続期間です。クラス自身が独自の依存関係(不変条件としても知られている)を作成する場合は、クラス自体を処理する責任があります。 Dependency Injectionはこれを逆転させます(これが注入ライブラリを「Inversion of Control」システムと呼ぶことが多いためです)。そのクラスが依存関係の作成、管理、クリーンアップを担当するのではなく、外部エージェントケース、IoCシステム)が代わりにそれを行います。
これにより、同じクラスのインスタンス間で共有される依存関係を持つ方がずっと簡単になります。例えば、クラスが消費するHTTPエンドポイントからデータをフェッチするサービスを考えてみましょう。このサービスはステートレス(つまり内部状態はありません)なので、実際にはアプリケーションを通してこのサービスのインスタンスを1つだけ必要とします。これを手動で行うことは可能ですが(たとえば、静的クラスを使用することによって)、クラスを作成して、それがシングルトンとして作成されることをIoCシステムに伝えることが非常に簡単です。
もう1つの例は、Webアプリケーションで使用されるデータベースコンテキストで、コントローラのインスタンスごとではなく、要求(またはスレッド)ごとに新しいContextが必要です。これにより、手動で渡すことなく、そのスレッドによって実行されるすべてのレイヤーにコンテキストを注入することができます。
これにより、消費するクラスが依存関係を管理する必要がなくなります。
コンストラクタインジェクション
コンストラクタインジェクションは、クラス全体が依存する依存関係を注入する最も安全な方法です。このような依存関係は、クラスのインスタンスを提供せずに作成できないため、 不変条件と呼ばれることがあります。構築時に依存性を注入することを要求することにより、オブジェクトが不整合な状態で作成されないことが保証される。
エラー状態でログファイルに書き込む必要のあるクラスを考えてみましょう。これはILogger
に依存してILogger
、必要に応じて注入して使用することができます。
public class RecordProcessor
{
readonly private ILogger _logger;
public RecordProcessor(ILogger logger)
{
_logger = logger;
}
public void DoSomeProcessing() {
// ...
_logger.Log("Complete");
}
}
テストを書くときに、コンストラクタが実際にテストされるケースよりも多くの依存関係を必要とすることに気づくことがあります。そのようなテストが多いほど、クラスがSingle Responsibility Principle (SRP)を破る可能性が高くなります。そのため、潜在的な警告信号をマスクすることができるため、テストクラスの初期化フェーズで、注入された依存関係のすべてのモックに対してデフォルト動作を定義することは、あまり良い習慣ではありません。
unittestは次のようになります。
[Test]
public void RecordProcessor_DependencyInjectionExample()
{
ILogger logger = new FakeLoggerImpl(); //or create a mock by a mocking Framework
var sut = new RecordProcessor(logger); //initialize with fake impl in testcode
Assert.IsTrue(logger.HasCalledExpectedMethod());
}
プロパティインジェクション
プロパティインジェクションにより、クラスの依存関係を作成後に更新することができます。これは、オブジェクトの作成を簡素化したいが、テストダブルでテストによって依存関係を上書きできるようにする場合に便利です。
エラー状態でログファイルに書き込む必要のあるクラスを考えてみましょう。クラスはデフォルトのLogger
どのように構築するのかを知っていますが、プロパティの注入によって上書きすることができます。しかし、この方法でプロパティインジェクションを使用すると、このクラスではこの例のConcreteLogger
であるILogger
正確な実装と緊密に結合していることにILogger
があります。可能な回避策は、必要なILogger実装を返すファクトリです。
public class RecordProcessor
{
public RecordProcessor()
{
Logger = new ConcreteLogger();
}
public ILogger Logger { get; set; }
public void DoSomeProcessing()
{
// ...
_logger.Log("Complete");
}
}
ほとんどの場合、コンストラクターインジェクションは、オブジェクトの作成直後にオブジェクトの状態をよりよく保証するため、プロパティインジェクションよりも優れています。
メソッド注入
メソッドインジェクションは、依存関係を処理に注入するきめ細かな方法です。現在の日付に基づいて何らかの処理を行うメソッドを考えてみましょう。現在の日付はテストから変更するのが難しいので、テストするメソッドに日付を渡す方がはるかに簡単です。
public void ProcessRecords(DateTime currentDate)
{
foreach(var record in _records)
{
if (currentDate.Date > record.ProcessDate)
{
// Do some processing
}
}
}
コンテナ/ DIフレームワーク
コードから依存関係を抽出して注入できるようにすると、コードを簡単にテストできるようになりますが、それは問題をさらに階層の上に押し上げ、結果として構築が困難なオブジェクトにもなります。様々な依存性注入フレームワーク/コントロールコンテナの反転がこの問題を克服するために書かれています。これにより、型マッピングを登録できます。これらの登録は、コンテナにオブジェクトの作成を依頼されたときに依存関係を解決するために使用されます。
次のクラスを考えてみましょう:
public interface ILogger {
void Log(string message);
}
public class ConcreteLogger : ILogger
{
public ConcreteLogger()
{
// ...
}
public void Log(string message)
{
// ...
}
}
public class SimpleClass
{
public SimpleClass()
{
// ...
}
}
public class SomeProcessor
{
public SomeProcessor(ILogger logger, SimpleClass simpleClass)
{
// ...
}
}
SomeProcessor
を構築するには、 ILogger
とSimpleClass
インスタンスが必要です。 Unityのようなコンテナは、このプロセスを自動化するのに役立ちます。
最初にコンテナを構築してからマッピングを登録する必要があります。これは通常、アプリケーション内で1回だけ実行されます。これが発生するシステムの領域は、通常、 Composition Root
// Register the container
var container = new UnityContainer();
// Register a type mapping. This allows a `SimpleClass` instance
// to be constructed whenever it is required.
container.RegisterType<SimpleClass, SimpleClass>();
// Register an instance. This will use this instance of `ConcreteLogger`
// Whenever an `ILogger` is required.
container.RegisterInstance<ILogger>(new ConcreteLogger());
コンテナが設定された後、オブジェクトを作成して、必要に応じて自動的に依存関係を解決することができます。
var processor = container.Resolve<SomeProcessor>();