为了加深对JUnit Rule的理解,将其拆分出来单独作为一篇文章讲述.
JUnit Rule原理分析
在写自定义Rule之前先对之前说到的系统实现的Rule做一个简单的原理分析,这样更能加深我们对自定义Rule的理解.强烈建议配合源码查看, 否则可能不知所云.
JUnit4的默认TestRunner 为org.junit.runners.BlockJUnit4ClassRunner,其中有一个methodBlock方法,该方法是运行测试的核心方法:
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只是一个抽象类,表示用于运行Junit测试用例的工具,通过它可以运行测试并通知给Notifier运行的结果。
在JUnit执行每个测试方法之前,methodBlock方法都会被调用,它用来把这个测试包装成一个Statement。Statement表示一个具体的动作,例如测试方法的执行,Before方法的执行和Rule的调用。它使用责任链模式,将Statement层层包裹,就能形成一个完整的测试,JUnit最后执行这个Statement。从上面的代码中可以看到,以下内容被包装进了Statement中:
- 测试方法的执行;
- 异常测试,对应@Test(expected=XXX.class);
- 超时测试,对应@Test(timeout=XXX);
- Before方法,对应@Before注解的方法;
- After方法,对应@After注解的方法;
- Rule的执行
在Statement中使用evaluate方法控制Statement执行的先后顺序,比如Before方法对应的Statement的RunBefores:
public class RunBefores extends Statement {
private final Statement next;
private final Object target;
private final List befores;
public RunBefores(Statement next, List 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方法会被最先调用,因为Before方法必须要在测试执行之前调用,然后再执行next的evaluate去调用下一个Statement。
同理RunAfter是在执行测试之后再去执行After方法的内容。
public class RunAfters extends Statement {
private final Statement next;
private final Object target;
private final List afters;
public RunAfters(Statement next, List afters, Object target) {
this.next = next;
this.afters = afters;
this.target = target;
}
@Override
public void evaluate() throws Throwable {
List errors = new ArrayList();
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);
}
内部只有一个applay方法,用于包裹上一级的Statement并返回一个新的Statement。所以如果实现一个Rule主要就是实现一个Statement。
自定义Rule
通过上面分析我们就可以知道如何实现一个Rule,我们举个例子:
下面这个例子是可以根据自己输入的Count来决定测试方法循环执行,并在每次执行前和执行后做了相应的打印。
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");
}
}
执行结果:
轻松搞定 (▽)