抽象类Statement作为命令模式的Command,只有一个方法public abstractvoid evaluate() throws Throwable;
作为命令模式的Invoker的各种Runner,将发出各种Statement并以它们表示运行JUnit测试组的整个过程。针对方法的标注如@Test 、@Before、@After、@BeforeClass、@AfterClass和各种测试场景,JUnit在org.junit.internal.runners.statements包中定义了Statement的子类——具体命令。
那么,测试一个方法时,需要判断@Test的ExpectException、FailOnTimeout,处理RunBefores、RunAfters,还要应对Rule……这就需要一个更大的Statement——复合命令。
ParentRunner<T>和BlockJUnit4ClassRunner中有长长的代码做这个事情。yqj2065感到小小奇怪的是:按照Erich Gamma的风格,恨不得每个类型都短短的,为什么不把复合命令提取出来呢?
以BlockJUnit4ClassRunner为例,设计一个MethodBlock,不管在org.junit.runners包作为工具,还是在org.junit.internal.runners.statements包与Statement的其他子类(具体命令)在一起都好。将相关代码放在MethodBlock中,非常容易而且清晰。下面的大约180行代码从BlockJUnit4ClassRunner中抽取出来,大家都安逸。
下面的代码中,那些辅助方法的参数都可以省略。
package org.junit.runners; import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.Test.None; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; import org.junit.runners.model.FrameworkField; import org.junit.runners.model.FrameworkMethod; import org.junit.internal.runners.statements.ExpectException; import org.junit.internal.runners.statements.Fail; import org.junit.internal.runners.statements.FailOnTimeout; import org.junit.internal.runners.statements.InvokeMethod; import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunBefores; import org.junit.rules.MethodRule; /** * Returns a Statement that, when executed, either returns normally if * {@code method} passes, or throws an exception if {@code method} fails. * * Here is an outline of the default implementation: * * <ul> * <li>Invoke {@code method} on the result of {@code createTest()}, and * throw any exceptions thrown by either operation. * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code * expecting} attribute, return normally only if the previous step threw an * exception of the correct type, and throw an exception otherwise. * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code * timeout} attribute, throw an exception if the previous step takes more * than the specified number of milliseconds. * <li>ALWAYS allow {@code @Rule} fields to modify the execution of the * above steps. A {@code Rule} may prevent all execution of the above steps, * or add additional behavior before and after, or modify thrown exceptions. * For more information, see {@link MethodRule} * <li>ALWAYS run all non-overridden {@code @Before} methods on this class * and superclasses before any of the previous steps; if any throws an * Exception, stop execution and pass the exception on. * <li>ALWAYS run all non-overridden {@code @After} methods on this class * and superclasses after any of the previous steps; all After methods are * always executed: exceptions thrown by previous steps are combined, if * necessary, with exceptions from After methods into a * {@link MultipleFailureException}. * </ul> * * This can be overridden in subclasses, either by overriding this method, * or the implementations creating each sub-statement. */ public class MethodBlock extends Statement{ private Statement statement; private final FrameworkMethod method; private final Object test; public MethodBlock(FrameworkMethod method, Object target) { this.method= method; test= target; } private void decorator(){ statement = methodInvoker(method, test); statement= possiblyExpectingExceptions(method, test, statement); statement= withPotentialTimeout(method, test, statement); statement= withBefores(method, test, statement); statement= withAfters(method, test, statement); statement= withRules(method, test, statement); } /* * BlockJUnit4ClassRunner * 除 makeNotifier */ // // Statement builders // /** * Returns a {@link Statement} that invokes {@code method} on {@code test} */ protected Statement methodInvoker(FrameworkMethod method, Object test) { return new InvokeMethod(method, test); } /** * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation * has the {@code expecting} attribute, return normally only if {@code next} * throws an exception of the correct type, and throw an exception * otherwise. * * @deprecated Will be private soon: use Rules instead */ @Deprecated protected Statement possiblyExpectingExceptions(FrameworkMethod method, Object test, Statement next) { Test annotation= method.getAnnotation(Test.class); return expectsException(annotation) ? new ExpectException(next, getExpectedException(annotation)) : next; } /** * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation * has the {@code timeout} attribute, throw an exception if {@code next} * takes more than the specified number of milliseconds. * * @deprecated Will be private soon: use Rules instead */ @Deprecated protected Statement withPotentialTimeout(FrameworkMethod method, Object test, Statement next) { long timeout= getTimeout(method.getAnnotation(Test.class)); return timeout > 0 ? new FailOnTimeout(next, timeout) : next; } /** * Returns a {@link Statement}: run all non-overridden {@code @Before} * methods on this class and superclasses before running {@code next}; if * any throws an Exception, stop execution and pass the exception on. * * @deprecated Will be private soon: use Rules instead */ @Deprecated protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) { List<FrameworkMethod> befores= getTestClass().getAnnotatedMethods( Before.class); return befores.isEmpty() ? statement : new RunBefores(statement, befores, target); } /** * Returns a {@link Statement}: run all non-overridden {@code @After} * methods on this class and superclasses before running {@code next}; all * After methods are always executed: exceptions thrown by previous steps * are combined, if necessary, with exceptions from After methods into a * {@link MultipleFailureException}. * * @deprecated Will be private soon: use Rules instead */ @Deprecated protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) { List<FrameworkMethod> afters= getTestClass().getAnnotatedMethods( After.class); return afters.isEmpty() ? statement : new RunAfters(statement, afters, target); } private Statement withRules(FrameworkMethod method, Object target, Statement statement) { Statement result= statement; for (MethodRule each : getTestClass().getAnnotatedFieldValues(target, Rule.class, MethodRule.class)) result= each.apply(result, method, target); return result; } private Class<? extends Throwable> getExpectedException(Test annotation) { if (annotation == null || annotation.expected() == None.class) return null; else return annotation.expected(); } private boolean expectsException(Test annotation) { return getExpectedException(annotation) != null; } private long getTimeout(Test annotation) { if (annotation == null) return 0; return annotation.timeout(); } public final TestClass getTestClass() { //ParentRunner<T> .validate() ? return new TestClass(test.getClass()); } @Override public void evaluate() throws Throwable { this.decorator(); statement.evaluate(); } }BlockJUnit4ClassRunner中只需要把 methodBlock(method).evaluate();
修改为 new MethodBlock(method,test).evaluate();