minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

では実際にjMockを使ってみます。

メインコード

メインクラスを Sample、外部インターフェイスを External とします。

public interface External {
  void someExternal();
}

public class Sample {

  private External external;

  public void some() {
    System.out.println("some start");
    external.someExternal();
    System.out.println("some end");
  }
  
  public void setExternal(External external) {
    this.external = external;
  }

}

必要なjarファイルのダウンロード

今回使用するのはjMock2(JDK1.5以上版)の 2.1.0 です。
JUnit4を使います。

ダウンロードしてきたら、以下のファイルにクラスパスを通します。

jmock-2.1.0.jar
jmock-junit4-2.1.0.jar
hamcrest-api-1.0.jar
hamcrest-library-1.0.jar

やや多いように感じますが、ファイルサイズは合計で280KB程度です。

テストコード

これに対応するテストケースを書いてみましょう。
とりあえず説明は後にして、サンプルテストコードを載せてしまいます。

@RunWith(JMock.class)
public class SampleTest {
  private Mockery context = new JUnit4Mockery();
  private External external;
  private Sample instance;
  
  @Before
  public void setUp() throws Exception {
    external = context.mock(External.class);
    instance = new Sample();
    instance.setExternal(external);
  }
  
  @Test
  public void testSome() {
    context.checking(new Expectations() {{
      allowing(external).someExternal();
    }});
    instance.some();
  }
  
}

正直慣れるまではこのコードを理解するのが難しいかとは思いますが、
これから徐々に説明していきます。

setUpなどはとりあえず置いといて、testSome() メソッドの中身を見てみましょう。
context.checking()instance.some() の2文だけです。

instance は setUp メソッドの中で初期化されています。
このUTを実行すると、テストは成功するはずです。

モックオブジェクトと「実体」

では仮に、context.checking() 文をコメントアウトしてテストを実行してみます。

java.lang.AssertionError:
  unexpected invocation: external.someExternal()
no expectations specified:
  did you forget to start an expectation with a cardinality clause?

こんな感じのエラーが発生してしまいました。
何故でしょうか?

instance.some() メソッドに処理が移ると、まず標準出力に

some start

と出力されます。ここまでは大丈夫ですね。では次です。

external.someExternal();

この文を実行しようとして、jMockは先程の例外を発生させたのです。

ここで思い出しましょう。
external はモックオブジェクトなのです。
そして何も定義されていません。
つまり、someExternal() を実行しようとしたのですが
実体が定義されていないので「何をしていいかわからない」のです。

通常、インスタンスがあるのにメソッドの実体が定義されていないなんて事は
有り得ないのですが、モックオブジェクトでは仮想的にそういう事が有り得ます。
初めはとっつきにくいこの概念ですが、いずれ慣れるはずです。

「期待する動作」の定義

では何故、初めに実行したコードでは例外が発生しなかったのでしょうか。
これが、jMock を理解する上で最も重要な「期待値の設定」という話に繋がります。

ではもう一度、コメントアウトしたコードを見てみましょう。

context.checking(new Expectations() {{
  allowing(external).someExternal();
}});

1行目と3行目はjMockの習わしだと思って軽く飛ばします。
肝心なのは2行目です。この文が何を意味しているかというと…

external インスタンスが someExternal() メソッドを実行することを期待している

これが先程話した「期待値の設定」です。
つまり、someExternal() を実行しようとしたときに
予め「期待値を設定」しておくことで
「何をしていいかわからない」ではなく
「確かにこのメソッドが実行された」と jMock が判断して
処理が続けられるのです。

  1. テストを実行

  2. モックオブジェクトのメソッド呼び出しがあった

  3. そのメソッド呼び出しは「期待」されたものか?

  4. 期待されていないものならば、例外(Assertion)を発生させて処理を中断

  5. 期待されていたものならば、メソッド呼び出しを正常終了させる

  6. もしメソッドが戻り値を持つものならば、そのときの「期待する戻り値」を返す

戻り値については次回以降で説明します。

setUp

最後に、放っておいた setUp メソッドについて簡単に説明します。

external = context.mock(External.class);
instance = new Sample();
instance.setExternal(external);

1行目で、モックオブジェクトを生成しています。
内部的には Proxy クラスによってリフレクションを使ったりしていますが
詳細は知らなくても構いません。

2行目で Sample クラスのインスタンスを生成し、
3行目で先程作成したモックオブジェクトをSampleクラスにセットしています。

では続いて、戻り値を持つメソッドについて説明していきましょう。