背景
在以下两篇文章中,我们已经探讨了 类上的 @Ignore 注解 以及 方法上的 @Ignore 注解 是如何发挥作用的。
那么 @Before 注解和 @After 注解又是如何发挥作用的呢?本文会对这个问题进行探讨。本文的主角是以下几位
- 注解
- 注解
- 抽象类
要点
Statement 以及它的一些子类的类图如下 ⬇️
一些类的全限定类名
文中提到 JUnit 4 中的类,它们的全限定类名一般都比较长,所以文中有时候会用简略的写法(例如将 org.junit.Before 写成 @Before)。我在这一小节把简略类名和全限定类名的对应关系列出来
| 简略的类名 | 全限定类名(Fully Qualified Class Name) |
|---|---|
After 或 @After | org.junit.After |
Before 或 @Before | org.junit.Before |
BlockJUnit4ClassRunner | org.junit.runners.BlockJUnit4ClassRunner |
JUnit4 | org.junit.runners.JUnit4 |
ParentRunner | org.junit.runners.ParentRunner |
RunAfters | org.junit.internal.runners.statements.RunAfters |
RunBefores | org.junit.internal.runners.statements.RunBefores |
Runner | org.junit.runner.Runner |
TestClass | org.junit.runners.model.TestClass |
Statement | org.junit.runners.model.Statement |
正文
一个具体的例子
我在本地创建了一个小项目来以便探讨本文的问题,这个项目的结构如下 ⬇️
.
├── pom.xml
└── src
├── main
│ └── java
│ └── org
│ └── example
│ └── SimpleCalculator.java
└── test
└── java
└── org
└── study
└── SimpleCalculatorTest.java
其中 SimpleCalculator.java 的内容如下 ⬇️
package org.example;
public class SimpleCalculator {
public int add(int a, int b) {
return a + b;
}
public int minus(int a, int b) {
return a - b;
}
}
SimpleCalculatorTest.java 的内容如下 ⬇️
package org.study;
import org.example.SimpleCalculator;
import org.junit.*;
import org.junit.runner.JUnitCore;
import java.util.Random;
public class SimpleCalculatorTest {
private int a;
private int b;
private String description;
private final SimpleCalculator calculator = new SimpleCalculator();
private static final int BOUND = 10;
@Before
public void prepare() {
Random random = new Random();
a = random.nextInt(BOUND);
b = random.nextInt(BOUND);
}
@Test
public void testAdd() {
int expectedResult = a + b;
description = String.format("%s = %s + %s", expectedResult, a, b);
Assert.assertEquals(expectedResult, calculator.add(a, b));
}
@Test
public void testMinus() {
int expectedResult = a - b;
description = String.format("%s = %s - %s", expectedResult, a, b);
Assert.assertEquals(expectedResult, calculator.minus(a, b));
}
@After
public void generateReport() {
String report = String.format("As [%s] holds, the test was successful", description);
System.out.println(report);
}
public static void main(String[] args) {
JUnitCore.runClasses(SimpleCalculatorTest.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>25</maven.compiler.source>
<maven.compiler.target>25</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>
如何找到 @Before/@After 注解
在 浅解 JUnit 4 第一篇: TestClass 一文中提到,对一个测试类 而言,JUnit 4 会生成对应的 。借助 ,我们就可以知道 中的哪些方法带有 @Before/@After 注解。
何时执行带有 @Before/@After 注解的方法
对普通的测试类 而言,它对应的 会是 的实例。 的继承体系如下图所示
runChild(final FrameworkMethod method, RunNotifier notifier) 方法的细节
继承了 (如上图所示),后者的 runChild(final FrameworkMethod method, RunNotifier notifier) 方法的代码如下 ⬇️
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
if (isIgnored(method)) {
notifier.fireTestIgnored(description);
} else {
Statement statement = new Statement() {
@Override
public void evaluate() throws Throwable {
methodBlock(method).evaluate();
}
};
runLeaf(statement, description, notifier);
}
}
其中的 if 分支用于处理带有 @Ignore 注解的方法(细节可以参考 浅解 JUnit 4 第十篇:方法上的 @Ignore 注解 一文)。现在我们看一下 else 分支做了什么。
else 分支做了两件事情 ⬇️
- 创建
Statement的实例 - 执行
runLeaf(Statement, Description, RunNotifier)方法
但这样的描述太粗略了,约等于没说。我们还是得仔细看看
Statement
我把 Statement 的代码复制到下方了 ⬇️
package org.junit.runners.model;
/**
* Represents one or more actions to be taken at runtime in the course
* of running a JUnit test suite.
*
* @since 4.5
*/
public abstract class Statement {
/**
* Run the action, throwing a {@code Throwable} if anything goes wrong.
*/
public abstract void evaluate() throws Throwable;
}
它的类图如下 ⬇️
Statement 看起来是对测试的封装。如果我们需要运行测试类 中的 ,就可以把 运行测试类 中的 这个逻辑封装为一个 Statement 对象。在 Statement 的 evaluate() 方法里,可以填写我们所需要的逻辑。
我们来看看这个 Statement 实例是如何生成的
- 这里会创建
Statement的一个匿名子类(这个匿名子类的名称是org.junit.runners.BlockJUnit4ClassRunner$1,但我们不必关心它的名称)的实例 - 这个匿名子类
override了evaluate()方法,在这个evaluate()方法里- 先调用
methodBlock(FrameworkMethod)方法得到一个Statement的实例 - 然后调用 的
evaluate()方法
- 先调用
这样说来,我们还需要看看 methodBlock(FrameworkMethod) 方法做了什么。我把这个方法的代码复制到下方了 ⬇️
protected Statement methodBlock(final FrameworkMethod method) {
Object test;
try {
test = new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
return createTest(method);
}
}.run();
} catch (Throwable e) {
return new Fail(e);
}
Statement statement = methodInvoker(method, test);
statement = possiblyExpectingExceptions(method, test, statement);
statement = withPotentialTimeout(method, test, statement);
statement = withBefores(method, test, statement);
statement = withAfters(method, test, statement);
statement = withRules(method, test, statement);
statement = withInterruptIsolation(statement);
return statement;
}
这个方法的 javadoc 比较长,我截了对应的图 ⬇️ 本文会探讨红框里的步骤
methodBlock(FrameworkMethod) 这个方法里做了很多事情,本文会探讨其中的四件事情。这四件事情我在下图中用红框标出来 ⬇️
为了便于讨论,我们看一个具体的例子。假设我们当前在处理 这个测试类中的 testAdd() 方法(这个方法带有 @Test 注解)。下方的思维导图中展示了 methodBlock(FrameworkMethod) 方法所做的四件事情 ⬇️
methodBlock(FrameworkMethod) 方法中做的第一件事:构造测试类的实例
相关的代码不复杂,读者朋友如果有兴趣的话,可以自己看看其中的细节
methodBlock(FrameworkMethod) 方法中做的第二件事:调用 methodInvoker(FrameworkMethod, Object) 方法创建 Statement 的实例
调用 methodInvoker(FrameworkMethod, Object) 方法,得到 的一个实例。而 继承了 ,前者的 evaluate() 方法中,会通过反射来调用测试类中的指定方法。我把这些内容画在下图中了 ⬇️
methodBlock(FrameworkMethod) 方法中做的第三件事:调用 withBefores(FrameworkMethod, Object, Statement) 方法得到 Statement 的实例
调用 withBefores(FrameworkMethod, Object, Statement) 方法时,可以找到带有 @Before 注解的方法(在本文的例子中,就是 prepare() 方法),于是会得到 的一个实例。而 继承了 ,前者的 evaluate() 方法中,会
- 先 通过反射调用测试类中带有
@Before注解的各个方法 - 后 调用 中的
evaluate()方法
我把这些内容画在下图中了 ⬇️
methodBlock(FrameworkMethod) 方法中做的第四件事:调用 withAfters(FrameworkMethod, Object, Statement) 方法得到 Statement 的实例
注意:第三件事和第四件事很相似
调用 withAfters(FrameworkMethod, Object, Statement) 方法时,可以找到带有 @After 注解的方法(在本文的例子中,就是 generateReport() 方法),于是会得到 的一个实例。而 继承了 ,前者的 evaluate() 方法中,会
- 先 调用 中的
evaluate()方法 - 后 通过反射调用测试类中带有
@After注解的各个方法
我把这些内容画在下图中了 ⬇️
说了这么多,但还没有进行验证呢。我们来打个断点验证一下,断点的位置如下图所示 👇
然后 debug SimpleCalculatorTest 类的 main 方法 ⬇️
当程序运行到断点这里时,可以看到前文所描述的 。 将 包装了一层,而 又将 包装了一层。这有点像拆月饼的包装,当我们拆开一层又一层的包装之后,最终还是可以看到月饼的 ( 就是最里面的月饼)。
这里多啰嗦几句,如果在 return 语句那里打断点,那么会看到 还会继续被包装为 (如下图所示)。不过这一层包装和 @Before/@After 注解的处理没有直接的关系,所以就一笔带过了。
到这里就把 methodBlock(FrameworkMethod) 方法里做的四件事情都讲清楚了。
至于执行 runLeaf(Statement, Description, RunNotifier) 方法,结合下图可以看出 runLeaf(Statement, Description, RunNotifier) 方法最核心的逻辑就是调用 Statement 对象的 evaluate() 方法 ⬇️
就本文的例子而言,我们打个断点就可以观察到当 runLeaf(Statement, Description, RunNotifier) 方法中执行 statement.evaluate() 这个语句时,会运行 Statement 的一个匿名子类中的 evaluate() 方法(这个匿名子类的名称是 org.junit.runners.BlockJUnit4ClassRunner$1,我们不必关心它的名称) ⬇️
而这个匿名子类的 evaluate() 方法的逻辑是
@Override
public void evaluate() throws Throwable {
methodBlock(method).evaluate();
}
按照前文的描述,对本文的例子而言,methodBlock(method) 方法返回的是 , 的 evaluate() 方法里会调用 的 evaluate() 方法。所以这里会间接调用 的 evaluate() 方法。我把这个过程画成了如下的思维导图 ⬇️
其他
画 "JUnit 4 如何处理 @Before/@After 注解" 一图所用到的代码
借助 PlantUML,可以用如下代码画出那张图
@startmindmap
title <i>JUnit 4</i> 如何处理 <i>@Before/@After</i> 注解
*:对普通的测试类 <i>T</i> 而言,
<i>JUnit 4</i> 为 <i>T</i> 生成的 <i>Runner: Runner<sub>T</sub></i>
会是 <i>org.junit.runners.JUnit4</i> 类的实例;
**:<i>org.junit.runners.JUnit4</i>
继承了
<i>BlockJUnit4ClassRunner</i>;
***:<i>BlockJUnit4ClassRunner</i>
继承了
<i>ParentRunner<FrameworkMethod></i>;
**:<i>ParentRunner</i>
会负责找到子节点并运行其中的测试;
***:<i>ParentRunner</i>
持有与 <i>T</i> 对应的 <i>TestClass: TestClass<sub>T</sub></i>;
****:借助 <i>TestClass<sub>T</sub></i> 可以查到
<i>T</i> 中的哪些方法带有 <i>@Test/@Before/@After</i> 注解;
**:<i>BlockJUnit4ClassRunner</i> 中的
<i>runChild(FrameworkMethod, RunNotifier)</i> 方法
会负责运行带有 <i>@Test</i> 注解的方法;
***:<i>runChild(FrameworkMethod, RunNotifier)</i> 方法
会间接调用 <i>methodBlock(FrameworkMethod)</i> 方法;
***:<i>methodBlock(FrameworkMethod)</i> 方法
会负责将 <b>运行某个测试方法</b> 的逻辑
包装成一层层的 <i>Statement</i> 对象;
***[#orange]:假设在测试类 <i>T</i> 中,
既可以找到带有 <i>@Before</i> 注解的方法,
也可以找到带有<i>@After</i> 注解的方法,
那么 <i>Statement</i> 对象的包装过程会是这样 <:point_right:>;
****[#orange] <&star> 称原始的 <i>Statement<i> 对象是 <i>statement<sub>1</sub></i>
*****_:<i>statement<sub>1</sub></i> 的精确类型是
<i>org.junit.internal.runners.statements.InvokeMethod</i>;
****[#orange]:<&star> 因为在测试类 <i>T</i> 中可以找到带有 <i>@Before</i> 注解的方法,
所以 <i>methodBlock(FrameworkMethod)</i> 方法
会把 <i>statement<sub>1</sub></i> 包装为 <i>statement<sub>2</sub></i>;
*****_:<i>statement<sub>2</sub></i> 的精确类型是
<i>org.junit.internal.runners.statements.RunBefores</i>;
****[#orange]:<&star> 因为在测试类 <i>T</i> 中可以找到带有 <i>@After</i> 注解的方法,
所以 <i>methodBlock(FrameworkMethod)</i> 方法
会把 <i>statement<sub>2</sub></i> 包装为 <i>statement<sub>3</sub></i>;
*****_:<i>statement<sub>3</sub></i> 的精确类型是
<i>org.junit.internal.runners.statements.RunAfters</i>;
***:<i>runLeaf(Statement, Description, RunNotifier)</i> 方法
会负责调用 <i>Statement</i> 对象上 <i>evaluate()</i> 方法;
legend left
橙色的节点展示了 <i>JUnit 4</i> 处理 <i>@Before/@After</i> 注解的核心步骤
end legend
@endmindmap
画 "org.junit.runners.model.Statement 和它的一些子类" 一图所用到的代码
借助 PlantUML,可以用如下代码画出那张图
@startuml
'https://plantuml.com/class-diagram
title <i>org.junit.runners.model.Statement</i> 和它的一些子类
abstract class org.junit.runners.model.Statement {
+{abstract} void evaluate() throws Throwable
}
class org.junit.internal.runners.statements.InvokeMethod
org.junit.runners.model.Statement <|-- org.junit.internal.runners.statements.InvokeMethod
class org.junit.internal.runners.statements.InvokeMethod {
- final FrameworkMethod testMethod
- final Object target
+ InvokeMethod(FrameworkMethod testMethod, Object target)
+ void evaluate() throws Throwable
}
class org.junit.internal.runners.statements.RunBefores
org.junit.runners.model.Statement <|-- org.junit.internal.runners.statements.RunBefores
class org.junit.internal.runners.statements.RunAfters
org.junit.runners.model.Statement <|-- org.junit.internal.runners.statements.RunAfters
class org.junit.internal.runners.statements.RunBefores {
- final Statement next
- final Object target
- final List<FrameworkMethod> befores
+ RunBefores(Statement next, List<FrameworkMethod> befores, Object target)
+ void evaluate() throws Throwable
# void invokeMethod(FrameworkMethod method) throws Throwable
}
class org.junit.internal.runners.statements.RunAfters {
- final Statement next
- final Object target
- final List<FrameworkMethod> afters
+ RunAfters(Statement next, List<FrameworkMethod> afters, Object target)
+ void evaluate() throws Throwable
# void invokeMethod(FrameworkMethod method) throws Throwable
}
note bottom of org.junit.internal.runners.statements.RunBefores
<i>RunBefores</i> 类用于处理 <i>@Before</i> 注解
end note
note bottom of org.junit.internal.runners.statements.RunAfters
<i>RunAfters</i> 类用于处理 <i>@After</i> 注解
end note
@enduml
画 "org.junit.runners.model.Statement" 一图所用到的代码
借助 PlantUML,可以用如下代码画出那张图
@startuml
'https://plantuml.com/class-diagram
title <i>org.junit.runners.model.Statement</i>
abstract class org.junit.runners.model.Statement {
+{abstract} void evaluate() throws Throwable
}
note left of org.junit.runners.model.Statement
<code>
Represents one or more actions
to be taken at runtime in the course
of running a JUnit test suite.
</code>
end note
note right of org.junit.runners.model.Statement::evaluate
<code>
Run the action,
throwing a {@code Throwable}
if anything goes wrong.
</code>
end note
@enduml
画“methodBlock(final FrameworkMethod method) 方法所做的四件事情”一图所用到的代码
借助 PlantUML,可以用如下代码画出那张图
@startmindmap
'https://plantuml.com/mindmap-diagram
title <i>methodBlock(final FrameworkMethod method)</i> 方法所做的四件事情
caption 其实 <i>methodBlock(final FrameworkMethod method)</i> 方法还做了其他事情\n这里只列举了本文关心的四件事情
* 四件事情
**[#orange]:1. 创建测试类 <b><i>SimpleCalculatorTest</i></b> 的实例,
并将这个实例保存到 <i>test</i> 变量里;
**[#orange]:2. 构造最内层的 <b><i>Statement</i></b> 实例
(我们将这个实例简称为 <i>statement<sub>1</sub></i>,
<i>statement<sub>1</sub></i> 的精确类型是
<b><i>org.junit.internal.runners.statements.InvokeMethod</i></b>);
*** <i>statement<sub>1</sub></i> 的 <i>evaluate()</i> 方法所做的事情是:
**** 通过反射来调用 <i>test</i> 的 <i>testAdd()</i> 方法
**[#orange]:3. 由于 <b><i>SimpleCalculatorTest</i></b>
中的 <i>prepare()</i> 方法带有 <i>@Before</i> 注解,
所以需要将 <i>statement<sub>1</sub></i> 封装成 <i>statement<sub>2</sub></i>
(<i>statement<sub>2</sub></i> 的精确类型是
<b><i>org.junit.internal.runners.statements.RunBefores</i></b>);
*** <i>statement<sub>2</sub></i> 的 <i>evaluate()</i> 方法所做的事情是:
****[#lightblue]:<b>先</b> 通过反射来调用 <b><i>SimpleCalculatorTest</i></b> 中
各个带有 <i>@Before</i> 注解的方法
(就本例而言, 只有 <i>prepare()</i> 一个方法);
**** <b>后</b> 调用 <i>statement<sub>1</sub></i> 的 <i>evaluate()</i> 方法
**[#orange]:4. 由于 <b><i>SimpleCalculatorTest</i></b>
中的 <i>generateReport()</i> 方法带有 <i>@After</i> 注解,
所以需要将 <i>statement<sub>2</sub></i> 封装成 <i>statement<sub>3</sub></i>
(<i>statement<sub>3</sub></i> 的精确类型是
<b><i>org.junit.internal.runners.statements.RunAfters</i></b>);
*** <i>statement<sub>3</sub></i> 的 <i>evaluate()</i> 方法所做的事情是:
**** <b>先</b> 调用 <i>statement<sub>2</sub></i> 的 <i>evaluate()</i> 方法
****[#lightgreen]:<b>后</b> 通过反射来调用 <b><i>SimpleCalculatorTest</i></b> 中
各个带有 <i>@After</i> 注解的方法
(就本例而言, 只有 <i>generateReport()</i> 一个方法);
header
掘金技术社区@金銀銅鐵
endheader
legend right
四个橙色节点展示了 <i>methodBlock(final FrameworkMethod method)</i> 方法所做的四件事情
浅蓝色节点展示了处理带有 <i>@Before</i> 注解的方法的时机
浅绿色节点展示了处理带有 <i>@After</i> 注解的方法的时机
end legend
@endmindmap
画“第二件事:调用 methodInvoker(FrameworkMethod, Object) 方法创建 Statement 的实例”一图所用到的代码
借助 PlantUML,可以用如下代码画出那张图
@startuml
'https://plantuml.com/class-diagram
title 第二件事:\n调用 <i>methodInvoker(FrameworkMethod, Object)</i> 方法\n创建 <i>Statement</i> 的实例
class org.junit.runners.BlockJUnit4ClassRunner {
#Statement methodInvoker(FrameworkMethod method, Object test)
}
abstract class org.junit.runners.model.Statement {
+{abstract} void evaluate() throws Throwable
}
class org.junit.internal.runners.statements.InvokeMethod
org.junit.runners.model.Statement <|-- org.junit.internal.runners.statements.InvokeMethod
class org.junit.internal.runners.statements.InvokeMethod {
- final FrameworkMethod testMethod
- final Object target
+ InvokeMethod(FrameworkMethod testMethod, Object target)
+ void evaluate() throws Throwable
}
note right of org.junit.runners.BlockJUnit4ClassRunner::methodInvoker
<code>
/**
* Returns a {@link Statement} that invokes {@code method} on {@code test}
*/
protected Statement methodInvoker(FrameworkMethod method, Object test) {
return new InvokeMethod(method, test);
}
</code>
end note
note right of org.junit.internal.runners.statements.InvokeMethod::InvokeMethod
<code>
public InvokeMethod(FrameworkMethod testMethod, Object target) {
this.testMethod = testMethod;
this.target = target;
}
</code>
end note
note right of org.junit.internal.runners.statements.InvokeMethod::evaluate
<code>
@Override
public void evaluate() throws Throwable {
testMethod.invokeExplosively(target);
}
</code>
end note
@enduml
画“第三件事:\n调用 withBefores(FrameworkMethod, Object, Statement) 方法”一图所用到的代码
借助 PlantUML,可以用如下代码画出那张图
@startuml
'https://plantuml.com/class-diagram
title 第三件事:\n调用 <i>withBefores(FrameworkMethod, Object, Statement)</i> 方法
class org.junit.runners.BlockJUnit4ClassRunner {
#Statement withBefores(FrameworkMethod method, Object target, Statement statement)
}
abstract class org.junit.runners.model.Statement {
+{abstract} void evaluate() throws Throwable
}
class org.junit.internal.runners.statements.RunBefores
org.junit.runners.model.Statement <|-- org.junit.internal.runners.statements.RunBefores
class org.junit.internal.runners.statements.RunBefores {
private final Statement next;
- final Object target
- final List<FrameworkMethod> befores
+ RunBefores(Statement next, List<FrameworkMethod> befores, Object target)
+ void evaluate() throws Throwable
# void invokeMethod(FrameworkMethod method) throws Throwable
}
note right of org.junit.runners.BlockJUnit4ClassRunner::withBefores
<code>
/**
* Returns a {@link Statement}: run all non-overridden {@code @Before}
* methods on this class and superclasses before running {@code next}; if
* any throws an Exception, stop execution and pass the exception on.
*/
protected Statement withBefores(FrameworkMethod method, Object target,
Statement statement) {
List<FrameworkMethod> befores = getTestClass().getAnnotatedMethods(
Before.class);
return befores.isEmpty() ? statement : new RunBefores(statement,
befores, target);
}
</code>
end note
note right of org.junit.internal.runners.statements.RunBefores::RunBefores
<code>
public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) {
this.next = next;
this.befores = befores;
this.target = target;
}
</code>
end note
note right of org.junit.internal.runners.statements.RunBefores::evaluate
<code>
@Override
public void evaluate() throws Throwable {
for (FrameworkMethod before : befores) {
invokeMethod(before);
}
next.evaluate();
}
</code>
end note
note right of org.junit.internal.runners.statements.RunBefores::invokeMethod
<code>
protected void invokeMethod(FrameworkMethod method) throws Throwable {
method.invokeExplosively(target);
}
</code>
end note
@enduml
画“第四件事:调用 withAfters(FrameworkMethod, Object, Statement) 方法”一图所用到的代码
借助 PlantUML,可以用如下代码画出那张图
@startuml
'https://plantuml.com/class-diagram
title 第四件事:\n调用 <i>withAfters(FrameworkMethod, Object, Statement)</i> 方法
class org.junit.runners.BlockJUnit4ClassRunner {
# Statement withAfters(FrameworkMethod method, Object target, Statement statement)
}
abstract class org.junit.runners.model.Statement {
+{abstract} void evaluate() throws Throwable
}
class org.junit.internal.runners.statements.RunAfters
org.junit.runners.model.Statement <|-- org.junit.internal.runners.statements.RunAfters
class org.junit.internal.runners.statements.RunAfters {
- final Statement next
- final Object target
- final List<FrameworkMethod> afters
+ RunAfters(Statement next, List<FrameworkMethod> afters, Object target)
+ void evaluate() throws Throwable
# void invokeMethod(FrameworkMethod method) throws Throwable
}
note right of org.junit.runners.BlockJUnit4ClassRunner::withAfters
<code>
/**
* Returns a {@link Statement}: run all non-overridden {@code @After}
* methods on this class and superclasses before running {@code next}; all
* After methods are always executed: exceptions thrown by previous steps
* are combined, if necessary, with exceptions from After methods into a
* {@link MultipleFailureException}.
*/
protected Statement withAfters(FrameworkMethod method, Object target,
Statement statement) {
List<FrameworkMethod> afters = getTestClass().getAnnotatedMethods(
After.class);
return afters.isEmpty() ? statement : new RunAfters(statement, afters,
target);
}
</code>
end note
note right of org.junit.internal.runners.statements.RunAfters::RunAfters
<code>
public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) {
this.next = next;
this.afters = afters;
this.target = target;
}
</code>
end note
note right of org.junit.internal.runners.statements.RunAfters::evaluate
<code>
@Override
public void evaluate() throws Throwable {
List<Throwable> errors = new ArrayList<Throwable>();
try {
next.evaluate();
} catch (Throwable e) {
errors.add(e);
} finally {
for (FrameworkMethod each : afters) {
try {
invokeMethod(each);
} catch (Throwable e) {
errors.add(e);
}
}
}
MultipleFailureException.assertEmpty(errors);
}
</code>
end note
note right of org.junit.internal.runners.statements.RunAfters::invokeMethod
<code>
protected void invokeMethod(FrameworkMethod method) throws Throwable {
method.invokeExplosively(target);
}
</code>
end note
@enduml
画“就本文的例子而言 当 runLeaf(Statement, Description, RunNotifier) 方法里执行 statement.evaluate() 语句时, 发生了什么?”一图所用到的代码
借助 PlantUML,可以用如下代码画出那张图
@startmindmap
'https://plantuml.com/mindmap-diagram
top to bottom direction
title 就本文的例子而言\n当 <i>runLeaf(Statement, Description, RunNotifier)</i> 方法里执行\n<i>statement.evaluate()</i> 语句时, 发生了什么?
*:它会调用 <b><i>Statement</i></b> 的一个匿名子类的 <i>evaluate()</i> 方法
(这个匿名子类的名称是 <b><i>org.junit.runners.BlockJUnit4ClassRunner$1</i></b>)
这个 <i>evaluate()</i> 方法里代码是 <b><i>methodBlock(method).evaluate();</i></b>
<i>methodBlock(method)</i> 返回的是 <i>statement<sub>4</sub></i>
而 <i>statement<sub>4</sub></i> 的 <i>evaluate()</i> 方法会调用 <i>statement<sub>3</sub></i> 的 <i>evaluate()</i> 方法
所以这里会间接调用 <i>statement<sub>3</sub></i> 的 <i>evaluate()</i> 方法;
**_:<i>statement<sub>3</sub></i> 的 <i>evaluate()</i> 方法的执行逻辑如下 <:point_down:>
(就本例而言, <i>statement<sub>3</sub></i> 的精确类型是 <b><i>org.junit.internal.runners.statements.RunAfters</i></b>);
***:<b>先</b> 调用 <i>statement<sub>2</sub></i> 的 <i>evaluate()</i> 方法
(<i>statement<sub>2</sub></i> 的精确类型是
<b><i>org.junit.internal.runners.statements.RunBefores</i></b>);
****_ <i>statement<sub>2</sub></i> 的 <i>evaluate()</i> 方法的执行逻辑如下 <:point_down:>
*****[#lightblue]:<b>先</b> 通过反射来调用 <b><i>SimpleCalculatorTest</i></b> 中
各个带有 <i>@Before</i> 注解的方法
(就本例而言, 只有 <i>prepare()</i> 一个方法);
*****:<b>后</b> 调用 <i>statement<sub>1</sub></i> 的 <i>evaluate()</i> 方法
(<i>statement<sub>1</sub></i> 的精确类型是
<b><i>org.junit.internal.runners.statements.InvokeMethod</i></b>);
******_ <i>statement<sub>1</sub></i> 的 <i>evaluate()</i> 方法的执行逻辑如下 <:point_down:>
******* 通过反射来调用 <b><i>SimpleCalculatorTest</i></b> 中的 <i>testAdd()</i> 方法
***[#lightgreen]:<b>后</b> 通过反射来调用 <b><i>SimpleCalculatorTest</i></b> 中
各个带有 <i>@After</i> 注解的方法
(就本例而言, 只有 <i>generateReport()</i> 一个方法);
legend left
浅蓝色节点展示了带有 <i>@Before</i> 注解的方法的运行时机
浅绿色节点展示了带有 <i>@After</i> 注解的方法的运行时机
end legend
@endmindmap