抽象类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();