bigywater.tech
Java · 323

JUnit 5 扩展模型

JUnit 5 的扩展模型有十几种回调接口。它们在一个测试方法的生命周期里到底按什么顺序被调用?多个扩展叠在一起时谁先谁后?抛了异常的测试谁能拦住?这篇把每一次回调都打印出来。

12 种
本文实现并观察的扩展接口的种数(条件、BeforeAll / AfterAll / BeforeEach / AfterEach / BeforeTestExecution / AfterTestExecution、实例后处理、参数解析、拦截、异常处理、TestWatcher)
4 → 2
@AfterAll 时已创建的 CloseableResource 数(4)与已关闭数(2)
Ext2 → Ext1
after 类回调与异常处理器的调用顺序(与注册顺序相反)

12 种扩展接口的回调顺序

实测 · 本机执行输出
  Ext1.evaluateExecutionCondition(OrderTest)
  Ext2.evaluateExecutionCondition(OrderTest)
  Ext1.beforeAll
  Ext2.beforeAll
  @BeforeAll
  OrderTest 构造器
  Ext1.postProcessTestInstance
  Ext2.postProcessTestInstance
  Ext1.evaluateExecutionCondition(swallowed())
  Ext2.evaluateExecutionCondition(swallowed())
  Ext1.beforeEach(swallowed)
  Ext2.beforeEach(swallowed)
  @BeforeEach
  Ext1.beforeTestExecution
  Ext2.beforeTestExecution
  Ext1.interceptTestMethod 之前
  Ext2.interceptTestMethod 之前
  测试方法体 swallowed:抛 IllegalStateException
  Ext2.handleTestExecutionException(IllegalStateException)
  Ext2.afterTestExecution
  Ext1.afterTestExecution
  @AfterEach
  Ext2.afterEach
  Ext1.afterEach
  Ext2.TestWatcher.testSuccessful
  Ext1.TestWatcher.testSuccessful
  OrderTest 构造器
  Ext1.postProcessTestInstance
  Ext2.postProcessTestInstance
  Ext1.evaluateExecutionCondition(passes(Greeting))
  Ext2.evaluateExecutionCondition(passes(Greeting))
  Ext1.beforeEach(passes)
  Ext2.beforeEach(passes)
  @BeforeEach
  Ext1.beforeTestExecution
  Ext2.beforeTestExecution
  Ext1.resolveParameter(Greeting)
  Ext1.interceptTestMethod 之前
  Ext2.interceptTestMethod 之前
  测试方法体 passes(收到 hi from Ext1)
  Ext2.interceptTestMethod 之后
  Ext1.interceptTestMethod 之后
  Ext2.afterTestExecution
  Ext1.afterTestExecution
  @AfterEach
  Ext2.afterEach
  Ext1.afterEach
  Ext2.TestWatcher.testSuccessful
  Ext1.TestWatcher.testSuccessful
  OrderTest 构造器
  Ext1.postProcessTestInstance
  Ext2.postProcessTestInstance
  Ext1.evaluateExecutionCondition(skipMe())
  Ext2.evaluateExecutionCondition(skipMe())
  Ext1.TestWatcher.testDisabled(名字以 skip 开头)
  Ext2.TestWatcher.testDisabled(名字以 skip 开头)
  OrderTest 构造器
  Ext1.postProcessTestInstance
  Ext2.postProcessTestInstance
  Ext1.evaluateExecutionCondition(fails())
  Ext2.evaluateExecutionCondition(fails())
  Ext1.beforeEach(fails)
  Ext2.beforeEach(fails)
  @BeforeEach
  Ext1.beforeTestExecution
  Ext2.beforeTestExecution
  Ext1.interceptTestMethod 之前
  Ext2.interceptTestMethod 之前
  测试方法体 fails:抛 AssertionError
  Ext2.handleTestExecutionException(AssertionError)
  Ext1.handleTestExecutionException(AssertionError)
  Ext2.afterTestExecution
  Ext1.afterTestExecution
  @AfterEach
  Ext2.afterEach
  Ext1.afterEach
  Ext2.TestWatcher.testFailed(AssertionError)
  Ext1.TestWatcher.testFailed(AssertionError)
  @AfterAll
  Ext2.afterAll
  Ext1.afterAll
  MethodSource [className = 'OrderTest', methodName = 'fails', methodParameterTypes = '']
  => java.lang.AssertionError: 真的失败
     OrderTest.fails(OrderTest.java:9)
     java.base/java.lang.reflect.Method.invoke(Method.java:580)
     Ext1.interceptTestMethod(Ext1.java:9)
     Ext1.interceptTestMethod(Ext1.java:9)
     java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
     java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
  4 containers found ] | 0 containers skipped ] | 4 containers started ] | 0 containers aborted ] | 4 containers successful ] | 0 containers failed ] | 4 tests found ] | 1 tests skipped ] | 0 tests aborted ] | 2 tests successful ] | 1 tests failed ]

