読者です 読者をやめる 読者になる 読者になる

ぺーぺーSEのブログ

備忘録・メモ用サイト。

JUnitで試験するときに使うMockライブラリ

JUnitを使用した単体試験のときに使えるMockについて。(なんか疲れたからまとめっぷりは中途)
以下みたいなのがある。

  • EasyMock
    • インスタンスのMockがほしいときに使うとよい
    • ネストしたクラスをMock化したいときはセッターとかDIとか使う
  • Mockito
    • EasyMockより便利?
  • PowerMock
    • EasyMock、Mockitoにできないことをできるように拡張する
    • なんか遅いらしい
      • だからEasyMock、Mockitoでできない機能だけ使う

個人的にDJunitはもうオワコンなので書かない。(カバレッジバグったりするし)
使うなら「EasyMock + PowerMock」か「Mockito + PowerMock」なのかな。

Mockito VS EasyMock

上記はMockito側の人だけど、適当にぐぐってみてもMockito押しの人が多い気がするなぁ。。。
ぐーぐる先生のトレンドによると2011年ちょい前にMockitoが逆転しておる。
http://www.google.co.jp/trends/explore#q=EasyMock%2C%20Mockito&cmpt=q


EasyMock

EasyMockは下記のライフサイクルで使用する。

  1. Mock化対象クラス・インターフェースからMockオブジェクトを作成(createMock())
  2. Mockオブジェクトのメソッドの振る舞いを記録(expect())
  3. Mockオブジェクトを記録モードから再生モードへ(replay())
  4. Mockオブジェクトを使ったテスト
  5. Mockオブジェクト再生後の検証(verify())
  6. Mockオブジェクトの初期化(reset())



■サンプルで使用するインターフェース(Mock化する対象)

public interface Concater {

  /**
   * 文字列を結合する
   */
  String concat(String str1, String str2) throws Exception;

}



■サンプルのテストコード

@Test
public void testMock() throws Exception {
  
  // 1. Mock化対象クラス・インターフェースからMockオブジェクトを作成
  Concater mock = EasyMock.createMock(Concater.class);
  
  // 2. Mockオブジェクトのメソッドの振る舞いを記録(Mock作成時は記録モード)
  EasyMock.expect(mock.concat("FirstName", "SecondName")).andReturn("FirstName SecondName");
  
  // 3. Mockオブジェクトを記録モードから再生モードへ
  EasyMock.replay(mock);
  
  // 4. Mockオブジェクトを使ったテスト
  assertThat(mock.concat("FirstName", "SecondName"), is("FirstName SecondName"));
  
  // 5. Mockオブジェクト再生後の検証
  EasyMock.verify(mock);
  
  // 6. Mockオブジェクトの初期化
  EasyMock.reset(mock);
}

本来はインスタンス化しづらいオブジェクトをMock化するんだけど、ここでは説明用って事で意味の無いコードになってる。

Mock化対象クラス・インターフェースからMockオブジェクトを作成

Mockを作成する際に使用するメソッドには以下の種類がある。
EasyMock.verify()した際にチェックされる内容が変わる。
引数にはMock化したクラス・インターフェースのクラス型を渡す。

  • 通常モード EasyMock.createMock()
    • メソッドの呼び出し順序はチェックされない
    • 想定(expectによる設定)以外のメソッドへの呼び出しが行われると失敗する
  • 厳密モード EasyMock.createStrictMock()
    • メソッドの呼び出し順序はチェックされる
    • 想定(expectによる設定)以外のメソッドへの呼び出しが行われると失敗する
  • 緩和モード EasyMock.createNiceMock()
    • メソッドの呼び出し順序はチェックされない
    • 想定(expectによる設定)以外のメソッドへの呼び出しが行われると失敗しない
      • メソッドの返り値の型によって特定の値を返却
      • 数値型であれば0、booleanであればfalse、オブジェクトであればnullなど


Mockオブジェクトのメソッドの振る舞いを記録

メソッド呼び出しの際の引数

Mockのメソッドを呼ぶ出す際の引数として明示的な値ではなく、なんらかのオブジェクト(EasyMock.anyObject())のようにフンワリさせることができる。

EasyMock.expect(mock.concat(
       (String) EasyMock.anyObject(),
       (String) EasyMock.anyObject())).andReturn(1.5);

任意のオブジェクトとはいえnullはヤダってときはEasyMock.notNull()を使う。

EasyMock.expect(mock.concat(
        (String) EasyMock.notNull(),
        (String) EasyMock.notNull())).andReturn("FirstName SecondName");

正規表現(EasyMock.matches())を指定することもできる。

EasyMock.expect(mock.concat(
        (String) EasyMock.matches("[A-Z][A-Z][A-Z]"),
        (String) EasyMock.matches("[A-Z][A-Z][A-Z]"))).andReturn("FirstName SecondName");

EasyMock.matches()の代わりにEasyMock.find()を使うと、指定の文字列を含む任意のStringを受け付けることができる。
他にも以下のようなメソッドが用意されている。

  • EasyMock.anyInt()
  • EasyMock.anyShort()
  • EasyMock.anyByte()
  • EasyMock.anyLong()
  • EasyMock.anyFloat()
  • EasyMock.anyDouble()
  • EasyMock.anyBoolean()
  • EasyMock.lt()
  • EasyMock.gt()
  • EasyMock.eq()


メソッド呼び出し後の返り値

