bigywater.tech
Java · 325

JUnit 动态测试

@TestFactory 能在运行时生成测试。动态测试的生命周期和普通测试一样吗?失败怎么展示?流是一次生成还是惰性的?工厂方法写错了会怎样?这篇逐项运行。

1 次
4 个动态测试里 @BeforeEach / @AfterEach 的执行次数(整个工厂方法只一次)
4 found / 3 + 1
4 个动态测试里 3 个成功、1 个失败,失败不影响其它
0 个
返回空流或工厂方法抛异常时找到的测试数

动态测试与生命周期

实测 · 本机执行输出
  basic():4 个动态测试,第 3 个故意失败
        [BeforeEach] 实例 #1
        [工厂方法 basic 执行]
          执行动态测试 平方 1
          执行动态测试 平方 2
          执行动态测试 平方 3
          执行动态测试 平方 4
        [AfterEach] 实例 #1
    │  └─ DT ✔
    │     └─ basic() ✔
    │        ├─ 平方 1 ✔
    │        ├─ 平方 2 ✔
    │        ├─ 平方 3 ✘ expected: <9> but was: <10>
    │        └─ 平方 4 ✔
    Failures (1):
        MethodSource [className = 'DT', methodName = 'basic', methodParameterTypes = '']
        => org.opentest4j.AssertionFailedError: expected: <9> but was: <10>
    汇总:4 tests found ] | 3 tests successful ] | 1 tests failed ]
  regular() 作对照:普通 @Test
        [BeforeEach] 实例 #1
          普通 @Test regular
        [AfterEach] 实例 #1
    │  └─ DT ✔
    │     └─ regular() ✔
    汇总:1 tests found ] | 1 tests successful ] | 0 tests failed ]

DynamicContainer

实测 · 本机执行输出
  containers():容器 A、容器 B(含嵌套容器 B-inner)
        [BeforeEach] 实例 #1
        [AfterEach] 实例 #1
    │  └─ DT ✔
    │     └─ containers() ✔
    │        ├─ 容器 A ✔
    │        │  ├─ A1 ✔
    │        │  └─ A2 ✔
    │        └─ 容器 B ✔
    │           ├─ B1 ✘ B1 出错
    │           └─ 嵌套容器 B-inner ✔
    │              └─ B-inner-1 ✔
    Failures (1):
        MethodSource [className = 'DT', methodName = 'containers', methodParameterTypes = '']
        => java.lang.IllegalStateException: B1 出错
    汇总:4 tests found ] | 3 tests successful ] | 1 tests failed ]

惰性流与返回类型

实测 · 本机执行输出
  lazy():Stream.iterate().limit(3)
        [BeforeEach] 实例 #1
          [流产生第 1 个元素]
          执行 惰性 1
          [流产生第 2 个元素]
          执行 惰性 2
          [流产生第 3 个元素]
          执行 惰性 3
        [AfterEach] 实例 #1
    │  └─ DT ✔
    │     └─ lazy() ✔
    │        ├─ 惰性 1 ✔
    │        ├─ 惰性 2 ✔
    │        └─ 惰性 3 ✔
    汇总:3 tests found ] | 3 tests successful ] | 0 tests failed ]
  collection()
        [BeforeEach] 实例 #1
        [AfterEach] 实例 #1
    │  └─ DT ✔
    │     └─ collection() ✔
    │        ├─ c1 ✔
    │        └─ c2 ✔
    汇总:2 tests found ] | 2 tests successful ] | 0 tests failed ]
  iterator()
        [BeforeEach] 实例 #1
        [AfterEach] 实例 #1
    │  └─ DT ✔
    │     └─ iterator() ✔
    │        └─ it1 ✔
    汇总:1 tests found ] | 1 tests successful ] | 0 tests failed ]
  closing()
        [BeforeEach] 实例 #1
          [流被关闭 onClose]
        [AfterEach] 实例 #1
    │  └─ DT ✔
    │     └─ closing() ✔
    │        └─ x ✔
    汇总:1 tests found ] | 1 tests successful ] | 0 tests failed ]

工厂方法的错误

实测 · 本机执行输出
  badReturn():返回 Stream<String>
        [BeforeEach] 实例 #1
        [AfterEach] 实例 #1
    │  └─ DT ✔
    │     └─ badReturn() ✘ @TestFactory method [java.util.stream.Stream<java.lang.String> DT.badReturn()] must return a single org.junit.jupiter.api.DynamicNode or a Stream, Collection, Iterable, Iterator
    Failures (1):
        MethodSource [className = 'DT', methodName = 'badReturn', methodParameterTypes = '']
        => org.junit.platform.commons.JUnitException: @TestFactory method [java.util.stream.Stream<java.lang.String> DT.badReturn()] must return a single org.junit.jupiter.api.DynamicNode or a Stream, Col
         Caused by: java.lang.ClassCastException: class java.lang.String cannot be cast to class org.junit.jupiter.api.DynamicNode (java.lang.String is in module java.base of loader 'bootstrap'; org.junit
    汇总:0 tests found ] | 0 tests successful ] | 0 tests failed ]
  factoryThrows()
        [BeforeEach] 实例 #1
        [AfterEach] 实例 #1
    │  └─ DT ✔
    │     └─ factoryThrows() ✘ 工厂方法自己抛异常
    Failures (1):
        MethodSource [className = 'DT', methodName = 'factoryThrows', methodParameterTypes = '']
        => java.lang.IllegalStateException: 工厂方法自己抛异常
    汇总:0 tests found ] | 0 tests successful ] | 0 tests failed ]
  emptyFactory()
        [BeforeEach] 实例 #1
        [AfterEach] 实例 #1
    │  └─ DT ✔
    │     └─ emptyFactory() ✔
    汇总:0 tests found ] | 0 tests successful ] | 0 tests failed ]

共享状态与实例

实测 · 本机执行输出
  perTestState():三个动态测试共享一个计数器与同一个测试实例
        [BeforeEach] 实例 #1
          a:共享的计数器 = 1,this.id = 1
          b:共享的计数器 = 2,this.id = 1
          c:共享的计数器 = 3,this.id = 1
        [AfterEach] 实例 #1
    │  └─ DT ✔
    │     └─ perTestState() ✔
    │        ├─ a ✔
    │        ├─ b ✔
    │        └─ c ✔
    汇总:3 tests found ] | 3 tests successful ] | 0 tests failed ]

分步演示

JUnit 动态测试
本步的对象

    本文用到的工具与边界

    • 本机 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
    • 没有验证:动态测试的并行执行、与 @TestInstance(PER_CLASS) 的组合、从文件 / 数据库读取的动态测试数据源、uniqueId 与 IDE 重新运行单个动态测试的支持、ExecutionMode 对 DynamicContainer 的影响
    京ICP备2026067502号-1京公网安备11010502063487号