Mockitoノススメ
モックライブラリ使ってますか?
僕はJavaの人なので、主にJUnitを使ってテストコードを書いています。テストコードを書いている最中、「もしこのオブジェクトから例外が帰ってきたら、ちゃんと例外のハンドリングができてんの?」等々、既存のオブジェクトの振る舞いを差し替えたくなることってありませんか?そういうときにモックライブラリを使うと、既存のオブジェクト処理を差し替える事ができます。
実は最初はモックライブラリって意味あるの?と懐疑的だったんです。どういうところに懐疑的だったかというと、
- テストコード中に出てくるモックライブラリのセットアップがめんどい。
- テストコードがプロダクトコードの実装に依存しちゃうんじゃないの?プロダクトコードをちょっと変えただけでテストが落ちるようになるんじゃないの?
みたいなところです。でもMockitoというモックライブラリを使ってテストコードを書き初めてからその態度は変わりました。Mockitoを使ってテストを書くと、次の効果が見込まれます。
- テストコードに対応するプロダクトコードの中のロジックのみに関心を持ったテストコードを記述できるようになります。
- スタブとして差し替えたインタフェースへの期待値が、そのままテストケースとして追加できます。
- 遅い処理を持ったオブジェクトをスタブへ差し替えられるので、スローテスト問題へ立ち向かう事ができます。
- モックとして差し替えた場合、プロダクトコード内で呼び出されていることを検証できます。
- オブジェクトをラップすることで、一部分だけ処理をスタブするパーシャルモック(部分モック)があます。
スタブとかモックの用語は、どちらもテスト用代替オブジェクトとして使うため、こうしてみると大きな差ではない気がするんですが、テスト信奉者の方々からすると結構重要な事として位置づけられるので、覚えておいた方がいいです。
Mockitoの使い方
それではMockitoの使い方ですが、簡単です。まず、モックオブジェクトの作り方。基本一行です。
List list = mock(List.class);
これでListクラスのモックオブジェクトの作成が完了です。続いて先にモックオブジェクトを使ってモックする場合、どうするかですが、もし、listにあるオブジェクトが追加されている事を検証したいとしましょう。そういうときはこう書きます。
List list = mock(List.class); target.setList(list); // モックオブジェクトに差し替える target.execute(); // テストしたいメソッドを実行する。 verify(list).add(eq("test")); // listにtestというStringが追加されている事を検証する。
eq("test")は、obj.equals("test")と同じような感じで、実行したときの引数に渡ってきた値を検証します。もしどんな値でもいいのであれば、any()メソッドを使ってください。any()メソッドを使う場合はキャストをしないと、コンパイルエラーになります。
もし2回以上実行されている事を確認したい場合は
List list = mock(List.class); target.setList(list); // モックオブジェクトに差し替える target.execute(); // テストしたいメソッドを実行する。 verify(list,times(2)).add(eq("test")); // listにtestというStringが2回実行された事を検証する。
逆に中で追加されていない場合はnever()を使います。
List list = mock(List.class); target.setList(list); // モックオブジェクトに差し替える target.execute(); // テストしたいメソッドを実行する。 verify(list,never()).add(eq("test")); // listにtestというStringが追加されていない事を検証する。
続いて、スタブの方法ですが、こんな感じ。
List list = mock(List.class); when(list.get(eq(0))).thenReturn(new Object()); //list.get(0)が呼ばれたらObjectを返す。
簡単ですね。もし、判断させたい場合は、
List mock = mock(List.class); when(mock.get(anyInt())).thenAnswer(new Answer<Object>() { public Object answer(InvocationOnMock invocation) throws Throwable { Object arg = invocation.getArguments()[0]; Integer index = (Integer) arg; if(index < 10){ return new Object(); } return null; } });
のようにAnswerクラスを使います。途中出てきたanyInt()は、どのintでもOKということですね。
続いて何らかの例外を投げさせたければ
List mock = mock(List.class); when(mock.get(anyInt())).thenThrow(new IndexOutOfBoundsException());
というように、thenThrowを使ってやってくだし。
ほんで、返値のないメソッドのスタブをさせたい場合は、
@Test(expected=UnsupportedOperationException.class) public void unmodifiable() throws Exception { List mock = mock(List.class); doThrow(new UnsupportedOperationException()).when(mock).add(any()); mock.add(new Object()); }
と言う感じで、doなんたらメソッドを使います。
ほんで、パーシャルモックの作成はこんな感じ。
List list = new ArrayList(); List mock = spy(list); doReturn(new Object()).when(mock).get(0);
とかすると、mock.get(0)で期待した通り、オブジェクトが返ってきます。
最後にQuick JUnitで実験的にMockitoをサポートするプラグインを作成しました。Quick JUnit Mockito Integration (Experimental)として配布中です。このプラグインをインストールすると、Qというコンテンツアシストで、
こんな感じでMockito関連のインポートだけじゃなく、hamcrestのCoreMatcherもインポートしているんで、便利でござる。
http://quick-junit.sourceforge.jp/updates/beta/ で配布中なので使ってみてくだし。