2009年12月14日月曜日

jMock/JDave/unfinalizer

メソッドの final 修飾子を外す jdave-unfinalizer というツールを使ってみた。

メソッドやクラスについている private とか final は、余りむやみに外すべきじゃないけど、特にテスト目的でたまに必要になる。ただ、private なメソッドを呼び出すのは簡単だけど、final メソッドのオーバーライドはちょっと難しい。

というわけで、ユニットテストの中で final を外すやり方を試してみる。実験の段取りは
  1. テスト対象とモックされるオブジェクトを書いてみる
  2. 普通の JMock テストを書いて、ちゃんと動かす。
  3. モックされるオブジェクトのメソッドを final にして、テストが失敗することを示す。
  4. unfinalizerを使うと、final が外れて2と同じ結果が得られる事を確認する。
という運びになる。

■ 1.テスト対象とモック対象
テスト対象は Greeter クラスで、まあいかにも作為的な仕様だけど「文字列 word で初期化された Greeter オブジェクトは、greet() 実行時に Echoer オブジェクトの echo() メソッドに word を渡し、その戻り値をクライアントに返す。」というふうにする。
public class Greeter {
   private final String word;
   public Greeter(String word) {
      this.word = word;
   }
   public String greet(Echoer echoer) {
      return echoer.echo(word);
   }
}
・以下のクラス Echoer は Greeter と協動するクラスで、こちらがモック対象となる
public class Echoer {
   public String echo(String str) {
      return str;
   }
}
■ 2.普通に JMock テストを通す
以下のコードでは、「文字列 "hello" で初期化された Greeter の greet() が呼ばれると、それに伴って Echoer の echo() が"hello"というパラメータとともに、ただ一度呼ばれる。」という事を、モッキングで確かめている。
@RunWith(JMock.class)
public class GreeterTest {
   private Mockery context = new Mockery() {{
      setImposteriser(ClassImposteriser.INSTANCE);
   }};
   @Test
   public void testHello() {
      final Echoer echoer = context.mock(Echoer.class);
      context.checking(new Expectations(){{
         oneOf(echoer).echo("hello");
      }});
      new Greeter("hello").greet(echoer);
   }
}
ここまで書いて、Outline ビューから testHello() をJUnitで実行する。いつもどおりにモックテストが成功するはず。

■ 3.final をつけて失敗させる
ここで Echoer echo() に final をつけて ”public final String echo(String str)”というシグネーチャにして実行してみる。すると、テスト失敗のメッセージからは原因が分かりにくいが、とにかく失敗する。(デバッガで追ってみると、モックじゃない本物のメソッドが実行されいて、それのため一度だけ呼ばれるはずのメソッドが呼ばれていないと認識されている模様。)

■ 4.unfinalizer を使ってみる
  • まず、ここからJDave の zip をダウンロードし、中にある jdave-unfinalizer-1.1.jarをプロジェクト直下に置く。
  • 次に、下図のように GreeterTest.testHello() の JUnit 実行設定で、VM argument に -javaagent:${workspace_loc:/unfinalizer-test1}/jdave-unfinalizer-1.1.jarを指定する。
  • ここで再度 testHello()メソッドを実行してみると、今度は上手くいったことが確認できる。final メソッドのオーバーライドができた。


========
モックテストの中で final メソッドの呼び出しを検証するような状況は、テストファーストを守って開発している場合、多分めったにない。だけどテストコードがないプロダクトで、後付テストを書く場合なんかには、珍しい状況ではない。そんなとき時、やり方がわからないとちょっと困る。

とりあえずメソッドに関しては、これで確認できたし、クラスも同じ事だろう。あとは、enum の列挙値を、テスト実行時に追加できるような仕組みが欲しい場面もたまにある。これも考えておきたい。

0 件のコメント:

コメントを投稿