一、Rule简介
Rule是JUnit4中的新特性,它让我们可以扩展JUnit的功能,灵活地改变测试方法的行为。JUnit中用@Rule和@ClassRule两个注解来实现Rule扩展,这两个注解需要放在实现了TestRule借口的成员变量(@Rule)或者静态变量(@ClassRule)上。@Rule和@ClassRule的不同点是,@Rule是方法级别的,每个测试方法执行时都会调用被注解的Rule,而@ClassRule是类级别的,在执行一个测试类的时候只会调用一次被注解的Rule
二、JUnit内置Rule
JUnit4中默认实现了一些常用的Rule:
TemporaryFolder Rule
使用这个Rule可以创建一些临时目录或者文件,在一个测试方法结束之后,系统会自动清空他们。
1 //创建TemporaryFolder Rule
2 //可以在构造方法上加入路径参数来指定临时目录,否则使用系统临时目录
3 @Rule
4 public TemporaryFolder tempFolder = new TemporaryFolder();
5
6 @Test
7 public void testTempFolderRule() throws IOException {
8 //在系统的临时目录下创建文件或者目录,当测试方法执行完毕自动删除
9 tempFolder.newFile("test.txt");
10 tempFolder.newFolder("test");
11 }
ExternalResource Rule
ExternalResource 是TemporaryFolder的父类,主要用于在测试之前创建资源,并在测试完成后销毁。
1 File tempFile;
2
3 @Rule
4 public ExternalResource extResource = new ExternalResource() {
5 //每个测试执行之前都会调用该方法创建一个临时文件
6 @Override
7 protected void before() throws Throwable {
8 tempFile = File.createTempFile("test", ".txt");
9 }
10
11 //每个测试执行之后都会调用该方法删除临时文件
12 @Override
13 protected void after() {
14 tempFile.delete();
15 }
16 };
17
18 @Test
19 public void testExtResource() throws IOException {
20 System.out.println(tempFile.getCanonicalPath());
21 }
22
ErrorCollector Rule
ErrorCollector允许我们收集多个错误,并在测试执行完后一次过显示出来
@Rule
public ErrorCollector errorCollector = new ErrorCollector();
@Test
public void testErrorCollector() {
errorCollector.addError(new Exception("Test Fail 1"));
errorCollector.addError(new Throwable("fff"));
}
Verifier Rule
Verifier是ErrorCollector的父类,可以在测试执行完成之后做一些校验,以验证测试结果是不是正确
1 String result;
2
3 @Rule
4 public Verifier verifier = new Verifier() {
5 //当测试执行完之后会调用verify方法验证结果,抛出异常表明测试失败
6 @Override
7 protected void verify() throws Throwable {
8 if (!"Success".equals(result)) {
9 throw new Exception("Test Fail.");
10 }
11 }
12 };
13
14 @Test
15 public void testVerifier() {
16 result = "Fail";
17 }
18
TestWatcher Rule
TestWatcher 定义了五个触发点,分别是测试成功,测试失败,测试开始,测试完成,测试跳过,能让我们在每个触发点执行自定义的逻辑。
1 @Rule
2 public TestWatcher testWatcher = new TestWatcher() {
3 @Override
4 protected void succeeded(Description description) {
5 System.out.println(description.getDisplayName() + " Succeed");
6 }
7
8 @Override
9 protected void failed(Throwable e, Description description) {
10 System.out.println(description.getDisplayName() + " Fail");
11 }
12
13 @Override
14 protected void skipped(AssumptionViolatedException e, Description description) {
15 System.out.println(description.getDisplayName() + " Skipped");
16 }
17
18 @Override
19 protected void starting(Description description) {
20 System.out.println(description.getDisplayName() + " Started");
21 }
22
23 @Override
24 protected void finished(Description description) {
25 System.out.println(description.getDisplayName() + " finished");
26 }
27 };
28
29 @Test
30 public void testTestWatcher() {
31 /*
32 测试执行后会有以下输出:
33 testTestWatcher(org.haibin369.test.RulesTest) Started
34 Test invoked
35 testTestWatcher(org.haibin369.test.RulesTest) Succeed
36 testTestWatcher(org.haibin369.test.RulesTest) finished
37 */
38 System.out.println("Test invoked");
39 }
TestName Rule
TestName能让我们在测试中获取目前测试方法的名字。
1 @Rule
2 public TestName testName = new TestName();
3
4 @Test
5 public void testTestName() {
6 //打印出测试方法的名字testTestName
7 System.out.println(testName.getMethodName());
8 }
Timeout与ExpectedException Rule
分别用于超时测试与异常测试,在JUnit4学习笔记(一):基本应用中有提到,这里不再举例。
三、实现原理与部分源码解析
在Junit4的默认Test Runner - org.junit.runners.BlockJUnit4ClassRunner中,有一个methodBlock方法:
1)测试方法的执行;
2)异常测试,对应于@Test(expected=XXX.class);
3)超时测试,对应与@Test(timeout=XXX);
4)Before方法,对应于@Before注解的方法;
5)After方法,对应于@After注解的方法;
6)Rule的执行。
在Statement中,可以用evaluate方法控制Statement执行的先后顺序,比如Before方法对应的Statement - RunBefores:
在evaluate中,所有Before方法会先被调用,因为Before方法必须要在测试执行之前调用,然后再执行fNext.evaluate()调用下一个Statement。
理解了Statement,再看回Rule的接口org.junit.rules.TestRule:
里面只有一个apply方法,用于包裹上级Statement并返回一个新的Statement。因此实现Rule主要是需要实现一个Statement。
四、自定义Rule
通过上面的分析,我们大概知道了如何实现一个Rule,下面是一个例子:
使用该自定义的Rule:
执行后打印出以下信息:
- Loop 1 started!
- Test invoked!
- Loop 1 finished!
- Loop 2 started!
- Test invoked!
- Loop 2 finished!
- Loop 3 started!
- Test invoked!
- Loop 3 finished!