Store 与资源关闭

实测 · 本机执行输出
  创建资源 root(根存储)
  创建资源 class(类存储)
  创建资源 method(a)
  beforeEach(a):子存储能读到父存储的 root 吗?true
  test a
  关闭资源 method(a)
  创建资源 method(b)
  beforeEach(b):子存储能读到父存储的 root 吗?true
  test b
  关闭资源 method(b)
  @AfterAll 时:创建 4 个,已关闭 2 个(method 级的资源在每个测试结束时已关闭,class 与 root 级的还没有)
  关闭资源 class(类存储)
  关闭资源 root(根存储)

@RegisterExtension

实测 · 本机执行输出
  静态字段 @RegisterExtension.evaluateExecutionCondition(RegTest)
  静态字段 @RegisterExtension.beforeAll
  静态字段 @RegisterExtension.postProcessTestInstance
  Ext2.postProcessTestInstance
  静态字段 @RegisterExtension.evaluateExecutionCondition(t(Greeting))
  实例字段 @RegisterExtension.evaluateExecutionCondition(t(Greeting))
  Ext2.evaluateExecutionCondition(t(Greeting))
  静态字段 @RegisterExtension.beforeEach(t)
  实例字段 @RegisterExtension.beforeEach(t)
  Ext2.beforeEach(t)
  静态字段 @RegisterExtension.beforeTestExecution
  实例字段 @RegisterExtension.beforeTestExecution
  Ext2.beforeTestExecution
  静态字段 @RegisterExtension.resolveParameter(Greeting)
  静态字段 @RegisterExtension.interceptTestMethod 之前
  实例字段 @RegisterExtension.interceptTestMethod 之前
  Ext2.interceptTestMethod 之前
  测试方法体(收到 hi from 静态字段 @RegisterExtension)
  Ext2.interceptTestMethod 之后
  实例字段 @RegisterExtension.interceptTestMethod 之后
  静态字段 @RegisterExtension.interceptTestMethod 之后
  Ext2.afterTestExecution
  实例字段 @RegisterExtension.afterTestExecution
  静态字段 @RegisterExtension.afterTestExecution
  Ext2.afterEach
  实例字段 @RegisterExtension.afterEach
  静态字段 @RegisterExtension.afterEach
  Ext2.TestWatcher.testSuccessful
  实例字段 @RegisterExtension.TestWatcher.testSuccessful
  静态字段 @RegisterExtension.TestWatcher.testSuccessful
  静态字段 @RegisterExtension.afterAll

分步演示

JUnit 5 扩展模型
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;junit-platform-console-standalone 1.11.3(JUnit Jupiter 5.11.3);--config junit.jupiter.execution.parallel.enabled=false
    • 没有验证:并行执行下的扩展回调顺序、@TestInstance(PER_CLASS) 下的差别、通过 ServiceLoader 自动注册的扩展(autodetection)、TestTemplate / 参数化测试里的 BeforeEach 次数(见参数化文章)、Jupiter 5.12+ 的新扩展点、嵌套类(@Nested)里的扩展继承
    京ICP备2026067502号-1京公网安备11010502063487号