扩展 JUnit 4,使用定制的 Runner

JUnit 的测试用例总是由 Runner 去执行,JUnit 提供了 @RunWith 这个测试类的 Annotation, 可来指定自定义的 Runner。如果未指定特别的  Runner,那么会采用默认的 Runner,可能不同的环境,如 Eclipse,控制台下会有不同的默认 Runner。


如果不清楚 Runner 是什么,那么可能见过 @RunWith(SpringJUnit4ClassRunner.class) 这个东西,它有助你加载 Spring 的配置文件,及与 Spring 相关的事物。

那么自定义的 Runner 有什么用呢?它可以截获到 @BeforeClass, @AfterClass, @Before, @After 这些事件,也就是能在测试类开始和结束执行前后,每个测试方法的执行前后处理点事情。

比如说从外部读取内容进行初始化测试数据,而且 JUnit 本身就提供了 @RunWith(Parameterized.class)  这个参数化 Runner,用了为带参数测试方法循环填充数据进行测试。JUnit 的参数化测试比 C# 还是要笨拙一些,C# 直接用方法注解一行行设置参数,我想 JUnit 稍加定制的话也行的。

现在用个例子来说明定制化 Runner  的表现功力,需继承自 BlockJUnit4ClassRunner, 这又是继承自 ParentRunner。可自定义那些 with 开头的方法来截获相应的事件,如:

来自  ParentRunner  的 withBeforeClasses, withAfterClasses
和定义的 BlockJUnit4ClassRunner 中的  withBefores, withAfters, withPotentialTimeout

等等,总之祖先类中的  protected 方法一般就是给大家开的口,看例子:
 1package cc.unmi;
 2
 3import org.junit.runners.BlockJUnit4ClassRunner;
 4import org.junit.runners.model.FrameworkMethod;
 5import org.junit.runners.model.InitializationError;
 6import org.junit.runners.model.Statement;
 7
 8/** 
 9* @author Unmi
10* @created 2013-04-17
11*/
12@SuppressWarnings("deprecation")
13public class MyTestRunner extends BlockJUnit4ClassRunner {
14
15    private Class<?> clazz;
16
17    public MyTestRunner(Class<?> klass) throws InitializationError {
18        super(klass);
19        this.clazz = klass;
20    }
21
22    // 拦截 BeforeClass 事件
23    protected Statement withBeforeClasses(final Statement statement) {
24        final Statement junitStatement = super.withBeforeClasses(statement);
25        return new Statement() {
26            @Override
27            public void evaluate() throws Throwable {
28                System.out.println("Before Class: " + clazz.getName());
29                junitStatement.evaluate();
30            }
31
32        };
33    }
34
35    // 拦截每一个方法的 Before 事件
36    protected Statement withBefores(final FrameworkMethod method, Object target, final Statement statement) {
37
38        final Statement junitStatement = super.withBefores(method, target, statement);
39        return new Statement() {
40            @Override
41            public void evaluate() throws Throwable {
42                System.out.println("Before before method: " + method.getName());
43                junitStatement.evaluate();
44                System.out.println("After before method: " + method.getName());
45            }
46        };
47    }
48
49    // 截获每一个测试方法的 after 事件
50    protected Statement withAfters(final FrameworkMethod method, Object target, final Statement statement) {
51        final Statement junitStatement = super.withAfters(method, target, statement);
52        return new Statement() {
53            @Override
54            public void evaluate() throws Throwable {
55                System.out.println("After method: " + method.getName());
56                junitStatement.evaluate();
57            }
58
59        };
60    }
61
62    // 截获测试类的 after 事件
63    protected Statement withAfterClasses(final Statement statement) {
64        final Statement junitStatement = super.withAfterClasses(statement);
65        return new Statement() {
66            @Override
67            public void evaluate() throws Throwable {
68                junitStatement.evaluate();
69                System.out.println("After Class: " + clazz.getName());
70            }
71        };
72    }
73}

上面的  junitStatement.evaluate() 才是真正去执行相应的用 @Before, @After, @BeforeClass, @AfterClass 和 @Test 标的方法,所以可以在执行真正方法前面植入点什么。

看测试类:
 1package cc.unmi;
 2
 3import org.junit.After;
 4import org.junit.AfterClass;
 5import org.junit.Before;
 6import org.junit.BeforeClass;
 7import org.junit.Test;
 8import org.junit.runner.RunWith;
 9
10/** 
11* @author Unmi
12* @created 2013-04-17
13*/
14@RunWith(MyTestRunner.class)  
15public class MyTest {  
16
17    @BeforeClass
18    public static void beforeClass() {
19        System.out.println("execute beforeClass");
20    }
21
22    @Before
23    public void before() {
24        System.out.println("execute before");
25    }
26
27    @Test
28    public void should_return_something_if_age_equals_18() {
29        System.out.println("execute should_return_something_if_age_equals_18");
30    }
31
32    @After
33    public void after() {
34        System.out.println("execute after");
35    }
36
37        @AfterClass
38        public static void afterClass() {
39            System.out.println("execute afterClass");
40        }
41    }

看执行效果吧:

Before Class: cc.unmi.MyTest
execute beforeClass
After method: should_return_something_if_age_equals_18
Before before method: should_return_something_if_age_equals_18
execute before
execute should_return_something_if_age_equals_18
After before method: should_return_something_if_age_equals_18
execute after
execute afterClass
After Class: cc.unmi.MyTest

由于给出了的执行结果,所以就不多解释了。

应用场景探讨:因为在 “拦截” 方法中可以感知道当前执行类和方法,所以可以在 withBeforeXxx 时初始化测试数据,比如可以加入自定义的注解,根据注解,继续用反射的方式对类、实例变量进行初始化,或某些清理工作。

再学习下 JUnit 的 @RunWith(Parameterized.class) 参数化测试。除此之外,还有 @ClassRule 和  @Rule 也有异曲同功之妙,见 JUnit扩展方式(一)-使用Rule对JUnit进行扩展(JUnit4.10)

参考: 1. JUnit扩展方式(二)-使用Runner对JUnit进行扩展(基础)
          2.  JUnit中的测试套件和参数化测试
          3. 使用 Feed4JUnit 进行数据与代码分离的 Java 单元测试
          4. 使用RunWith注解改变JUnit的默认执行类,并实现自已的Listener
          5. JUnit扩展方式(一)-使用Rule对JUnit进行扩展(JUnit4.10) 永久链接 https://yanbin.blog/extend-junit-4-customized-runner/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。