浅解 JUnit 4 第十一篇:@Before 注解和 @After 注解如何发挥作用?

6 阅读8分钟

背景

在以下两篇文章中,我们已经探讨了类上的 @Ignore 注解以及方法上的 @Ignore 注解是如何发挥作用的。

那么 @Before 注解和 @After 注解又是如何发挥作用的呢?本文会对这个问题进行探讨。本文的主角是以下几位

  • org.junit.Before\text{org.junit.Before} 注解
  • org.junit.After\text{org.junit.After} 注解
  • org.junit.runners.model.Statement\text{org.junit.runners.model.Statement} 抽象类

要点

  • 对一个测试类 T\text{T} 而言,JUnit 4 会生成与 T\text{T} 对应的 TestClass:TestClassT\text{TestClass}:\text{TestClass}_\text{T}
    • 借助 TestClassT\text{TestClass}_\text{T} 就可以知道 T\text{T} 上有哪些方法带有 @Before/@After 注解
  • 对普通的测试类 T\text{T} 而言,JUnit 4 所生成的与 T\text{T} 对应的 Runner:RunnerT\text{Runner}:\text{Runner}_\text{T} 会是 org.junit.runners.JUnit4\text{org.junit.runners.JUnit4} 类的实例
    • org.junit.runners.JUnit4\text{org.junit.runners.JUnit4} 继承了 BlockJUnit4ClassRunner
    • BlockJUnit4ClassRunner 继承了 ParentRunner<FrameworkMethod>
    • ParentRunner 会负责找到子节点并运行其中的测试
    • ParentRunner<T> 中的 runChild(T child, RunNotifier notifier) 方法负责运行这个子节点中的测试
    • BlockJUnit4ClassRunner 中的 runChild(final FrameworkMethod, RunNotifier) 方法会负责运行带有 @Test 注解的方法
    • BlockJUnit4ClassRunner 中的 methodBlock(FrameworkMethod) 方法负责将“运行某个测试方法”的逻辑包装成一层层的 Statement 对象
      • 例如原始的 Statement 对象是 statement1statement_1
      • 如果测试类 T\text{T} 中包含带有 @Before 注解的方法,那么 methodBlock(FrameworkMethod) 方法会把 statement1statement_1 包装为 statement2statement_2 (它的精确类型是 org.junit.internal.runners.statements.RunBefores\text{org.junit.internal.runners.statements.RunBefores})
      • 如果测试类 T\text{T} 中包含带有 @After 注解的方法,那么 methodBlock(FrameworkMethod) 方法会把 statement2statement_2 包装为 statement3statement_3 (它的精确类型是 org.junit.internal.runners.statements.RunAfters\text{org.junit.internal.runners.statements.RunAfters})
    • BlockJUnit4ClassRunner 中的 methodBlock(FrameworkMethod) 方法会负责调用 Statement 对象上 evaluate() 方法

Statement 以及它的一些子类的类图如下 ⬇️

brief.png

正文

一个具体的例子

我在本地创建了一个小项目来以便探讨本文的问题,这个项目的结构如下 ⬇️

├── 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 一文中提到,对一个测试类 T\text{T} 而言,JUnit 4 会生成对应的 TestClass:TestClassT\text{TestClass}: \text{TestClass}_\text{T}。借助 TestClassT\text{TestClass}_\text{T},我们就可以知道 T\text{T} 中的哪些方法带有 @Before/@After 注解。

何时执行带有 @Before/@After 注解的方法

对普通的测试类 T\text{T} 而言,它对应的 Runner:RunnerT\text{Runner}: \text{Runner}_\text{T} 会是 org.junit.runners.JUnit4\text{org.junit.runners.JUnit4} 的实例。org.junit.runners.JUnit4\text{org.junit.runners.JUnit4} 的继承体系如下图所示

image.png

runChild(final FrameworkMethod method, RunNotifier notifier) 方法的细节

org.junit.runners.JUnit4\text{org.junit.runners.JUnit4} 继承了 org.junit.runners.BlockJUnit4ClassRunner\text{org.junit.runners.BlockJUnit4ClassRunner} (如上图所示),后者的 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 分支做了两件事情 ⬇️

  1. 创建 Statement 的实例
  2. 执行 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;
}

它的类图如下 ⬇️ image.png

