JUnit4.8.2源代码分析-5.1 Statement之复合命令

时间:2022-08-17 05:09:07

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