平常我们执行 JUnit 用例时,可以使用命令行或在 IDE 中使用 Run As: JUnit Test,直接得到测试结果;但是背后的执行过程是怎么样的,值得我们深思,理解框架代码逻辑,有助于更好的使用该框架(或二次开发),本文将通过分析 JUnitCore.java 源码一一道来。
JUnitCore 使用外观模式(facade),对外提供一致的界面,同时支持运行 JUnit 4 或 JUnit 3.8.x 用例,通过命令行执行用例,形如:java org.junit.runner.JUnitCore TestClass1 TestClass2 ....
Calculator.java 功能为计算形如 “1+2+3+...” 的和。
public class Calculator{
// 计算累加和,形如“1+2+3”
public int evaluate(String expression){
int sum = 0;
for(String summand:expression.split("\\+")){
sum += Integer.valueOf(summand);
}
return sum;
}
}
CalculatorTest.java 为测试 new Calculator().evaluate(String expression) 的标准 JUnit4 测试类。
// 测试evaluate方法:
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class CalculatorTest{
@Test
public void testEvaluatesExpression(){
Calculator calculator = new Calculator();
int sum = calculator.evaluate("1+2+3");
assertEquals(6, sum);
}
}
命令行执行 JUnit:
[root@localhost junittest]# java -cp .:junit-4.10.jar org.junit.runner.JUnitCore CalculatorTest
JUnit version 4.10
.
Time: 0.01
OK (1 test)
输出结果解析:The single . means that one test has been run and the OK in the last line tells you that your test is successful.
JUnitCore 构造函数:
public JUnitCore() {
this.notifier = new RunNotifier();
}
首先新建 RunNotifier(运行通知器),告知 JUnit,运行用例的进展。
JUnitCore 入口主函数:
public static void main(String[] args) {
Result result = new JUnitCore().runMain(new RealSystem(), args);
System.exit((result.wasSuccessful()) ? 0 : 1);
}
主函数调用 runMain(new RealSystem(), args) 方法,返回运行结果 Result。
Result runMain(JUnitSystem system, String[] args) {
system.out().println("JUnit version " + Version.id());
JUnitCommandLineParseResult jUnitCommandLineParseResult = JUnitCommandLineParseResult
.parse(args);
RunListener listener = new TextListener(system);
addListener(listener);
return run(jUnitCommandLineParseResult.createRequest(defaultComputer()));
}
JUnitCommandLineParseResult
.parse(args),解析 main() 参数,将参数(测试类:CalculatorTest)赋给 JUnitCommandLineParseResult 类成员:List> classes;
实例化 RunListener(运行监听器),将测试类所有方法的运行过程事件注册给 RunNotifier(运行通知器)。
jUnitCommandLineParseResult.createRequest(defaultComputer()) 返回一个 Request(描述怎么去执行用例)
在 createRequest() 方法中,会新建 AllDefaultPossibilitiesBuilder 实例,AllDefaultPossibilitiesBuilder extends RunBuilder, RunBuilder 运行构建者,为测试类构建测试运行器(runners)的一种策略.
AllDefaultPossibilitiesBuilder.java 源码:
public class AllDefaultPossibilitiesBuilder extends RunnerBuilder {
private final boolean canUseSuiteMethod;
public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) {
this.canUseSuiteMethod = canUseSuiteMethod;
}
public Runner runnerForClass(Class<?> testClass) throws Throwable {
List builders = Arrays.asList(new RunnerBuilder[] { ignoredBuilder(),
annotatedBuilder(), suiteMethodBuilder(), junit3Builder(),
junit4Builder() });
for (RunnerBuilder each : builders) {
Runner runner = each.safeRunnerForClass(testClass);
if (runner != null) {
return runner;
}
}
return null;
}
protected JUnit4Builder junit4Builder() {
return new JUnit4Builder();
}
protected JUnit3Builder junit3Builder() {
return new JUnit3Builder();
}
protected AnnotatedBuilder annotatedBuilder() {
return new AnnotatedBuilder(this);
}
protected IgnoredBuilder ignoredBuilder() {
return new IgnoredBuilder();
}
protected RunnerBuilder suiteMethodBuilder() {
if (this.canUseSuiteMethod) {
return new SuiteMethodBuilder();
}
return new NullBuilder();
}
}
其中,如下源码根据测试类寻找特定的 Runner, 使用责任链模式,只要测试类在对应的 RunBuilder 不返回空,该类的测试运行器就属于该 RunRuilder 下的 runner。
for (RunnerBuilder each : builders) {
Runner runner = each.safeRunnerForClass(testClass);
if (runner != null) {
return runner;
}
}
foreach 依次判断测试类属于哪一个 Runner: ignoredBuilder, annotatedBuilder, suiteMethodBuilder,junit3Builder , junit4Builder, 判断条件如下:
ignoredBuilder的判断条件:
testClass.getAnnotation(Ignore.class) != null
annotatedBuilder的判断条件:
RunWith annotation = (RunWith) currentTestClass
.getAnnotation(RunWith.class);
if (annotation != null)
return buildRunner(annotation.value(), testClass);
suiteMethodBuilder的判断条件:
if (this.canUseSuiteMethod)
junit3Builder的判断条件:
if (isPre4Test(testClass))
junit4Builder的判断条件:
直接返回return new BlockJUnit4ClassRunner(testClass),就是说以上判断条件都不满足时,使用junit4Builder
所以,平时我们写 JUnit4 用例时,不添加任何@RunWith,默认使用 BlockJUnit4ClassRunner 测试运行器。
最后执行:
public Result run(Runner runner) {
Result result = new Result();
RunListener listener = result.createListener();
this.notifier.addFirstListener(listener);
try {
this.notifier.fireTestRunStarted(runner.getDescription());
runner.run(this.notifier);
this.notifier.fireTestRunFinished(result);
} finally {
removeListener(listener);
}
return result;
}
this.notifier.fireTestRunStarted(runner.getDescription());告知运行通知器 notifier 测试开始。
runner.run(this.notifier);使用对应的测试运行器执行用例,本文测试运行器为 BlockJUnit4ClassRunner, statement.evaluate() 执行用例。
this.notifier.fireTestRunFinished(result);获取执行结果 result。
JUnitCore is a facade for running tests. It supports running JUnit 4 tests, JUnit 3.8.x tests, and mixtures. To run tests from the command line, run java org.junit.runner.JUnitCore TestClass1 TestClass2 ....
Register an instance of this class with RunNotifier to be notified of events that occur during a test run. All of the methods in this class are abstract and have no implementation; override one or more methods to receive events.
If you write custom runners, you may need to notify JUnit of your progress running tests. Do this by invoking the RunNotifier passed to your implementation of Runner.run(RunNotifier). Future evolution of this class is likely to move fireTestRunStarted(Description) and fireTestRunFinished(Result) to a separate class since they should only be called once per run.
A RunnerBuilder is a strategy for constructing runners for classes. Only writers of custom runners should use RunnerBuilders. A custom runner class with a constructor taking a RunnerBuilder parameter will be passed the instance of RunnerBuilder used to build that runner itself.
Represents a strategy for computing runners and suites.
A Request is an abstract description of tests to be run.
Represents one or more actions to be taken at runtime in the course of running a JUnit test suite.