火柴人战争遗产3
413.8MB · 2026-02-21
在以下两篇文章里,我们已经探讨了两种具体的 Runner (即 IgnoredClassRunner 和 Suite)是如何构建的。
但这只是两种具体的情况,那么 Runner 构建的一般情形是怎样的呢?本文会对此进行探讨。本文的主角是
AllDefaultPossibilitiesBuilder 会依次用 5 个候选的 RunnerBuilder 来执行下述操作
RunnerBuilder 来构建对应的 Runner
null,则返回它null,则继续尝试下一个 RunnerBuilder5 个候选的 RunnerBuilder 是
IgnoredBuilderAnnotatedBuilderSuiteMethodBuilder/NullBuilderJUnit3BuilderJUnit4Builder文中提到 JUnit 4 中的类,它们的全限定类名一般都比较长,所以文中有时候会用简略的写法(例如将 org.junit.runners.Suite 写成 Suite)。我在这一小节把简略类名和全限定类名的对应关系列出来
RunnerBuilder 系列| 简略的类名 | 全限定类名(Fully Qualified Class Name) |
|---|---|
RunnerBuilder | org.junit.runners.model.RunnerBuilder |
AllDefaultPossibilitiesBuilder | org.junit.internal.builders.AllDefaultPossibilitiesBuilder |
IgnoredBuilder | org.junit.internal.builders.IgnoredBuilder |
AnnotatedBuilder | org.junit.internal.builders.AnnotatedBuilder |
SuiteMethodBuilder | org.junit.internal.builders.SuiteMethodBuilder |
NullBuilder | org.junit.internal.builders.NullBuilder |
JUnit3Builder | org.junit.internal.builders.JUnit3Builder |
JUnit4Builder | org.junit.internal.builders.JUnit4Builder |
Runner 系列| 简略的类名 | 全限定类名(Fully Qualified Class Name) |
|---|---|
Runner | org.junit.runner.Runner |
ParentRunner | org.junit.runners.ParentRunner |
IgnoredClassRunner | org.junit.internal.builders.IgnoredClassRunner |
Suite | org.junit.runners.Suite |
JUnit38ClassRunner | org.junit.internal.runners.JUnit38ClassRunner |
BlockJUnit4ClassRunner | org.junit.runners.BlockJUnit4ClassRunner |
JUnit4 | org.junit.runners.JUnit4 |
我创建了一个小项目来讨论本文的问题,这个项目中包含以下目录/文件(.idea/ 目录是 Intellij IDEA 生成的,可以忽略它)
src/main/java/org/example 目录下有以下文件
SimpleAdder.javasrc/test/java/org/study 目录下有以下文件
SimpleAdderTest.javapom.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 d">
<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>
AllDefaultPossibilitiesBuilder 是如何构建 Runner 的?我们在 AllDefaultPossibilitiesBuilder 类的 runnerForClass(Class<?> testClass) 方法里打一个断点,断点的位置如下图所示
然后 debug SimpleAdderTest 的 main 方法。等运行到断点时,会看到 testClass 参数的值为 org.study.SimpleAdderTest.class
我们看一下 AllDefaultPossibilitiesBuilder 类中的 runnerForClass(Class<?> testClass) 方法的代码
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
List<RunnerBuilder> builders = Arrays.asList(
ignoredBuilder(),
annotatedBuilder(),
suiteMethodBuilder(),
junit3Builder(),
junit4Builder());
for (RunnerBuilder each : builders) {
Runner runner = each.safeRunnerForClass(testClass);
if (runner != null) {
return runner;
}
}
return null;
}
runnerForClass(Class<?> testClass) 方法的逻辑是
List<RunnerBuilder> 类型的 builders,builders 里的 5 个元素分别是
IgnoredBuilder 的实例AnnotatedBuilder 的实例SuiteMethodBuilder 的实例或 NullBuilder 的实例JUnit3Builder 的实例JUnit4Builder 的实例builders 中的各个元素来构建 Runner
null,则返回它null,则继续下一轮尝试builders 中的所有元素构建的结果都是 null,则返回 null我们分别看看 builders 里的 5 个元素是如何构建 Runner 的
IgnoredBuilder 的实例IgnoredBuilder 的代码是这样的
package org.junit.internal.builders;
import org.junit.Ignore;
import org.junit.runner.Runner;
import org.junit.runners.model.RunnerBuilder;
public class IgnoredBuilder extends RunnerBuilder {
@Override
public Runner runnerForClass(Class<?> testClass) {
if (testClass.getAnnotation(Ignore.class) != null) {
return new IgnoredClassRunner(testClass);
}
return null;
}
}
它的 runnerForClass(Class<?> testClass) 方法的逻辑是
testClass 上有 @Ignore 注解,则返回对应的 IgnoredClassRunnernull更多细节可以参考 浅解 JUnit 4 第五篇:IgnoredBuilder 和 RunnerBuilder 一文
AnnotatedBuilder 的实例AnnotatedBuilder 的主要代码如下 (package 和 import 语句已略去)
public class AnnotatedBuilder extends RunnerBuilder {
private static final String CONSTRUCTOR_ERROR_FORMAT = "Custom runner class %s should have a public constructor with signature %s(Class testClass)";
private final RunnerBuilder suiteBuilder;
public AnnotatedBuilder(RunnerBuilder suiteBuilder) {
this.suiteBuilder = suiteBuilder;
}
@Override
public Runner runnerForClass(Class<?> testClass) throws Exception {
for (Class<?> currentTestClass = testClass; currentTestClass != null;
currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) {
RunWith annotation = currentTestClass.getAnnotation(RunWith.class);
if (annotation != null) {
return buildRunner(annotation.value(), testClass);
}
}
return null;
}
private Class<?> getEnclosingClassForNonStaticMemberClass(Class<?> currentTestClass) {
if (currentTestClass.isMemberClass() && !Modifier.isStatic(currentTestClass.getModifiers())) {
return currentTestClass.getEnclosingClass();
} else {
return null;
}
}
public Runner buildRunner(Class<? extends Runner> runnerClass,
Class<?> testClass) throws Exception {
try {
return runnerClass.getConstructor(Class.class).newInstance(testClass);
} catch (NoSuchMethodException e) {
try {
return runnerClass.getConstructor(Class.class,
RunnerBuilder.class).newInstance(testClass, suiteBuilder);
} catch (NoSuchMethodException e2) {
String simpleName = runnerClass.getSimpleName();
throw new InitializationError(String.format(
CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName));
}
}
}
}
为什么会有 AnnotatedBuilder?
有些时候,用户需要指定 Runner(一个典型的例子是指定 Suite 作为 Runner,此时就需要 @RunWith(Suite.class) 这样的内容)。而 AnnotatedBuilder 可以解析测试类上的 @RunWith 注解。假设 @RunWith 注解的 value() 方法的返回值为 XXXRunner.class(XXXRunner 必须是某个 Runner),那么 AnnotatedBuilder 会尝试调用 XXXRunner.class 中的构造函数,从而构建 XXXRunner 的实例。
我们先整体理解一下 runnerForClass(Class<?> testClass) 方法,然后再仔细看它的具体逻辑。不严谨地说,这个方法做的事情可以概括为以下两步
然后我们再看看其中的细节
这个 runnerForClass(Class<?> testClass) 方法中有一个 for 循环,在这个循环的每一轮里,会
@RunWith 注解
buildRunner(Class<? extends Runner>, Class<?>) 方法来构建对应的 RunnerEnclosingClass(姑且称之为外围类或者宿主类吧),继续进行下一轮循环这么说可能有点抽象,我来举个具体的例子吧。假设我们用程序模拟了一个简单的 ALU 的功能,现在需要对其逻辑运算功能以及算术运算功能分别进行测试。测试用的代码也许会是这个样子
package org.study;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
public class ALUSuite {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(ALUSuite.LogicGateSuite.class);
System.out.println(result.getFailures());
}
@Suite.SuiteClasses({AndGateTest.class, OrGateTest.class, NotGateTest.class})
class LogicGateSuite {
}
@Suite.SuiteClasses({HalfAdderTest.class, FullAdderTest.class})
class ArithmeticSuite {
}
}
此时 @RunWith 注解是在 ALUSuite 这个类上,但测试类是 ALUSuite.LogicGateSuite.class
runnerForClass(Class<?> testClass) 方法里的 for 循环会运行两轮
| 第几轮循环 | currentTestClass 是什么 | currentTestClass 上有 @RunWith 注解吗? |
|---|---|---|
1 | ALUSuite.LogicGateSuite | 没有 |
2 | ALUSuite | 有 |
如果我们使用 @RunWith 注解时,指定的 Runner 是 Suite(即,value() 的返回值是 Suite.class),那么更多的细节可以参考 浅解 JUnit 4 第六篇:AnnotatedBuilder 和 RunnerBuilder 一文。
SuiteMethodBuilder 的实例或 NullBuilder 的实例从 SuiteMethod 类的 javadoc 来看 SuiteMethodBuilder 应该是用于支持 JUnit 3.8 风格的 suite() 静态方法的,我们现在只探索 JUnit 4 的内容,JUnit 3.8 的内容就跳过吧。
NullBuilder 的代码很少,我复制到下方了 从代码可以看出, NullBuilder 构建的 Runner 总会是 null。
package org.junit.internal.builders;
import org.junit.runner.Runner;
import org.junit.runners.model.RunnerBuilder;
public class NullBuilder extends RunnerBuilder {
@Override
public Runner runnerForClass(Class<?> each) throws Throwable {
return null;
}
}
JUnit3Builder 的实例JUnit3Builder 的代码不多,我复制到下方了 从代码(以及类名、方法名)来看,它是为了兼容旧的单元测试(JUnit 4 之前的单元测试)。我们现在只探索 JUnit 4 的内容,所以 JUnit3Builder 的内容就跳过吧。
package org.junit.internal.builders;
import org.junit.internal.runners.JUnit38ClassRunner;
import org.junit.runner.Runner;
import org.junit.runners.model.RunnerBuilder;
public class JUnit3Builder extends RunnerBuilder {
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
if (isPre4Test(testClass)) {
return new JUnit38ClassRunner(testClass);
}
return null;
}
boolean isPre4Test(Class<?> testClass) {
return junit.framework.TestCase.class.isAssignableFrom(testClass);
}
}
JUnit4Builder 的实例JUnit4Builder 的代码很少,我复制到下方了
package org.junit.internal.builders;
import org.junit.runner.Runner;
import org.junit.runners.JUnit4;
import org.junit.runners.model.RunnerBuilder;
public class JUnit4Builder extends RunnerBuilder {
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
return new JUnit4(testClass);
}
}
JUnit4Builder 的 runnerForClass(Class<?> testClass) 方法会返回一个 JUnit4 类的实例。
我用了 PlantUML 来画这张图,具体的代码如下
@startuml
'https://plantuml.com/class-diagram
title <i>org.junit.runners.model.RunnerBuilder</i> 和它的一些子类
abstract class org.junit.runners.model.RunnerBuilder
class org.junit.internal.builders.AllDefaultPossibilitiesBuilder
class org.junit.internal.builders.IgnoredBuilder
class org.junit.internal.builders.AnnotatedBuilder
class org.junit.internal.builders.SuiteMethodBuilder
class org.junit.internal.builders.NullBuilder
class org.junit.internal.builders.JUnit3Builder
class org.junit.internal.builders.JUnit4Builder
org.junit.runners.model.RunnerBuilder <|-- org.junit.internal.builders.AllDefaultPossibilitiesBuilder
org.junit.runners.model.RunnerBuilder <|-- org.junit.internal.builders.IgnoredBuilder
org.junit.runners.model.RunnerBuilder <|-- org.junit.internal.builders.AnnotatedBuilder
org.junit.runners.model.RunnerBuilder <|-- org.junit.internal.builders.SuiteMethodBuilder
org.junit.runners.model.RunnerBuilder <|-- org.junit.internal.builders.NullBuilder
org.junit.runners.model.RunnerBuilder <|-- org.junit.internal.builders.JUnit3Builder
org.junit.runners.model.RunnerBuilder <|-- org.junit.internal.builders.JUnit4Builder
@enduml
我用了 PlantUML 来画这张图,具体的代码如下
@startuml
'https://plantuml.com/class-diagram
title <i>org.junit.runner.Runner</i> 和它的一些子类
abstract class org.junit.runner.Runner
abstract class org.junit.runners.ParentRunner<T>
class org.junit.internal.builders.IgnoredClassRunner
class org.junit.runners.Suite
class org.junit.internal.runners.JUnit38ClassRunner
class org.junit.runners.BlockJUnit4ClassRunner
class org.junit.runners.JUnit4
org.junit.runner.Runner <|-- org.junit.runners.ParentRunner
org.junit.runner.Runner <|-- org.junit.internal.builders.IgnoredClassRunner
org.junit.runners.ParentRunner <|-- org.junit.runners.Suite : extend ParentRunner<Runner>
org.junit.runner.Runner <|-- org.junit.internal.runners.JUnit38ClassRunner
org.junit.runners.ParentRunner <|-- org.junit.runners.BlockJUnit4ClassRunner : extends ParentRunner<FrameworkMethod>
org.junit.runners.BlockJUnit4ClassRunner <|-- org.junit.runners.JUnit4
@enduml
我用了 PlantUML 来画这张图,具体的代码如下
@startuml
'https://plantuml.com/class-diagram
title <i>org.junit.runners.JUnit4</i> 继承体系简图
caption 注意: 图中只画了本文关心的类/接口/方法/字段
class org.junit.runner.Runner
class org.junit.runners.ParentRunner<T>
class org.junit.runners.BlockJUnit4ClassRunner
class org.junit.runners.JUnit4
org.junit.runner.Runner <|-- org.junit.runners.ParentRunner
org.junit.runners.ParentRunner <|-- org.junit.runners.BlockJUnit4ClassRunner : extends ParentRunner<FrameworkMethod>
org.junit.runners.BlockJUnit4ClassRunner <|-- org.junit.runners.JUnit4
abstract class org.junit.runner.Runner {
+{abstract} void run(RunNotifier notifier)
}
abstract class org.junit.runners.ParentRunner<T> {
+void run(final RunNotifier notifier)
#{abstract} List<T> getChildren()
}
class org.junit.runners.BlockJUnit4ClassRunner {
#List<FrameworkMethod> getChildren()
}
@enduml