背景
我在 浅解 JUnit 4 这个专栏陆续添加了一些文章。现有的文章初步探讨了 Test/Runner/RunnerBuilder 这些核心类的逻辑。但是 JUnit 4 的入口在哪里呢?本文会继续对这个问题进行探讨(由于 JUnitCore 这个类涉及的内容较多,一篇文章写不完,本文是 下篇,上篇的链接是 浅解 JUnit 4 第八篇:JUnitCore (上))。
要点
正文
我创建了一个小项目来讨论本文的问题
项目结构
这个项目的结构和 浅解 JUnit 4 第七篇:AllDefaultPossibilitiesBuilder 一文中的 项目结构 那一小节所描述的相同。为了避免在两篇文章之间跳来跳去,我还是把项目结构在本文中复述一遍 ⬇️
这个项目中包含以下目录/文件
src/main/java/org/example目录下有以下文件SimpleAdder.java
src/test/java/org/study目录下有以下文件SimpleAdderTest.java
pom.xml(在项目顶层)
其中 SimpleAdder.java 的内容如下 ⬇️
package org.example;
public class SimpleAdder {
public int add(int a, int b) {
return a + b;
}
}
SimpleAdderTest.java 的内容如下 ⬇️
package org.study;
import org.example.SimpleAdder;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.JUnitCore;
public class SimpleAdderTest {
private final SimpleAdder simpleAdder = new SimpleAdder();
@Test
public void testAdd() {
Assert.assertEquals(2, simpleAdder.add(1, 1));
}
public static void main(String[] args) {
JUnitCore.runClasses(SimpleAdderTest.class);
}
}
pom.xml 的内容如下 ⬇️
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>junit-study</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
JUnitCore 里的 runClasses(Class<?>...) 方法
根据 JUnitCore 的 javadoc 的描述(如下图所示 ⬇️),我们可以调用 runClasses(Class<?>...) 方法
这里涉及如下 6 个方法,我们逐个来看
runClasses(Class<?>... classes)runClasses(Computer computer, Class<?>... classes)run(Computer computer, Class<?>... classes)run(Request request)run(Runner runner)defaultComputer()
第 1 个方法: runClasses(Class<?>... classes)
我把这个方法的代码复制到下方了 ⬇️
/**
* Run the tests contained in <code>classes</code>. Write feedback while the tests
* are running and write stack traces for all failed tests after all tests complete. This is
* similar to {@link #main(String[])}, but intended to be used programmatically.
*
* @param classes Classes in which to find tests
* @return a {@link Result} describing the details of the test run and the failed tests.
*/
public static Result runClasses(Class<?>... classes) {
return runClasses(defaultComputer(), classes);
}
runClasses(Class<?>... classes) 方法所做的事情如下方的思维导图所示 ⬇️
第 2 个方法: runClasses(Computer computer, Class<?>... classes)
我把这个方法的代码复制到下方了 ⬇️
/**
* Run the tests contained in <code>classes</code>. Write feedback while the tests
* are running and write stack traces for all failed tests after all tests complete. This is
* similar to {@link #main(String[])}, but intended to be used programmatically.
*
* @param computer Helps construct Runners from classes
* @param classes Classes in which to find tests
* @return a {@link Result} describing the details of the test run and the failed tests.
*/
public static Result runClasses(Computer computer, Class<?>... classes) {
return new JUnitCore().run(computer, classes);
}
runClasses(Computer computer, Class<?>... classes) 方法所做的事情如下方的思维导图所示 ⬇️
第 3 个方法: run(Computer computer, Class<?>... classes)
我把这个方法的代码复制到下方了 ⬇️
/**
* Run all the tests in <code>classes</code>.
*
* @param computer Helps construct Runners from classes
* @param classes the classes containing tests
* @return a {@link Result} describing the details of the test run and the failed tests.
*/
public Result run(Computer computer, Class<?>... classes) {
return run(Request.classes(computer, classes));
}
run(Computer computer, Class<?>... classes) 方法所做的事情如下方的思维导图所示 ⬇️
我们在 Request 类的 classes(Computer computer, Class<?>... classes) 方法里打一个断点,断点的位置如下图所示 ⬇️
然后 debug SimpleAdderTest 里的 main 方法
可以观察到
builder变量是AllDefaultPossibilitiesBuilder的一个实例classes变量是一个数组(其中只有一个元素:org.study.SimpleAdderTest.class)
我们 Step Into Computer 类中的 getSuite(final RunnerBuilder builder, Class<?>[] classes) 方法
Computer 类中的 getSuite(final RunnerBuilder builder, Class<?>[] classes) 方法,会创建 Suite 类的某个匿名子类的实例,因此也会调用 Suite 类中的某个构造函数,我们 Step Into 这个构造函数
在 Suite 类的这个构造函数中,可以观察到
builder变量是RunnerBuilder的一个匿名子类的实例(它可以访问一个AllDefaultPossibilitiesBuilder的实例)classes变量是一个数组(其中只有一个元素:org.study.SimpleAdderTest.class)
基于以上观察,我们可以概括出 Request 类的 classes(Computer computer, Class<?>... classes) 方法所做的事情 ⬇️
- 创建一个
AllDefaultPossibilitiesBuilder类的实例 - 调用
Computer类中的getSuite(final RunnerBuilder builder, Class<?>[] classes)方法- 该方法会返回
Suite类的某个匿名子类的实例 - 可以(间接地)通过 来为测试类构建对应的
Runner - 对测试类 而言, 可以构建对应的
Runner: - 都是 的子节点
- 该方法会返回
- 将 包装成
Request对象
第 4 个方法: run(Request request)
我把这个方法的代码复制到下方了 ⬇️
/**
* Run all the tests contained in <code>request</code>.
*
* @param request the request describing tests
* @return a {@link Result} describing the details of the test run and the failed tests.
*/
public Result run(Request request) {
return run(request.getRunner());
}
run(Request request) 方法所做的事情如下方的思维导图所示 ⬇️
这个方法的 request 参数是通过 Request 类中 runner(final Runner runner) 方法构造的,所以我们可以简单将 Request 视为 Runner 的包装 ⬇️
第 5 个方法: run(Runner runner)
我把这个方法的代码复制到下方了 ⬇️
/**
* Do not use. Testing purposes only.
*/
public Result run(Runner runner) {
Result result = new Result();
RunListener listener = result.createListener();
notifier.addFirstListener(listener);
try {
notifier.fireTestRunStarted(runner.getDescription());
runner.run(notifier);
notifier.fireTestRunFinished(result);
} finally {
removeListener(listener);
}
return result;
}
这个方法负责运行 Runner,并通知相关的 RunListener。其中涉及的细节比较多,我们在后续的文章中再展开说。本文不讲这些细节。
第 6 个方法: defaultComputer()
我把这个方法的代码复制到下方了 ⬇️
static Computer defaultComputer() {
return new Computer();
}
defaultComputer() 这个方法会返回 Computer 的一个实例。
其他
画“JUnitCore 类的一个入口: runClasses(Class<?>... classes) 方法”一图所用到的代码
我用 PlantUML 画了那张图,所用到的代码如下 ⬇️
@startuml
'https://plantuml.com/class-diagram
title <i>JUnitCore</i> 类的一个入口: <i>runClasses(Class<?>... classes)</i> 方法
class org.junit.runner.JUnitCore {
+{static} Result runClasses(Class<?>... classes)
+{static} Result runClasses(Computer computer, Class<?>... classes)
+Result run(Computer computer, Class<?>... classes)
+Result run(Request request)
+Result run(Runner runner)
~{static} Computer defaultComputer()
}
note right of org.junit.runner.JUnitCore::"runClasses(Class<?>... classes)"
<i>return runClasses(defaultComputer(), classes);</i>
end note
note right of org.junit.runner.JUnitCore::"runClasses(Computer computer, Class<?>... classes)"
<i>return new JUnitCore().run(computer, classes);</i>
end note
note left of org.junit.runner.JUnitCore::"run(Computer computer, Class<?>... classes)"
<i>return run(Request.classes(computer, classes));</i>
end note
note left of org.junit.runner.JUnitCore::"run(Request request)"
<i>return run(request.getRunner());</i>
end note
note right of org.junit.runner.JUnitCore::"run(Runner runner)"
Result result = new Result();
RunListener listener = result.createListener();
notifier.addFirstListener(listener);
try {
notifier.fireTestRunStarted(runner.getDescription());
runner.run(notifier);
notifier.fireTestRunFinished(result);
} finally {
removeListener(listener);
}
return result;
end note
note left of org.junit.runner.JUnitCore::defaultComputer()
<i>return new Computer();</i>
end note
@enduml
画“runClasses(Class<?>... classes) 方法做了什么”一图所用到的代码
我用 PlantUML 画了那张图,所用到的代码如下 ⬇️
@startmindmap
'https://plantuml.com/mindmap-diagram
title <i>runClasses(Class<?>... classes)</i> 方法做了什么
* <i>runClasses(Class<?>... classes)</i> 方法
**[#Orange] 第 <i>1</i> 步: 调用 <i>defaultComputer()</i> 方法
***:此方法只有一行代码 ⬇️
<i>return new Computer();</i>;
**[#Orange] 第 <i>2</i> 步: 调用 <i>runClasses(Computer computer, Class<?>... classes)</i> 方法
caption 橙色节点展示了 <i>runClasses(Class<?>... classes)</i> 方法 所做的事情
@endmindmap
画"runClasses(Computer computer, Class<?>... classes) 方法做了什么" 一图所用到的代码
我用 PlantUML 画了那张图,所用到的代码如下 ⬇️
@startmindmap
'https://plantuml.com/mindmap-diagram
top to bottom direction
title <i>runClasses(Computer computer, Class<?>... classes)</i> 方法做了什么
* <i>runClasses(Computer computer, Class<?>... classes)</i> 方法
**[#Orange] 第 <i>1</i> 步: 创建 <i>JUnitCore</i> 的实例
**[#Orange]:第 <i>2</i> 步: 通过 <i>JUnitCore</i> 的实例,
调用 <i>run(Computer computer, Class<?>... classes)</i> 方法;
@endmindmap
画"run(Computer computer, Class<?>... classes) 方法做了什么"一图所用到的代码
我用 PlantUML 画了那张图,所用到的代码如下 ⬇️
@startmindmap
'https://plantuml.com/mindmap-diagram
top to bottom direction
title <i>run(Computer computer, Class<?>... classes)</i> 方法做了什么
* <i>run(Computer computer, Class<?>... classes)</i> 方法
**[#Orange]:第 <i>1</i> 步: 调用 <i>Request</i> 类里的
<i>classes(Computer, Class<?>...)</i> 方法;
**[#Orange] 第 <i>2</i> 步: 调用 <i>run(Request request)</i> 方法
@endmindmap
画 "run(Request request) 方法做了什么" 一图所用到的代码
我用 PlantUML 画了那张图,所用到的代码如下 ⬇️
@startmindmap
'https://plantuml.com/mindmap-diagram
top to bottom direction
title <i>run(Request request)</i> 方法做了什么
* <i>run(Request request)</i> 方法
**[#Orange]:第 <i>1</i> 步: 调用 <i>Request</i> 类里的
<i>getRunner()</i> 方法;
**[#Orange] 第 <i>2</i> 步: 调用 <i>run(Runner runner)</i> 方法
@endmindmap