EasyMockとは
ユニットテストを行う際に、対象のクラスが他のオブジェクトに依存している場合、その依存するオブジェクトの状態に応じて実行結果が変わってしまうため、正しく動作しているかどうか確認するのが難しくなるという問題が発生する。これを回避するには、そのオブジェクトの動作をシミュレートする「モックオブジェクト」を作成し、実際のオブジェクトの代わりにテストに利用するという手法が一般的である。
たとえばサーバで自動生成されるオブジェクトや、データベースから得られる結果を格納したオブジェクト、Webサービスを利用して取得するデータなどは、開発者側で制御することができないため、テストのための理想的な結果を得ることが難しい。DIのように使用する実装が実行時に決まるようなケースでも同様である。そこで、決められたパラメータを渡すとそれに対する正しい結果を返すような仮のオブジェクトを作成し、これを利用してユニットテストを行うのである。
Javaアプリケーション開発において、このモックオブジェクトの作成を簡単に行えるようにするツールが「EasyMock」だ。EasyMockを使えば、与えられたインタフェースに対するモックオブジェクトを動的に生成し、それをそのままユニットテストに用いることができる。自前でモック用の特別なクラスを実装する必要はなく、数行のコードだけで極めてシンプルにモックの定義が可能となっている。デフォルトでサポートされているのはインタフェースに対するモックオブジェクト生成のみだが、拡張ライブラリを利用すればクラスに対するモックオブジェクトの生成もできるようになる。
EasyMockを利用したユニットテスト
EasyMockの最新版はこのページよりダウンロードできる。ダウンロードしたzipファイルを解凍し、中に含まれている「easymock-3.0.jar」をクラスパスに追加して利用する。その他に、「cglib」(cglib-x.x.x.jar)と、「ObjectWeb ASM」(asm-x.x.x.jar)も必要なので、別途ダウンロードしてクラスパスに追加する。なお、クラスからのモックオブジェクトの生成を行いたい場合は「EasyMock Class Extension」を利用する。
それでは、実際にEasyMockを使ったユニットテストを作成してみよう。まず、モックのベースとなるインタフェースを用意する。ここでは以下に示すようなDataSourceインタフェースを用意した。
public interface DataSource {
public String get(String key);
}
このインタフェースにはget()というメソッドがひとつだけ定義してあるが、この時点ではまだ実装は作っていない。つまりget()が返す結果は、ローカルのオブジェクトが保持しているのか、データベースから取得するのか、あるいはWebサービスから取得するのか、様々な可能性があるが、この時点ではまだ決定していないということである。
次に、このDataSourceを使うプログラムとして次のようなクラスを用意する。
public class EasyMockSample {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/* テスト対象のメソッド */
public String findValue(String key) {
return this.dataSource.get(key);
}
}
findValue()メソッドはDataSourceオブジェクトのget()メソッドで取得したデータを返す。このfindValue()が今回テスト対象としたいメソッドである。しかしこの時点ではまだDataSourceのget()メソッドが未実装であるため、そのままではテストを実施することができない。そこでEasyMockを利用してモックを作成する。
EasyMockを使ったテストでは、まずorg.easymock.EasyMockクラスのcreateMock()メソッドを使ってモックオブジェクトを作成する。このメソッドには、次のように対象となるインタフェースのclassオブジェクトを渡す。
DataSource mock = EasyMock.createMock(DataSource.class);
続いて、作成したモックの振る舞いを定義する。例えば次のコードでは、get()メソッドに"testkey"という文字列を渡して実行すると、"resultValue"という文字列を返すように定義している。
EasyMock.expect(mock.get("testkey")).andReturn("resultValue");
戻り値を設定しない場合には単にモックオブジェクトに対するメソッド呼び出しを記述すればよいが、戻り値を設定したいのであれば、expect()メソッドの引数としてメソッド呼び出しを行い、その結果に対してandReturn()メソッドで想定する戻り値を指定する。振る舞いとして複数のメソッド呼び出しを設定することもできる。また、呼び出しの回数や順序を指定しておくことも可能となっている。
振る舞いが定義できたら、最後に次のようにreplay()メソッドを実行して、モックを振る舞いの記録モードから再生モードへ切り替えておく。この処理を行っておかなければモックは正しく動作しない。
EasyMock.replay(mock);
以上でモックの準備は完了である。あとは作成したモック(本稿の例ではmock変数)を利用してfindValue()をテストすればよい。以下のコードは、JUnitを利用したユニットテストの例である。
import org.easymock.EasyMock;
import org.junit.Test;
import static org.junit.Assert.*;
public class EasyMockSampleTest {
@Test
public void testFindValue() {
System.out.println("findValue() method test.");
// モックを作成
DataSource mock = EasyMock.createMock(DataSource.class);
// モックの振る舞いを記録
EasyMock.expect(mock.get("testkey")).andReturn("resultValue");
EasyMock.replay(mock);
// テストを実行
EasyMockSample sample = new EasyMockSample();
sample.setDataSource(mock);
String result = sample.findValue("testkey");
assertEquals("resultValue", result);
}
}
ここでは作成したDataSourceのモックをEasyMockSampleにセットした上で、findValue("testkey")の呼び出しを行っている。findValue()は渡された文字列を用いてDataSourceのget()メソッドを呼び出すので、ここでモックに記録した振る舞いが再現されることになる。上の例ではassertEquals()でfindValue()の結果をチェックしている。
findValue()はget()の実行結果をそのまま返すメソッドとして作ってあるので、findValue("testkey")の呼び出しが正しく実行されたのであれば、戻り値は"resultValue"になるはずである。したがってこのテストはのように正常に終了する。
ではもしfindValue()の実装が間違っていた場合はどうだろうか。仮に、次のようにget()の結果に誤った加工を加えていた場合、当然ながらテストは失敗しのような結果になるはずだ。
public String findValue(String key) {
return this.dataSource.get(key).toUpperCase();
}
繰り返しになるが、このテストコードはDataSourceの実装には一切依存していない。このような実装に依存しないユニットテストのためのモックを、非常に簡潔なコードで作成することができるのがEasyMockを使うメリットである。