岩本隆史の日記帳(アーカイブ)

はてなダイアリーのサービス終了をうけて移行したものです。更新はしません。

たまにはDIのことも思い出してあげてください

以前、設計勉強会でご一緒させていただいた坂本さんが、おもしろい記事を書かれています。

DIを使わずに依存オブジェクトをモックに差し替える手法を紹介する記事です。

ただ、私ならやっぱりDIを使います。テストが楽に書けるし、テスト対象クラスのコンストラクタやファクトリメソッドをテストコードがオーバーライドするのは気持ちが悪い。*1

このあたりの議論は「依存性注入(DI)は成功したか?」という記事でも触れられています。

よりテストしやすくする目的でコードを変更することは良いことだと論ずる人達もいれば、その目的のためにカプセル化を解くのは良くない、と主張する人達もいる。

私は前者なのでしょう。オーバーライドでモックに差し替えられるようにするのも「カプセル化を解く」行為のように私にはみえます。

とはいえ、私は誰かを説得したいわけではありません。InfoQの議論でも(例によって)結論が出ていないように、結局は好みの問題になるのでしょう。なお、この記事のタイトルは、ホッテントリメーカーで作りました

DIを使うとコードはどうなるか

書くまでもないことかもしれませんが、せっかく考えたので、元記事で紹介されている(モック差し替え後の)コードをDIに変えるとどうなるかメモしておきます。

テストコード(DIを使わない場合)
class ApplicationTest extends TestCase {
  // まずMockとなるViewクラスとそのインスタンスを用意しておく。
  MockView mockView = new MockView();
  // この例では単純に"display()メソッドが呼ばれたか?"だけを後でassert出来るようにしておく。
  private class MockView extends View
  {
    boolean isDisplayed = false;
    public void display() {
      isDisplayed = true;
    }
    public void validate() {
      assertTrue(isDisplayed);
    }
  }
 
  // 実際のテストコード
  public void testApplication {
    // ここで、createView()だけ差し替えたApplicationクラス(の派生クラス)を
    // インナークラスで作る。これによりMockのViewを使ってrun()が実行される。
    Application a = new Application() {
      protected View createView() {
        return mockView;
      }
    };
    a.run();
    mockView.validate();
  }
}
テストコード(DIを使う場合)
class MockView extends View {
  boolean isDisplayed = false;

  public void display() {
    isDisplayed = true;
  }

  public boolean isDisplayed() {
    return isDisplayed;
  }
}

class ApplicationTest extends TestCase {
  public void testApplication {
    Application a = new Application();
    MockView mockView = new MockView();
    // ビューをコンストラクタで渡すのは不自然な気がするので、
    // メソッドの引数にしました。(これもDIですよね?)
    a.run(mockView);
    // ビューのassertはここで。
    assertTrue(mockView.isDisplayed());
  }
}
テスト対象コード(DIを使わない場合)
class Application {
  //...
  public void run() {
    View v = createView();
    v.display();
    //...
  }
  protected View createView() {
    return new View();
  }
  //...
}
テスト対象コード(DIを使う場合)
class Application {
  //...
  public void run(View v) {
    v.display();
    //...
  }
  //...
}

もうひとつ元記事で取り上げられているトランザクションの例も、ほぼ同様です。ただしトランザクションオブジェクトは、コンストラクタで渡すのが自然だと思います。