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 を使えばもっと簡潔に書けると思う。
0 件のコメント:
コメントを投稿