在JUnit中定制Runner

JUnit4 Customer Runners

Posted by Rancho on 2019-05-29 Word Count 700, 3 min to read

前言

本文将快速介绍如何在JUnit测试框架中使用自定义Runner来运行单测
当前, 这需要配合@RunWith注解

准备

首先, 添加项目依赖

1
2
3
4
5
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>

实现Runner

JUnit框架提供了抽象类Runner, 它定义了两个抽象方法

1
2
3
public abstract Description getDescription();

public abstract void run(RunNotifier var1);

另外, 它还需要实现Describable接口, 即实现getDescription() 方法
接下来我们实现一个CustomerRunner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;

import java.lang.reflect.Method;

/**
* @author rancho
* @date 2019-05-29
*/
public class CustomerRunner extends Runner {

private Class testedClass;

public CustomerRunner(Class testedClass) {
super();
this.testedClass = testedClass;
}

/**
* implements Describable interface
* @return Description
*/
@Override
public Description getDescription() {
return Description.createTestDescription(testedClass, "A Customer runner");
}

/**
* invoke the target tested methods using reflection
* @param notifier used for firing events that have information about the test progress
*/
@Override
public void run(RunNotifier notifier) {
System.out.println("running the tests based on CustomerRunner" + testedClass);

try {
Object testedObject = testedClass.newInstance();
for (Method method : testedClass.getMethods()) {
if (method.isAnnotationPresent(Test.class)) {
notifier.fireTestStarted(Description.createTestDescription(testedClass, method.getName()));
method.invoke(testedObject);
notifier.fireTestFinished(Description.createTestDescription(testedClass, method.getName()));
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

在这个实现类中, 我们定义了一个接受类参数的构造函数, 这是JUnit框架对Runner的规范. 单测运行时, JUnit会将目标类传给Runner的构造器.
getDescription方法用于返回描述信息, 而最关键的就在于run方法实现, 通过反射来调用目标方法.
另外, 通过run方法的入参RunNotifier, 我们可以出发具有各种测试进度信息的事件. 这里我们仅在目标方法调用前后触发事件. 感兴趣的话可以自行翻下RunNotifier中定义的其他方法

接下来, 我们随便写个测试用例来用下这个Runner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
* @author rancho
* @date 2019-05-29
*/
@RunWith(CustomerRunner.class)
public class CustomerRunnerTest {

Calculator calculator = new Calculator();

@Test
public void testCalculator() {
System.out.println("start Calculator");
assertEquals("addition", 8, calculator.add(5, 3));
}

}

class Calculator {
public int add(int a, int b) {
return a + b;
}
}

Runner是JUnit中的低级运行器, 相比之下, 其子类ParentRunnerBlockJUnit4Runner更加易于定制和扩展
ParentRunner同样是个抽象类, 它会以分层的方式来运行测试
一般情况, 如果你想对Runner做定制, 从BlockJUnit4Runner来派生子类是不错的选择

小结

通过定制JUnit Runner, 开发人员可以自由控制测试执行的整个过程. 一些流行的第三方Runner实现还有SpringJUnit4ClassRunner, MockitoJUnitRunner, HierarchicalContextRunner
如果你想runwith multiple runner, 可以考虑从多个父类派生出新的子类Runner
而在PowerMock中, 这个问题是通过代理的方式解决的(@PowerMockRunnerDelegate), 也是个不错的思路