Statement 看起来是对测试的封装。如果我们需要运行测试类 T\text{T} 中的 method1method_1,就可以把 运行测试类 T\text{T} 中的 method1method_1 这个逻辑封装为一个 Statement 对象。在 Statementevaluate() 方法里,可以填写我们所需要的逻辑。

我们来看看这个 Statement 实例是如何生成的

image.png

  • 这里会创建 Statement 的一个匿名子类(这个匿名子类的名称是 org.junit.runners.BlockJUnit4ClassRunner$1,我们不必关心它的名称)
  • 这个匿名子类 overrideevaluate() 方法,在这个 evaluate() 方法里
    • 先调用 methodBlock(FrameworkMethod) 方法得到一个 Statement 的实例 SS
    • 然后调用 SSevaluate() 方法

这样说来,我们还需要看看 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 比较长,我截了对应的图 ⬇️ 本文会探讨红框里的步骤

image.png

methodBlock(FrameworkMethod) 这个方法里做了很多事情,本文会探讨其中的四件事情。这四件事情我在下图中用红框标出来 ⬇️

image.png

为了便于讨论,我们看一个具体的例子。假设我们当前在处理 SimpleCalculatorTest \text{SimpleCalculatorTest } 这个测试类中的 testAdd() 方法(这个方法带有 @Test 注解)。下方的思维导图中展示了 methodBlock(FrameworkMethod) 方法所做的四件事情 ⬇️

image.png

methodBlock(FrameworkMethod) 方法中做的第一件事:构造测试类的实例

相关的代码不复杂,读者朋友如果有兴趣的话,可以自己看看其中的细节 image.png

methodBlock(FrameworkMethod) 方法中做的第二件事:调用 methodInvoker(FrameworkMethod, Object) 方法创建 Statement 的实例 statement1statement_1

调用 methodInvoker(FrameworkMethod, Object) 方法,会得到 org.junit.internal.runners.statements.InvokeMethod\text{org.junit.internal.runners.statements.InvokeMethod} 的一个实例。而 org.junit.internal.runners.statements.InvokeMethod\text{org.junit.internal.runners.statements.InvokeMethod} 继承了 org.junit.runners.model.Statement\text{org.junit.runners.model.Statement},前者的 evaluate() 方法中,会调用反射来调用测试类中的指定方法。我把这些内容画在下图中了 ⬇️

image.png

methodBlock(FrameworkMethod) 方法中做的第三件事:调用 withBefores(FrameworkMethod, Object, Statement) 方法得到 Statement 的实例 statement2statement_2

调用 withBefores(FrameworkMethod, Object, Statement) 方法时,可以找到带有 @Before 注解的方法(在本文的例子中,就是 prepare() 方法),于是会得到 org.junit.internal.runners.statements.RunBefores\text{org.junit.internal.runners.statements.RunBefores} 的一个实例。而 org.junit.internal.runners.statements.RunBefores\text{org.junit.internal.runners.statements.RunBefores} 继承了 org.junit.runners.model.Statement\text{org.junit.runners.model.Statement},前者的 evaluate() 方法中,会

  • 通过反射调用测试类中带有 @Before 注解的各个方法
  • 调用 statement1statement_1 中的 evaluate() 方法

我把这些内容画在下图中了 ⬇️ 2.png

methodBlock(FrameworkMethod) 方法中做的第四件事:调用 withAfters(FrameworkMethod, Object, Statement) 方法得到 Statement 的实例 statement3statement_3

注意:第三件事和第四件事很相似

调用 withAfters(FrameworkMethod, Object, Statement) 方法时,可以找到带有 @After 注解的方法(在本文的例子中,就是 generateReport() 方法),于是会得到 org.junit.internal.runners.statements.RunAfters\text{org.junit.internal.runners.statements.RunAfters} 的一个实例。而 org.junit.internal.runners.statements.RunAfters\text{org.junit.internal.runners.statements.RunAfters} 继承了 org.junit.runners.model.Statement\text{org.junit.runners.model.Statement},前者的 evaluate() 方法中,会

  • 调用 statement2statement_2 中的 evaluate() 方法
  • 通过反射调用测试类中带有 @After 注解的各个方法

我把这些内容画在下图中了 ⬇️

4.png

其他

画 "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>@Before</i> 注解
}

note bottom of org.junit.internal.runners.statements.RunAfters {
用于处理 <i>@After</i> 注解
}

@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