Parameterized の使い方と JMock の使い方を知っていれば、コードの意味は自明だと思う。
@RunWith(ParameterizedJMockRunner.class)
public class ATest {
@Parameters public static Collection<Object[]> params() {
return Arrays.asList(new Object[][]{
{-1, -2}, {0, 0}, {1, 2}, {2, 4}});
}
Mockery ctx = new JUnit4Mockery();
int param;
int expected;
public ATest(int param, int expected) {
this.param = param;
this.expected = expected;
}
@Test public void testCallB() {
final B b = ctx.mock(B.class);
ctx.checking(new Expectations(){{ oneOf(b).foo(expected); }});
new A(b).callB(param);
}
}冒頭の RunWith で指定している ParameterizedJMockRunner は以下のようなもの。
public class ParameterizedJMockRunner extends Parameterized {
public ParameterizedJMockRunner(Class<?> klass) throws Throwable {
super(klass);
}
@Override
protected List<Runner> getChildren() {
List<Runner> original = super.getChildren();
List<Runner> result = new ArrayList<Runner>(original.size());
for (Runner each : super.getChildren()) try {
result.add(new ChildRunner(each));
} catch (InitializationError e) {
throw new RuntimeException(e);
}
return result;
}
}オーバライドした getChildren() は、基底クラスの Parameterized が持っている子 Runner を、自前の Runner である ChildRunner にすり替えている。
ChildRunner は、以下のようなコードにした。
class ChildRunner extends BlockJUnit4ClassRunner {
private Field mockeryField;
private final int parameterSetNumber;
private final List<Object[]> fParameterList;
public ChildRunner(Runner runner) throws InitializationError {
super(Util.getJavaClass(runner));
try {
parameterSetNumber = Util.parameterSetNumberOf(runner);
fParameterList = Util.parameterListOf(runner);
mockeryField = Util.findMockeryField(runner);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
@Override
public Object createTest() throws Exception {
return getTestClass().getOnlyConstructor().newInstance(computeParams());
}
private Object[] computeParams() throws Exception {
return fParameterList.get(parameterSetNumber);
}
@Override
protected String getName() {
return String.format("[%s]", parameterSetNumber);
}
@Override
protected String testName(final FrameworkMethod method) {
return String.format("%s[%s]", method.getName(),
parameterSetNumber);
}
@Override
protected void validateZeroArgConstructor(List<Throwable> errors) {
// constructor can, nay, should have args.
}
@Override
protected Statement classBlock(RunNotifier notifier) {
return childrenInvoker(notifier);
}
@Override
protected Statement methodInvoker(
final FrameworkMethod method, final Object test) {
return new InvokeMethod(method, test) {
@Override public void evaluate() throws Throwable {
try {
method.invokeExplosively(test);
assertMockeryIsSatisfied(test);
} catch (InvocationTargetException e) {
//省略: Test.expected の処理
throw e;
}
}
};
}
private void assertMockeryIsSatisfied(Object testFixture) {
mockeryOf(testFixture).assertIsSatisfied();
}
protected Mockery mockeryOf(Object test) {
try { return (Mockery) mockeryField.get(test); }
catch (Exception ex) { throw new RuntimeException(ex); }
}
}
コードの内容は、JMock クラスのコードと Parameterized の 内部クラスTestClassRunnerForParameters のコードを混ぜたような感じ。methodInvoker のコードが JMock 系で、それ以外が TestClassRunnerForParameters系。なんだかダラダラしたコードになったが、これでも例外処理とか手を抜いている。本当は Parameterized.TestClassRunnerForParameters コードを 継承したかったが、private なクラスなので駄目だった。
Util.xxx() とあるのは、ベタなリフレクションコードを別クラスに移動したもの。これは、さほど類推が難しくないだろうから、コードは割愛した。
こんな感じで JMock とParameterized を併用できる。まあ前ポストの、共通コードを別メソッドに括り出す方式に比べて、それ程優れてるとは言えない気もするが・・・
今回は pure Java で書いたが、AspectJ を使えばもっと簡潔に書けると思う。