テストのためのリファクタリング

最近、テスティング野郎*1になりつつあります。もともと単体テストはそれなりに好きだったのですが、ここ1年ぐらいは、テストを簡単に行なえるように、テストのために本体のクラスを書き換えることが多くなった気がします。

たとえば、テストコードを書く前に作ってしまった*2、以下のようなクラスがあったとします。

public class TestTarget {
  public int func() 
    ComponentA a = new ComponentA();
    ComponentB b = new ComponentB();
    return a.hoge() + b.hoge();
  }
}

このクラスをテストしようとした場合、func メソッド内で ComponentA と ComponentB という他のクラスに異存してしまっているため、それぞれのクラスの動作を知っていないとテストコードが記述できません。
つまり、TestTarget クラスに対するテストであるにもかかわらず、ComponentA、ComponentB という TestTarget 以外のクラスが関係してしまっているのです。

ここでテスティング野郎ならば、テストを行ないやすくするために、以下のようなリファクタリングを行ないます*3。ポイントとしてはすでに TestTarget を使用しているクラスへの影響を最小限に押さえるために、リストラクチャリングではなく、リファクタリングになるようにすることです。

public class TestTarget {
  private ComponentFactory factory;
  
  public TestTarget() {
    this(new ComponentFactoryImpl());
  }
  
  /**
   * テストのために用意されたコンストラクタ
   */
  TestTarget(ComponentFactory factory) {
    this.factory = factory;
  }

  public int func() 
  // コンポーネントを直接 new せず、ファクトリ経由で new する.
    Component a = factory.createComponentA();
    Component b = factory.createComponentB();
    return a.hoge() + b.hoge();
  }
}

上記のように、コンポーネントの生成をテスト対象クラス内で行なわないようにすることで、テストコードでコンポーネントのモックを差し込むことができるようになります*4
テストクラスからの使用を前提としたパッケージプライベートなメソッドやコンストラクタを用意するというのは、なじみのない人には気持ち悪いのかも知れませんが、私は必要に応じて使用しています。
テスト用のパッケージプライベートなメソッドを用意しないことで、必要以上にテストコードが複雑化するよりはよっぽど良いと思います。

ちなみに、DIコンテナを使用してコンポーネントをインジェクションするような設計としておけば、クラス内で他コンポーネントの生成を行なうということは無くなると思うので、自然とテストイージーなつくりとなります。例えば下記のようなつくりになるかと。あー、シンプルで(*´д`*)キモチイイー。*5

public class TestTarget {
  private Component a;
  private Component b;
  public void setA(Component a) {
    this.a = a;
  }
  public void setB(Component b) {
    this.b = b;
  }

  public int func() {
    return a.hoge() + b.hoge();
  }
}

*1:単体テストが大好きな奴らのことです。JUnitのグリーンバーで(*´д`*)ハァハァできるかも知れません。

*2:テストファーストで作ればそんなことは無いのかもしれませんが・・・(汗

*3:たぶんね。テスティング野郎が周りにいないので未確認です。

*4:ファクトリではなく、コンポーネントA、コンポーネントBそれぞれをコンストラクタ引数で受け取るようにすることも可

*5:オブジェクトの生成を言語レベルでコントロールできれば、もっと気持ち( ・∀・)イイ!のかもしれませんが・・・新たな予約語などで。