JUnit Rule简述
Rule是JUnit 4.7之后新加入的特性,有点类似于拦截器,可以在测试类或测试方法执行前后添加额外的处理,本质上是对@BeforeClass, @AfterClass, @Before, @After等的另一种实现,只是功能上更灵活多变,易于扩展,且方便在类和项目之间共享。
JUnit的Rule特性提供了两个注解@Rule和@RuleClass,大体上说@Rule可以与@Before及@After对应,@ClassRule可以与@BeforeClass及@AfterClass对应。自JUnit4.10起可以使用TestRule接口代替此前一直在用的MethodRule接口,实际项目中可以通过实现TestRule或继承自JUnit内置Rule类进行扩展。
适用场景
在简述中已经提到Rule特性本身也是对@BeforeClass, @AfterClass, @Before, @After功能的另外实现,所以基本上这四种注解的使用场景都适用于Rule,同时JUnit内置的Rule类还能够提供这四种注解未提供的功能。总体上说Rule特性的适用场景包括但不限于如下需求:
- 在测试类或测试方法执行前后添加初始化或环境清理操作
- 在测试执行过程中收集错误信息且无需中断测试
- 在测试结束后添加额外的测试结果校验功能
- 在测试执行前后创建及删除测试执行过程中产生的临时文件或目录
- 对测试过程中产生的异常进行灵活校验
- 将多个Rules串接在一起执行
- 测试用例执行失败时重试指定次数
从使用习惯上来说,对于简单项目,@BeforeClass, @AfterClass, @Before, @After等注解已经能够满足测试需求;对于复杂点的项目,从易扩展、易维护和方便复用的角度考虑最好使用Rule特性,方便添加和移除Rule实例,灵活性大大提高。
注解分类
JUnit中通过两个注解@Rule和@ClassRule来实现Rule扩展,这两个注解使用时需要放在实现了TestRule接口的Rule变量或返回Rule的方法之上,且修饰符都必须为public。
二者具体区别如下:
- 被注解的变量或方法类型不同
- @Rule修饰的变量或方法的修饰符必须为public,非static
- @ClassRule修饰的变量或方法的修饰符必须为public static
- 注解的级别不同
- @Rule为变量或方法级注解,每个测试方法执行时都会调用被该注解修饰的Rule
- @ClassRule为类级注解,执行单个测试类时只会调用一次被该注解修饰的Rule
- 注解的对象限制不同
- @Rule无注解对象限制
- @ClassRule不能注解ErrorCollector(Verifier)
TestRule接口
TestRule是测试类或测试方法执行过程及报告信息的接口,可以在TestRule中添加初始化及环境清理的操作、监控测试执行的日志打印或UI截图操作、测试结果成功或失败校验操作等。TestRule仅定义了唯一的方法apply(),所以可以在TestRule实现类的apply()方法中加入测试项目需要的操作。
复制代码
public interface TestRule {
//在实现类的apply()中加入测试需要的操作,本质上是对Statement实例base的进一步封装
Statement apply(Statement base, Description description);
}
复制代码
JUnit内置Rule
除了Rule特性外,JUnit还新增了一些核心Rule,均实现了TestRule接口,包括Verifier抽象类,ErrorCollector实现类,ExternalResource抽象类,TemporaryFolder实现类,TestWatcher抽象类,TestName实现类,ExpectedException实现类,Timeout实现类及RuleChain实现类(deprecated)。各接口实现类及类图参考如下:
- Verifier:所有测试结束后对测试执行结果添加额外的逻辑验证测试最终成功与否。该抽象类为子类提供一个接口方法verify()供扩展
- ErrorCollector:是Verifier类的一个子类实现,用于在测试执行过程中收集错误信息,不会中断测试,最后调用verify()方法处理
- ExternalResource:外部资源管理。该抽象类为子类提供了两个接口方法before()和after(),可以根据项目实际需要覆写扩展
- TemporaryFolder:是抽象类ExternalResource的一个子类实现,用于在JUnit测试执行前后,创建和删除临时目录
- TestWatcher:监视测试方法生命周期的各个阶段。该抽象类为子类提供了五个接口方法succeeded(), failed(), skipped(), starting()及finished()供扩展
- TestName:是抽象类TestWatcher的一个子类实现,用于在测试执行过程中获取测试方法名称。在starting()中记录测试方法名,在getMethodName()中返回
- ExpectedException:与@Test中的expected相对应,提供更强大灵活的异常验证功能,@Test只能修饰待测试方法,ExpectedException可以修饰待测试类
- Timeout:与@Test中的timeout相对应,@Test只能修饰待测试方法,Timeout可以修饰待测试类
- RuleChain:用于将多个Rules串在一起执行。RuleChain已经deprecated了,但是其源码实现比较有趣,所以本篇没有直接去掉。
篇幅原因此处仅简要介绍这些Rules提供的功能,后续将在专门的Rule及TestRule实现类源码分析中详解其实现。
JUnit Rule源码分析
以下过程是以典型的单个待测试类调用BlockJUnit4ClassRunner执行测试为例进行分析,如果对源码分析无兴趣可直接跳到JUnit Rule扩展示例部分。
分析Rule特性的源码实现之前需要先梳理Statement的概念及执行过程,tStatement是对原子级测试的封装,我们在JUnit Runner中看到的测试用例执行过程是顺序执行不同注解修饰的测试方法,即@BeforeClass->@Before->@Test->@After->@Before->@Test->@After->@Before->@Test->@After->@AfterClass(此处以三个待测试方法为例)。那么JUnit是如何将这一系列串接在一起的呢?其设计思想就是通过Statement以责任链的模式将其层层封装,责任链中上个节点的Statement中都存在对下一个节点的引用。Statement可以说是JUnit的核心设计之一,理清了Statement的执行过程就抓住了JUnit实现原理的主线。
Rule特性又是如何织入Statement的封装与执行过程的呢?我们知道Rule特性中有两个注解@ClassRule和@Rule用来修饰Rule变量或返回Rule的方法,这些变量或方法返回值的类型都需要实现TestRule接口,而TestRule中唯一定义的方法apply()的返回值类型就是Statement,所以JUnit中Rule特性的实现类同样是Statement的一种。根据BlockJUnit4ClassRunner的父类ParentRunner中的classBlock()方法中的调用,以及BlockJUnit4ClassRunner中methodBlock()方法中的调用,我们基本上可以梳理出@ClassRule和@Rule在整个Statement责任链中的执行顺序,以JUnit内置的Rule实现类ErrorCollector,TemporaryFolderr和TestName为例(含两个@Test修饰的待测试方法):
@ClassRule注解ErrorCollector类实例
执行顺序为:
@BeforeClass->@Before->@Test->@After->@Before->@Test->@After->@AfterClass->@ClassRule(verify())
@ClassRule注解TemporaryFolder类实例
执行顺序为:
@ClassRule(before())->@BeforeClass->@Before->@Test->@After->@Before->@Test->@After->@AfterClass->@ClassRule(after())
@Rule注解TestName类实例
执行顺序为:
@BeforeClass->@Rule(starting())->@Before->@Test->@After->@Rule(starting())->@Before->@Test->@After->@AfterClass
如果测试用例中@ClassRule和@Rule两个都存在,则按实际覆写的接口方法所处测试阶段顺序织入测试执行过程。
上述部分是分析Statement的设计思想及Rule在Statement执行过程中的顺序,文字描述通常都比较晦涩,还是特出关键源码一步步解读比较好,此处的简析仅仅是希望对Statement和Rule有一些大致的概念,方便后续的源码解读。
Statement封装过程的关键代码
Statement封装过程中有两个主要的方法classBlock()和methodBlock(),其中classBlock()是测试类级别的封装,也就是说测试类级注解@BeforeRule, @AfterRule以及@ClassRule修饰的方法在此处链式封装,methodBlock()是测试方法级别的封装,也就是说测试方法级别注解@Test(expected=xxx) , @Test(timeout=xxx), @Before, @After()及@Rule修饰的方法在此处链式封装。
先看一下classBlock()的调用过程:
复制代码
//org.junit.runners.ParentRunner
protected Statement classBlock(final RunNotifier notifier) {
Statement statement = childrenInvoker(notifier); //构造出所有测试方法基本的Statement类对象
if (!areAllChildrenIgnored()) {
statement = withBeforeClasses(statement); //对应@BeforeClass
statement = withAfterClasses(statement); //对应@AfterClass
statement = withClassRules(statement); //对应@ClassRule
}
return statement; //返回层层封装后的Statement类对象
}
复制代码
classBlock()中写的很清楚,首先调用childrenInvoker()构造Statement的基本行为,如果所有的子测试都没有被Ignore则通过withBeforeClasses(), withAfterClasses()及withClassRules()继续封装。先放一下,分析完childrenInvoker()的调用过程再从这里接入。
复制代码
//org.junit.runners.ParentRunner
protected Statement childrenInvoker(final RunNotifier notifier) {
return new Statement() {
@Override
public void evaluate() {
runChildren(notifier);
}
};
}
复制代码
childrenInvoker()的作用是构造基本的Statement行为,即执行所有的子测试runChildren(),在runChildren()中循环调用每个子测试runChild()。
复制代码
//org.junit.runners.ParentRunner
private void runChildren(final RunNotifier notifier) {
final RunnerScheduler currentScheduler = scheduler;
try {
for (final T each : getFilteredChildren()) {
currentScheduler.schedule(new Runnable() {
public void run() {
ParentRunner.this.runChild(each, notifier);
}
});
}
} finally {
currentScheduler.finished();
}
}
复制代码
复制代码
//org.junit.runners.ParentRunner
protected abstract void runChild(T child, RunNotifier notifier);
复制代码
复制代码
//org.junit.runners.BlockJUnit4ClassRunner
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
if (isIgnored(method)) {
notifier.fireTestIgnored(description);
} else {
Statement statement = new Statement() {
@Override
public void evaluate() throws Throwable {
methodBlock(method).evaluate();
}
};
runLeaf(statement, description, notifier);
}
}
复制代码
因为ParentRunner中只有runChild()的抽象方法,所以该方法的具体实现在其子类BlockJUnit4ClassRunner中,子类的runChild()中调用了测试方法级的层层封装methodBlock()。
复制代码
//org.junit.runners.BlockJUnit4ClassRunner
protected Statement methodBlock(final FrameworkMethod method) {
Object test;
try {
test = new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
return createTest(method);
}
}.run();
} catch (Throwable e) {
return new Fail(e);
}
Statement 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);
return statement;
}
复制代码
复制代码
//org.junit.runners.BlockJUnit4ClassRunner
protected Object createTest() throws Exception {
return getTestClass().getOnlyConstructor().newInstance();
}
复制代码
复制代码
//org.junit.runners.BlockJUnit4ClassRunner
protected Statement methodInvoker(FrameworkMethod method, Object test) {
return new InvokeMethod(method, test);
}
复制代码
methodBlock()中首先在createTest()中通过反射构造实例,在将该实例及FrameworkMethod类对象method作为methodInvoker()的入参构造出基本的Statement类对象。
复制代码
//org.junit.internal.runners.statements.InvokeMethod
public class InvokeMethod extends Statement {
private final FrameworkMethod testMethod;
private final Object target;
public InvokeMethod(FrameworkMethod testMethod, Object target) {
this.testMethod = testMethod;
this.target = target;
}
@Override
public void evaluate() throws Throwable {
testMethod.invokeExplosively(target);
}
}
复制代码
构造出基本的Statement类对象后,在执行后续操作对该Statement类对象进行层层封装。篇幅原因就不再对如下possiblyExpectingExceptions等五个方法的调用过程作进一步解析,这些方法调用和下面将要讲解的classBlock()方法实现中的下半部分很相似,只是此处是测试方法级的封装调用,classBlock()中是测试类级的封装调用。
复制代码
Statement statement = methodInvoker(method, test); //构造出测试方法基本的Statement类对象
statement = possiblyExpectingExceptions(method, test, statement); //对应@Test(expected=xxx)
statement = withPotentialTimeout(method, test, statement); //对应@Test(timeout=xxx), deprecated
statement = withBefores(method, test, statement); //对应@Before
statement = withAfters(method, test, statement); //对应@After
statement = withRules(method, test, statement); //对应@Rule
return statement; //返回层层封装后的待测试方法
复制代码
再回到前面classBlock()中的分析过程,该方法的后半部分会对构造出的所有方法的基本statement类对象作进一步封装,依次为withBeforeClasses(), withAfterClasses()及withClassRules()。
复制代码
//org.junit.runners.ParentRunner
protected Statement classBlock(final RunNotifier notifier) {
Statement statement = childrenInvoker(notifier); //构造出所有测试方法基本的Statement类对象
if (!areAllChildrenIgnored()) {
statement = withBeforeClasses(statement); //对应@BeforeClass
statement = withAfterClasses(statement); //对应@AfterClass
statement = withClassRules(statement); //对应@ClassRule
}
return statement; //返回层层封装后的Statement类对象
}
复制代码
复制代码
//org.junit.runners.ParentRunner
protected Statement withBeforeClasses(Statement statement) {
List befores = testClass
.getAnnotatedMethods(BeforeClass.class);
return befores.isEmpty() ? statement :
new RunBefores(statement, befores, null);
}
复制代码
withBeforeClasses()调用过程:提取出待测试类中用@BeforeClass注解的所有方法,再把这些方法和childrenInvoker()中构造出的基本Statement类对象作为入参用Statement的子类RunBefores重新封装并返回。
复制代码
//org.junit.runners.ParentRunner
protected Statement withAfterClasses(Statement statement) {
List afters = testClass
.getAnnotatedMethods(AfterClass.class);
return afters.isEmpty() ? statement :
new RunAfters(statement, afters, null);
}
复制代码
withAfterClasses()调用过程:提取出待测试类中用@AfterClass注解的所有方法,再把这些方法和withBeforeClasses()中返回的Statement类对象作为入参用Statement的子类RunAfters重新封装并返回。
复制代码
//org.junit.runners.ParentRunner
private Statement withClassRules(Statement statement) {
List classRules = classRules();
return classRules.isEmpty() ? statement :
new RunRules(statement, classRules, getDescription());
}
复制代码
withClassRules()调用过程:提取出待测试类中用@ClassRule注解的所有Rule类变量或返回值为Rule类的方法,再把这些变量和方法同withAfterClasses()中返回的Statement类对象作为入参用Statement的子类RunRules重新封装并返回。
复制代码
//org.junit.runners.ParentRunner
protected List classRules() {
ClassRuleCollector collector = new ClassRuleCollector();
testClass.collectAnnotatedMethodValues(null, ClassRule.class, TestRule.class, collector);
testClass.collectAnnotatedFieldValues(null, ClassRule.class, TestRule.class, collector);
return collector.getOrderedRules();
}
复制代码
复制代码
//org.junit.runners.ParentRunner
private static class ClassRuleCollector implements MemberValueConsumer {
final List entries = new ArrayList();
public void accept(FrameworkMember member, TestRule value) {
ClassRule rule = member.getAnnotation(ClassRule.class);
entries.add(new RuleContainer.RuleEntry(value, RuleContainer.RuleEntry.TYPE_TEST_RULE,
rule != null ? rule.order() : null));
}
public List getOrderedRules() {
if (entries.isEmpty()) {
return Collections.emptyList();
}
Collections.sort(entries, RuleContainer.ENTRY_COMPARATOR);
List result = new ArrayList(entries.size());
for (RuleContainer.RuleEntry entry : entries) {
result.add((TestRule) entry.rule);
}
return result;
}
}
复制代码
classRules()方法用于获取@ClassRule修饰的所有TestRule实现类。
复制代码
//org.junit.rules.RunRules
public class RunRules extends Statement {
private final Statement statement;
public RunRules(Statement base, Iterable rules, Description description) {
statement = applyAll(base, rules, description);
}
@Override
public void evaluate() throws Throwable {
statement.evaluate();
}
private static Statement applyAll(Statement result, Iterable rules,
Description description) {