2009年12月29日火曜日

@Runwith(Parameterized + JMock)

以下のような 2クラスがあるときの、A のテストを考える。
public interface B { void foo(int s); }

public class A {
   final B b;
   public A(B b) { this.b = b; }
   public void callB(int s) { b.foo(s * 2); }
}
コードの通り、クラス A はクラス B への 依存を持ち、「 A は、callB() の引数を 2倍して、B の foo() を呼び出す」というコラボレーションの仕様がある。

この A の B とのコラボレーションを検証したいが、assertX メソッドだとややこしくなるので、普通はモッキングの手法を使うことになる。
@RunWith(JMock.class)
public class ATest {
   Mockery context = new JUnit4Mockery();
   @Test public void testCallB() {
      final B b = context.mock(B.class);
      context.checking(new Expectations(){{ oneOf(b).foo(2); }});
      new A(b).callB(1);
   }
}
ここでテスト値が 1 だけなのは不十分なので、-1, 0, 2 での検証も追加したくなったとする。以下のように共通メソッドにくくり出して、呼び出しを並べても良いが・・・
@Test public void testCallB() {
   final B b = context.mock(B.class);
   assertCallB(b, -1, -2);
   assertCallB(b, 0, 0);
   assertCallB(b, 1, 2);
   assertCallB(b, 2, 4);
}
void assertCallB(final B b, int param, final int expected) {
   context.checking(new Expectations(){{ oneOf(b).foo(expected); }});
   new A(b).callB(param);
}
・・・ JUnitでは Parameterized という Suit Runner 派生クラスによる、Parameterized Test が標準でサポートされていて、こっちも活用してみたい。

ところが、アノテーションの仕組み上 RunWith を二重に指定する事はできない(できたとしても Runner の振る舞いがどう合成されるかは自明ではないが)ので、困ったことになる。
@RunWith(Parameterized.class)
@RunWith(JMock.class) // 認められない
public class ATest {
   @Parameters public static Collection<Object[]> params() {
      return Arrays.asList(new Object[][]{{-1, -2}, {0, 0}, {1, 2}, {2, 4}});
   }
   Mockery context = new JUnit4Mockery();
   final int param;
   final int expected;
   public ATest(int param, int expected) {
      this.param = param; 
      this.expected = expected;
   }
   @Test public void testCallB() {
      final B b = context.mock(B.class);
      context.checking(new Expectations(){{ oneOf(b).foo(expected); }});
      new A(b).callB(param);
   }
}
これを後で考えてみる。(←考えた[12/30])

0 件のコメント:

コメントを投稿