JUnit + EasyMock

このところ EasyMock が結構面白い。
EasyMock っていうのは、インタフェースからモックオブジェクトを自動生成してくれるライブラリです。
モックオブジェクトがあると、他クラスが持つ機能を利用して動作するクラスのテストを行なう場合に他クラスが実際にはなくてもテストが行なえます。
また、モックオブジェクトはテストのためのものなので、わざと例外をおこさせたり、任意の戻り値を返させることが簡単に出来ます。


たとえば、以下のようなテスト対象のクラス Foo と Foo が利用する機能を持ったインタフェース IBar があるとします。

public class Foo {
  private IBar bar;
  public void setBar( IBar bar ) {
    this.bar = bar;
  }

  public int fooFunc( String arg1, String arg2 ) throws HogeException {
    return bar.barFunc1( arg1 ) + bar.barFunc2( arg2 );
  }
}


public interface IBar {
  public int barFunc1( String arg ) throws HogeException;
  public int barFunc2( String arg );
}

この例では、Foo#fooFunc メソッドは bar#barFunc1 メソッドと bar#barFunc2 メソッドを呼び出しその戻り値を加算した値を返す処理を行なっています。また、fooFunc メソッドの引数に渡された文字列をそれぞれ barFunc1 と barFunc2 の引数として渡しています。


このような処理を行なうことを期待している Foo#fooFunc メソッドですが、IBar の実装クラスを利用しているため、テストを行なうには IBar の実装クラスを設定する必要があります。
しかし、IBar の実装が Foo クラス作成時にはまだ用意できない場合や、Foo クラス単体をテストしたい場合(IBar の実装内容にFooのテストコードが左右されるのは望ましくない)があります。
このような場合、 IBar のインタフェースを満たす嘘っぱちのクラスを作成し、そのオブジェクトを利用することになります。


今まではそのような場合は以下のようなテストを書いていたと思います。

public class BarTest extends TestCase {
  private Foo target;
  private BarMock barMock;
  protected void setUp() {
    target = new Foo();
    barMock = new BarMock();
    target.setBar( barMock );
  }

  public void testFooFuncMethod() throws Exception {
    // モックオブジェクトに戻り値を設定
    barMock.barFunc1Result = 100;
    barMock.barFunc2Result = 200;

    // 処理対象メソッドの呼び出し
    String arg1 = "ののタン";
    String arg2 = "(*´д`*)ハァハァ";
    int actualResult = target.fooFunc( arg1, arg2 );
    
    // 検証
    assertEquals( "bar.barFunc1 引数", arg1, barMock.barFunc1Arg );
    assertEquals( "bar.barFunc2 引数", arg2, barMock.barFunc2Arg );
    int expectedResult = barMock.barFunc1Result + barMock.barFunc2Result;
    assertEquals( "Foo#fooFunc 戻り値", expectedResult, actualResult );
  }

  private static class BarMock implements IBar {
    String barFunc1Arg;// テストコードなので、無駄になるカプセル化は行なわない
    String barFunc2Arg;
    int barFunc1Result;
    int barFunc2Result;

    public int barFunc1( String arg ) throws HogeException {
      barFunc1Arg = arg;
      return barFunc1Result;
    }

    public int barFunc2( String arg ) {
      barFunc2Arg = arg;
      return barFunc2Result;
    }
  }
}

上記の例ではモックオブジェクトのためのクラスを作成してテストを行なっています。
モックオブジェクトのためのクラスでは、メソッド呼び出しの引数を記録したり、戻り値を設定したりすることができるようになっています。これらの値をつかって検証を行ないます。


しかしながら、毎回すべてのモックオブジェクトが必要なインタフェースに対してモックオブジェクトのクラスを作成するのは非常に労力がかかりますし、メソッドが追加されるなどインタフェースが変更されるたびにモックオブジェクトを使用している箇所のソースを修正する必要が出てしまい、コストがかかります。


そこで、EasyMock を使用するとこれらのコストから解放されることになります。EasyMockではインタフェースからモックオブジェクトを自動生成することが出来ますし、戻り値や引数のチェック等などが簡単に行なえます。
EasyMock が持つ機能は以下になります。

  • インタフェースからモックオブジェクトを動的に自動生成
  • 呼び出されるメソッドとその順番を検証
  • メソッド呼び出し時の引数を検証
  • メソッド呼び出しの戻り値を設定
  • メソッド呼び出しで例外を送出


今日はここまで。明日は EasyMock の使用手順と、メソッド呼び出し時引数の検証方法について書こうと思います。時間があればorz


追伸:
EasyMock 以外にも jMock っていうのもあるみたいです。機能的には同じようなことができるらしいです。ちがうのは使用手順。自分にあった方を選べばいいかと。あとテストの複雑さとか。
複雑なテストを書く場合は jMock のほうが分かりやすく書けるって噂を聞いたことも。そのうち jMock についても調べてみます。