EasyMock.expect()メソッドの次にandReturn()を呼び出し、このメソッドを呼び出す結果として何を出力する必要があるかを指定する。
例外を発生させたい場合はandThrow()を呼び出す。
times()メソッドで呼び出し回数を指定することもできる。

複数モックを使用する場合

IMocksControlによって複数のモックオブジェクトを生成することが可能になり、複数のモックにまたがってメソッド呼出しの順序を チェックすることができる。
例えば、IMyInterfaceインタフェースに対して2つのモックオブジェクトをセットアップ するとしましょう。そしてまず、mock1.a()とmock2.a()を順番に、次にmock1.c() とmock2.c()を任意の回数、最後にmock2.b() と mock1.b()を順に実行することを 期待します。この設定を行うコードは以下のようになります。

IMocksControl ctrl = createStrictControl();
IMyInterface mock1 = ctrl.createMock(IMyInterface.class);
IMyInterface mock2 = ctrl.createMock(IMyInterface.class);

mock1.a();
mock2.a();

ctrl.checkOrder(false);

mock1.c();
expectLastCall().anyTimes();     
mock2.c();
expectLastCall().anyTimes();     

ctrl.checkOrder(true);

mock2.b();
mock1.b();

ctrl.replay();
ctrl.verity();
ctrl.reset();

EasyMock(org.easymock.EasyMock)の他にEasyMockClassExtension(とorg.easymock.classextension.EasyMock)がある。
後者は本来インタフェースからしかモックを作れないEasyMockの機能を拡張し、クラスからでもモックを作れるようにしてくれる機能みたい。
参考:
http://easymock.org/
http://www.ibm.com/developerworks/jp/java/library/j-easymock.html
http://www.geocities.jp/nn_51/easymock/EasyMock2_2_Documentation.html
http://prepro.wordpress.com/tag/easymock/

Mockito

■サンプルのテストコード

@Test
public void testMock() throws Exception {
  
  // 1. Mock化対象クラス・インターフェースからMockオブジェクトを作成
  Concater mock = Mockito.mock(Concater.class);
  
  // 2. Mockオブジェクトのメソッドの振る舞いを記録
  Mockito.when(mock.concat("FirstName", "SecondName")).thenReturn("FirstName SecondName");
  
  // 3. Mockオブジェクトを使ったテスト
  assertThat(mock.concat("FirstName", "SecondName"), is("FirstName SecondName"));
  
  // 4. Mockオブジェクト再生後の検証、および初期化
  EasyMock.verify(mock).clear();
}

EasyMockよりコード量が少ない。
参考:
Mockit Web Site: http://code.google.com/p/mockito/
Mockit Documents: http://docs.mockito.googlecode.com/hg/latest/org.mockito/Mockito.html
http://qiita.com/mstssk/items/98e597c13f12746c907d


PowerMock with EasyMock

Mavenを使う場合

<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-library</artifactId>
    <version>1.1</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.easymock</groupId>
    <artifactId>easymock</artifactId>
    <version>3.2</version>
  </dependency>
  <dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>1.5.2</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-easymock</artifactId>
    <version>1.5.2</version>
    <scope>test</scope>
  </dependency>
</dependencies>



■使い方

import org.junit.Test;
import org.junit.runner.RunWith;
import org.easymock.EasyMock;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
 
@RunWith(PowerMockRunner.class) //PowerMockの利用を宣言
@PrepareForTest({MockTarget1.class, MockTarget2.class}) //モック対象のクラスを宣言(複数可能)
public class TestClass {

  @Test
  public void testMock() throws Exception {
    // テストを書く
  }
}


staticメソッドのMock

ここでのPowerMockのフルネームは「org.powermock.api.easymock.PowerMock」。

// 1. PowerMockでMock化したいstaticメソッド持つクラスをロード
PowerMock.mockStatic(<staticメソッドを呼び出すクラス>);

// 2. EasyMockでstaticメソッドの振る舞いを記録
EasyMock.expect(<staticメソッド呼出>).andReturn(<戻り値>);

// 3. PowerMockでMockを再生モードへ
PowerMock.replay(<staticメソッドを呼び出すクラス>);

// 4. Mockを使ったテスト
// 省略

// 5. PowerMockでMock再生後の検証
PowerMock.verify(<staticメソッドを呼び出すクラス>);

// 6. PowerMockでMockを初期化
PowerMock.reset(<staticメソッドを呼び出すクラス>);



クラスのstaticメソッドの部分的にMock化したい場合は

PowerMock.createPartialMock(<staticメソッドを呼び出すクラス>, <メソッド名1>, <メソッド名2>,・・・)

verify時のモードの違いにより

PowerMock.createStrictMock(Class<T> type)
PowerMock.mockStaticNice(Class<?> type)

などがある。
コンストラクタを呼び出す場合は下記。

PowerMock.expectNew(<コンストラクタを監視したいクラス>, <引数1>, <引数2>,・・・).andReturn(<返すインスタンス>);

privateメソッドをMock化したい場合は下記。

instance = PowerMock.createPartialMock(<モックにするクラス>, new String[]{<メソッド名1>, <メソッド名2>,・・・});
PowerMock.expectPrivate(<インスタンス>, <メソッド名>, <引数1>, <引数2>,...).andReturn(<戻り値>);

詳しくは「http://powermock.googlecode.com/svn/docs/powermock-1.5.2/apidocs/index.html」。

参考:
https://code.google.com/p/powermock/
http://d.hatena.ne.jp/irof/20130517/p1
http://blog.tarotaro.org/archives/772