DI (依存性注入) って何のためにするのかわからない人向けに頑張って説明してみる を、単体テストにフォーカスしたら単体テストが本来の目的では無い的なコメントが散見されました。
確かに DI コンテナは元々は SOLID 原則の D に該当する依存性逆転の原則 (Dependency inversion principle) の「具体ではなく、抽象に依存しなければならない」に大きく関係するものになります。
Wikipedia から引用します。
ソフトウエアモジュールを疎結合に保つための特定の形式を指す用語。
この原則に従うとソフトウェアの振る舞いを定義する上位レベルのモジュールから
下位レベルモジュールへの従来の依存関係は逆転し、結果として下位レベルモジュールの
実装の詳細から上位レベルモジュールを独立に保つことができるようになる。
例えば以下のように上位レベルのモジュール(Presentation)が下位レベルのモジュール(Application)にがっちり依存してるとします。
class SomePresentation
{
public void Foo()
{
var c = new SomeApplication();
c.Bar();
}
}
class SomeApplication
{
public void Bar()
{
}
}
クラス図にするとこんな感じですね。 (メソッドは省略)
こんな風に上位のクラス(Presentation)が、より下位のクラス (Application) に依存していると上位のクラスの再利用が妨げられます。SomeApplication をがっちりつかんでて使いまわししづらいです。芋づる式に上位のクラスを使おうと思うと下位のクラスにがついてきます。
依存性の逆転
実装に直接依存するのではなく、間に抽象を挟むことで実装に依存しなくなります。
ISomeApplication は、SomePresentation の都合によって定義されるもので SomePresentation の一部だと考えると SomeApplication から Presentation 側に矢印が向いてる感じになってるように見えますね。矢印の向きが反対になりました。
組み立てないと…
こういう風に定義すると、やっぱりクラスは組み立てないといけません。そのために、こんな感じのクラスを組み立てる役割を負う人が必要になります。
こういう風にしておかないと SomePresentation を使う人全員がクラスを正しく組み立てないといけないという責任を負うことになります。それはつらいよね。
class PresentationFactory
{
public SomePresentation CreateSomePresentation() => new SomePresentation(
new SomeApplication(new SomeRepository()) // 今回は未登場だけど Application は Repository にさらに依存するとしたらこんな感じのコードになる
);
}
こういったコンストラクターに必要な実装クラスのインスタンスを設定してインスタンスを生成してくれるような処理を汎用的にしたものが DI コンテナーです。C# だと Unity とか DryIoc とかがあります。Java だと Spring Framework とかが有名ですね。私も就職した 2005 年くらいに Spring Framework や今はサポートも切れた Seasar2 で DI について初めて触れました。
単体テストも容易に
といういことで、下位のレイヤーの実装に引きずられなくなるので、結果としてモック実装を差し込むことが出来て単体テストも容易になります。素晴らしい!
実際単体テストって必須だよね
フットワーク軽くアプリを開発しようとしたら単体テストはほぼ必須です。MS でも Microsoft の DevOps への道のり にあるように、実行が遅い UI テストやクラスを結合した状態でのテストよりは、素早く開発サイクルを回すために単体テストをしっかりやるという話が触れられてます。
最初は育てる予定が無かったソフトウェアでも不慮の事故で育てることになったときに単体テストが出来ないと大変なので単体テスト可能なようにつくっておくことは大事だと思います。そのために DI コンテナ使ってレイヤー間はインターフェースを挟んで実装を差し替え可能なようにしておくと必要になったタイミングで単体テストを書くことが出来て幸せになれます。
まとめ
とまぁこんな感じで書きましたが個人的には DI(依存性の注入)は、単体テスト容易なソフトウェアを作るための現時点での現実解です。
単体テストは 100% 完全にする必要はないけど単体テストが出来ないように作るデメリットは大きすぎて、大きなソフトウェアでは私は単体テスト不可能に作る勇気はちょっとありません…。
そして、最近のフレームワークのほとんどは DI コンテナを前提に作られてるので、その恩恵を受けつつ単体テストも出来るように開発するのが実装のためのオーバーヘッド(インターフェースを追加で定義しないといけない)も許容範囲で、一番楽できるという選択だと思います。
という考えから「DI (依存性注入) って何のためにするのかわからない人向けに頑張って説明してみる」に書いたように単体テストを容易にするために DI を前提とした形で作るといいよね!という記事が生まれました。
ということで DI 前提の作りにして単体テストをやろうと思ったときに、やることが出来る感じにコードを書いて、なるべく楽しましょう。
あ、あと頑張って書いたので、もしこの説明が抜けてるとか、こういう解釈のほうがいいとかいうのがあれば編集リクエストなどで教えてください m(_ _)m