JUnit4学习笔记(四):利用Rule扩展JUnit

一、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方法:

protected Statement methodBlock(FrameworkMethod method) {
    Object test;
    try {
        test = new ReflectiveCallable() {
            @Override
            protected Object runReflectiveCall() throws Throwable {
                return createTest();
            }
        }.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;
}  

在JUnit执行每个测试方法之前,methodBlock方法都会被调用,用于把该测试包装成一个Statement。Statement代表一个具体的动作,例如测试方法的执行,Before方法的执行或者Rule的调用,类似于J2EE中的Filter,Statement也使用了责任链模式,将Statement层层包裹,就能形成一个完整的测试,JUnit最后会执行这个Statement。从上面代码可以看到,有以下内容被包装进Statement中:

1)测试方法的执行;

2)异常测试,对应于@Test(expected=XXX.class);

3)超时测试,对应与@Test(timeout=XXX);

4)Before方法,对应于@Before注解的方法;

5)After方法,对应于@After注解的方法;

6)Rule的执行。

在Statement中,可以用evaluate方法控制Statement执行的先后顺序,比如Before方法对应的Statement - RunBefores:

 1 public class RunBefores extends Statement {
 2     private final Statement fNext;
 3
 4     private final Object fTarget;
 5
 6     private final List<FrameworkMethod> fBefores;
 7
 8     public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) {
 9         fNext = next;
10         fBefores = befores;
11         fTarget = target;
12     }
13
14     @Override
15     public void evaluate() throws Throwable {
16         for (FrameworkMethod before : fBefores) {
17             before.invokeExplosively(fTarget);
18         }
19         fNext.evaluate();
20     }
21 }  

在evaluate中,所有Before方法会先被调用,因为Before方法必须要在测试执行之前调用,然后再执行fNext.evaluate()调用下一个Statement。

理解了Statement,再看回Rule的接口org.junit.rules.TestRule:

1 public interface TestRule {
2     Statement apply(Statement base, Description description);
3 }  

里面只有一个apply方法,用于包裹上级Statement并返回一个新的Statement。因此实现Rule主要是需要实现一个Statement。

四、自定义Rule

通过上面的分析,我们大概知道了如何实现一个Rule,下面是一个例子:

 1 /*
 2    用于循环执行测试的Rule,在构造函数中给定循环次数。
 3  */
 4 public class LoopRule implements TestRule{
 5     private int loopCount;
 6
 7     public LoopRule(int loopCount) {
 8         this.loopCount = loopCount + 1;
 9     }
10
11     @Override
12     public Statement apply(final Statement base, Description description) {
13         return new Statement() {
14             //在测试方法执行的前后分别打印消息
15             @Override
16             public void evaluate() throws Throwable {
17                 for (int i = 1; i < loopCount; i++) {
18                     System.out.println("Loop " + i + " started!");
19                     base.evaluate();
20                     System.out.println("Loop "+ i + " finished!");
21                 }
22             }
23         };
24     }
25 }
26  

使用该自定义的Rule:

1 @Rule
2 public LoopRule loopRule = new LoopRule(3);
3
4 @Test
5 public void testLoopRule() {
6     System.out.println("Test invoked!");
7 }
8  

执行后打印出以下信息:

  1. Loop 1 started!
  2. Test invoked!
  3. Loop 1 finished!
  4. Loop 2 started!
  5. Test invoked!
  6. Loop 2 finished!
  7. Loop 3 started!
  8. Test invoked!
  9. Loop 3 finished!

JUnit4学习笔记(四):利用Rule扩展JUnit

时间: 2024-10-25 21:50:53

JUnit4学习笔记(四):利用Rule扩展JUnit的相关文章

Caliburn.Micro学习笔记(四)----IHandle&lt;T&gt;实现多语言功能

Caliburn.Micro学习笔记(四)----IHandle<T>实现多语言功能 说一下IHandle<T>实现多语言功能 因为Caliburn.Micro是基于MvvM的UI与codebehind分离, binding可以是双向的所以我们想动态的实现多语言切换很是方便今天我做一个小demo给大家提供一个思路 先看一下效果 点击英文  变成英文状态点chinese就会变成中文                          源码的下载地址在文章的最下边 多语言用的是资源文件建

Junit4学习笔记--方法的执行顺序

package com.lt.Demo.TestDemo; import java.util.Arrays; import java.util.Collection; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; im

WEB前端学习笔记 四

接上一篇,web学习笔记 四,在此感谢您对此篇笔记的认可,但转发时请注明文章出自网知博学. 2.0  html的语法格式 html的标签要写在尖括号中 :<> 在在英文输入法状态下,按住shift键然后再按它左侧的尖括号就可了, 先学习一个简单的h1标签,是个标题标签,在html中这样写: <h1>我在h1标签中,我就是标题</h1> 那么h1标签中所包裹的文字,就标记成标题了.通过浏览器的解析后在页面上显示出来的效果就是字体加粗,加黑,和word中的标题性质一样! 大

初探swift语言的学习笔记四(类对象,函数)

作者:fengsh998 原文地址:http://blog.csdn.net/fengsh998/article/details/29606137 转载请注明出处 假设认为文章对你有所帮助,请通过留言或关注微信公众帐号fengsh998来支持我,谢谢! swift扩展了非常多功能和属性,有些也比較奇P.仅仅有慢慢学习,通过经验慢慢总结了. 以下将初步学习一下类的写法. 码工,最大爱好就是看码,而不是文字,太枯燥. // // computer.swift // swiftDemo // // C

如何输出格式化的字符串(学习笔记四)

如何输出格式化的字符串(学习笔记四) 我们经常会输出类似 '亲爱的xxx你好!你xx月的话费是xx,余额是xx' 之类的字符串,而xxx的内容都是根据变量变化的,所以,需要一种简便的格式化字符串的方式. 在Python中,采用的格式化方式和C语言是一致的,用 % 实现,举例如下: >>> 'Hello, %s' % ('world') 'Hello, world' 截图如下: 注: (1)红线上的百分号,两边可有空格也可无: (2)对于只有一个变量的情况,我们可以将'world'外的括号

JUnit4学习笔记(三):assertThat语法与Matcher

一.使用JUnit的一般测试语法 org.junit.Assert类里有各种断言方法,大部分情况下我们会像下面这个例子一样编写测试: 1 public class AssertThatTest { 2 private int id = 6; 3 private boolean trueValue = true; 4 private Object nullObject = null; 5 private String msg = "Hello World"; 6 7 @Test 8 pu

公益图书馆-学习笔记四

1.复选框及其使用方法: <select class="form-control statusSelect" name="select" onchange="javascript:location.href=this.value;"> //注意: onchange内的javascript函数 <option selected value="/user/index?status=yes">我被选中<

JUnit4学习笔记(二):参数化测试与假定(Assumption)

一.一个简单的测试 编写一个只有一种运算的计算器: 1 public class Calculator { 2 public static double divide(int dividend, int divisor) { 3 return dividend / divisor; 4 } 5 } 为这个方法编写测试: 1 public class CalculatorTest { 2 //允许误差 3 private static final double DELTA = 0.01; 4 5

laravel3学习笔记(四)

原作者博客:ieqi.net ==================================================================================================== 视图 Laravel3遵循MVC模式,视图层负责将控制器处理好的数据展示出来,view层相关代码文件保存在application/views目录下,并且以php结尾. 因为PHP本身就可以和HTML混写的特性,一般而言,PHP框架的View层某种程度上也可以作为模板使