背景
在 浅解 Junit 4 第四篇:类上的 @Ignore 注解 一文中,我们初步探讨了 测试类上的 @Ignore 注解是如何起作用的,要点如下图所示 👇

新的问题来了, <math xmlns="http://www.w3.org/1998/Math/MathML"> org.junit.internal.builders.IgnoredClassRunner \text{org.junit.internal.builders.IgnoredClassRunner} </math>org.junit.internal.builders.IgnoredClassRunner 又是怎么构建出来的呢?本文会对 IgnoredClassRunner 的构建过程进行探讨。
要点
- 可以使用构建者(
builder)设计模式来构建RunnerRunner的构建者(builder)是RunnerBuilderRunnerBuilder是抽象类,我们需要用它的子类来执行具体的构建逻辑
- 类上有
@Ignore的测试类,它对应的Runner是IgnoredClassRunner,而IgnoredClassRunner对应的构建者(builder)是IgnoredBuilder
正文
项目结构
探讨本文的问题,不需要在项目中加入新的 java 代码,项目中已有的代码在 浅解 Junit 4 第四篇:类上的 @Ignore 注解 一文中的 一个具体的例子 这一小节有具体的描述,这里就不赘述了。项目结构如下图所示(.idea/ 目录是 Intellij IDEA 生成的,可以忽略它)

IgnoredClassRunner 是如何构建的?
这里用到了 构建者(builder) 设计模式。用大白话说,就是对某个类 <math xmlns="http://www.w3.org/1998/Math/MathML"> C \text{C} </math>C 而言,我们不需要关心 <math xmlns="http://www.w3.org/1998/Math/MathML"> C \text{C} </math>C 具体是如何构建的,由专门的 <math xmlns="http://www.w3.org/1998/Math/MathML"> Builder C \text{Builder}\text{C} </math>BuilderC 来负责构建 <math xmlns="http://www.w3.org/1998/Math/MathML"> C \text{C} </math>C,我们只需要调用 <math xmlns="http://www.w3.org/1998/Math/MathML"> Builder C \text{Builder}\text{C} </math>BuilderC 里的方法(例如可能是 build(C c) 这样的方法)。生活中也有很多这样的例子。
- 我们用真实的棋盘下棋时,不必关心棋盘具体是如何制作出来的。棋盘的制造商会负责处理好各种细节(可以把棋盘的制造商视为棋盘的
builder) - 我们用电脑时,不必关心电脑具体是如何制造出来的。电脑的制造商会负责处理好各种细节(可以把电脑的制造商视为电脑的
builder) - 我们做算法题时,不必关心题目是如何编出来的。命题者会负责处理好各种细节(可以把命题者视为题目的
builder) - 我们看小说时,不必关心小说的情节是如何构思的。作者会负责处理好各种细节(可以把作者视为小说的
builder) - <math xmlns="http://www.w3.org/1998/Math/MathML"> ⋯ \cdots </math>⋯
就 IgnoredClassRunner 而言,它的构建者(builder)是 <math xmlns="http://www.w3.org/1998/Math/MathML"> org.junit.internal.builders.IgnoredBuilder \text{org.junit.internal.builders.IgnoredBuilder} </math>org.junit.internal.builders.IgnoredBuilder。而 IgnoredBuilder 是 <math xmlns="http://www.w3.org/1998/Math/MathML"> org.junit.runners.model.RunnerBuilder \text{org.junit.runners.model.RunnerBuilder} </math>org.junit.runners.model.RunnerBuilder 的子类。RunnerBuilder 和 IgnoredBuilder 的简要类图如下 👇

IgnoredBuilder 中的 runnerForClass((Class<?>) 的代码如下
java
@Override
public Runner runnerForClass(Class<?> testClass) {
if (testClass.getAnnotation(Ignore.class) != null) {
return new IgnoredClassRunner(testClass);
}
return null;
}
其逻辑比较直观
- 如果
testClass对应的类上有@Ignore注解,则返回对应的IgnoredClassRunner - 否则,返回
null
测试类,运行器(Runner),以及运行器的构建者(RunnerBuilder)三者之间的关系如下图所示

如果一个测试类 T 上带有 @Ignore 注解,那么此时测试类,运行器(Runner),以及运行器的构建者(RunnerBuilder)三者之间的关系如下图所示

其他
文中的"RunnerBuilder 和 IgnoredBuilder 的简要类图"一图是如何绘制的?
我是用 PlantUML 画的,具体代码如下
puml
@startuml
'https://plantuml.com/class-diagram
title <i>RunnerBuilder</i> 和 <i>IgnoredBuilder</i> 的简要类图
abstract class org.junit.runners.model.RunnerBuilder
class org.junit.internal.builders.IgnoredBuilder
org.junit.runners.model.RunnerBuilder <|-- org.junit.internal.builders.IgnoredBuilder
abstract class org.junit.runners.model.RunnerBuilder {
+{abstract}Runner runnerForClass(Class<?> testClass) throws Throwable
}
class org.junit.internal.builders.IgnoredBuilder {
+Runner runnerForClass(Class<?> testClass)
}
note right of org.junit.runners.model.RunnerBuilder::runnerForClass
这个方法的 <i>javadoc</i> 提到
Override to calculate the correct runner for a test class at runtime.
end note
note right of org.junit.internal.builders.IgnoredBuilder::runnerForClass
如果 <i>testClass</i> 对应的类上有 <i>@Ignore</i> 注解,
则此方法返回对应的 <i>IgnoredClassRunner</i>;
否则此方法返回 <i>null</i>
end note
caption 注意:图中只列出了本文关心的字段和方法
@enduml
文中的"测试类,运行器(Runner),以及运行器的构建者(RunnerBuilder)"一图是如何绘制的?
我是用 PlantUML 画的,具体代码如下
puml
@startmindmap
'https://plantuml.com/mindmap-diagram
top to bottom direction
title 测试类,运行器(<i>Runner</i>),以及运行器的构建者(<i>RunnerBuilder</i>)
*[#Orange] 一个测试类 <i>T</i>
**[#lightgreen] 对应的 <i>Runner</i> 是 <i>Runner<sub>T</sub></i>
***[#lightblue] 用 <i>RunnerBuilder</i> 的某个子类来构建 <i>Runner<sub>T</sub></i>
legend right
橙色节点表示测试类
浅绿色节点表示 <i>Runner</i>
浅蓝色节点表示 <i>RunnerBuilder</i>
endlegend
@endmindmap
文中的"当测试类上有 @Ignore 注解时"一图是如何绘制的?
我是用 PlantUML 画的,具体代码如下
puml
@startmindmap
'https://plantuml.com/mindmap-diagram
top to bottom direction
title 当测试类上有 <i>@Ignore</i> 注解时
*[#Orange]:一个带有 <i>@Ignore</i> 注解的测试类 <i>T</i>
(<b><i>T</i> 类上有 <i>@Ignore</i> 注解</b>);
**[#lightgreen] 对应的 <i>Runner</i> 是 <i>IgnoredClassRunner<sub>T</sub></i>
***[#lightblue]:对应的 <i>RunnerBuilder</i> 是 <i>IgnoredBuilder</i>
用 <i>IgnoredBuilder</i> 来构建 <i>IgnoredClassRunner<sub>T</sub></i>;
legend right
橙色节点表示测试类
浅绿色节点表示 <i>Runner</i>
浅蓝色节点表示 <i>RunnerBuilder</i>
endlegend
@endmindmap