為了加深對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中:
- 測試方法的執(zhí)行;
- 異常測試,對應(yīng)@Test(expected=XXX.class);
- 超時測試,對應(yīng)@Test(timeout=XXX);
- Before方法,對應(yīng)@Before注解的方法;
- After方法,對應(yīng)@After注解的方法;
- 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é)果:

輕松搞定 (▽)
