JUnit4.8.2ソース分析-5.1 Statementの複合コマンド

8414 ワード

抽象クラス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とBlockJUnit 4 ClassRunnerには長いコードがあります.Erich Gammaのスタイルに従って、すべてのタイプが短いのが憎くて、どうして複合コマンドを抽出しないのですか?
BlockJUnit 4 ClassRunnerを例に、orgにかかわらずMethodBlockを設計する.junit.runnersパッケージはツールとしてorgにあります.junit.internal.runners.statementsパッケージはStatementの他のサブクラス(具体的なコマンド)と一緒にいても良いです.関連コードをMethodBlockに配置すると、非常に簡単で明確です.次の約180行のコードはBlockJUnit 4 ClassRunnerから抽出され、みんなは安逸です.
以下のコードでは,それらのアシストメソッドのパラメータは省略できる.
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();
    }
}
BlockJUnit 4 ClassRunnerでは
methodBlock(method).evaluate();
new MethodBlock(method,test)に変更する.evaluate();