JUnit Rule 原理分析

為了加深對JUnit Rule的理解,將其拆分出來單獨(dú)作為一篇文章講述.

JUnit Rule原理分析

在寫自定義Rule之前先對之前說到的系統(tǒng)實(shí)現(xiàn)的Rule做一個簡單的原理分析,這樣更能加深我們對自定義Rule的理解.強(qiáng)烈建議配合源碼查看, 否則可能不知所云.

JUnit4的默認(rèn)TestRunner 為org.junit.runners.BlockJUnit4ClassRunner,其中有一個methodBlock方法,該方法是運(yùn)行測試的核心方法:

protected Statement methodBlock(FrameworkMethod method) {
    Object test;
    try {
        test = (new ReflectiveCallable() {
            protected Object runReflectiveCall() throws Throwable {
                return BlockJUnit4ClassRunner.this.createTest();
            }
        }).run();
    } catch (Throwable var4) {
        return new Fail(var4);
    }

    Statement statement = this.methodInvoker(method, test);
    statement = this.possiblyExpectingExceptions(method, test, statement);
    statement = this.withPotentialTimeout(method, test, statement);
    statement = this.withBefores(method, test, statement);
    statement = this.withAfters(method, test, statement);
    statement = this.withRules(method, test, statement);
    return statement;
}

Note : 解釋一下, Runner只是一個抽象類,表示用于運(yùn)行Junit測試用例的工具,通過它可以運(yùn)行測試并通知給Notifier運(yùn)行的結(jié)果。

在JUnit執(zhí)行每個測試方法之前,methodBlock方法都會被調(diào)用,它用來把這個測試包裝成一個Statement。Statement表示一個具體的動作,例如測試方法的執(zhí)行,Before方法的執(zhí)行和Rule的調(diào)用。它使用責(zé)任鏈模式,將Statement層層包裹,就能形成一個完整的測試,JUnit最后執(zhí)行這個Statement。從上面的代碼中可以看到,以下內(nèi)容被包裝進(jìn)了Statement中:

  1. 測試方法的執(zhí)行;
  2. 異常測試,對應(yīng)@Test(expected=XXX.class);
  3. 超時測試,對應(yīng)@Test(timeout=XXX);
  4. Before方法,對應(yīng)@Before注解的方法;
  5. After方法,對應(yīng)@After注解的方法;
  6. Rule的執(zhí)行

在Statement中使用evaluate方法控制Statement執(zhí)行的先后順序,比如Before方法對應(yīng)的Statement的RunBefores:

public class RunBefores extends Statement {
    private final Statement next;

    private final Object target;

    private final List<FrameworkMethod> befores;

    public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) {
        this.next = next;
        this.befores = befores;
        this.target = target;
    }

    @Override
    public void evaluate() throws Throwable {
        for (FrameworkMethod before : befores) {
            before.invokeExplosively(target);
        }
        next.evaluate();
    }
}

在evaluate中,所有的Before方法會被最先調(diào)用,因?yàn)锽efore方法必須要在測試執(zhí)行之前調(diào)用,然后再執(zhí)行next的evaluate去調(diào)用下一個Statement。

同理RunAfter是在執(zhí)行測試之后再去執(zhí)行After方法的內(nèi)容。

public class RunAfters extends Statement {
    private final Statement next;

    private final Object target;

    private final List<FrameworkMethod> afters;

    public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) {
        this.next = next;
        this.afters = afters;
        this.target = target;
    }

    @Override
    public void evaluate() throws Throwable {
        List<Throwable> errors = new ArrayList<Throwable>();
        try {
            next.evaluate();
        } catch (Throwable e) {
            errors.add(e);
        } finally {
            for (FrameworkMethod each : afters) {
                try {
                    each.invokeExplosively(target);
                } catch (Throwable e) {
                    errors.add(e);
                }
            }
        }
        MultipleFailureException.assertEmpty(errors);
    }
}

在理解上面的Statement之后,再回過頭看Rule的接口org.junit.rules.TestRule:

public interface TestRule {
    /**
     * Modifies the method-running {@link Statement} to implement this
     * test-running rule.
     *
     * @param base The {@link Statement} to be modified
     * @param description A {@link Description} of the test implemented in {@code base}
     * @return a new statement, which may be the same as {@code base},
     *         a wrapper around {@code base}, or a completely new Statement.
     */
    Statement apply(Statement base, Description description);
}

內(nèi)部只有一個applay方法,用于包裹上一級的Statement并返回一個新的Statement。所以如果實(shí)現(xiàn)一個Rule主要就是實(shí)現(xiàn)一個Statement。

自定義Rule

通過上面分析我們就可以知道如何實(shí)現(xiàn)一個Rule,我們舉個例子:


舉個栗子

下面這個例子是可以根據(jù)自己輸入的Count來決定測試方法循環(huán)執(zhí)行,并在每次執(zhí)行前和執(zhí)行后做了相應(yīng)的打印。

package com.lulu.androidtestdemo.junit.rule;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

/**
 * Created by zhanglulu on 2018/1/24.
 */
public class LoopRule implements TestRule {
    private Statement base;
    private Description description;
    private int loopCount;

    public LoopRule(int loopCount) {
        this.loopCount = loopCount;
    }

    @Override
    public Statement apply(Statement base, Description description) {
        this.base = base;
        this.description = description;
        System.out.println("apply");
        return new LoopStatement(base);
    }

    public class LoopStatement extends Statement {
        private final Statement base;
        public LoopStatement(Statement base) {
            this.base = base;
        }
        @Override
        public void evaluate() throws Throwable {
            for (int i = 0; i < loopCount; i++) {
                System.out.println("Loop " + i + " Started");
                base.evaluate();
                System.out.println("Loop " + i + " Finished");
            }
        }
    }
}

測試我們的LoopRule

public class TestLoopRule {
    @Rule
    public LoopRule customRule = new LoopRule(3);

    @Test
    public void testMyCustomRule() {
        System.out.println("execute testMyCustomRule");
    }
}

執(zhí)行結(jié)果:

執(zhí)行結(jié)果

輕松搞定 ()

敲行代碼再睡覺
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容