bigywater.tech
Java · 381

测试夹具:构造可读且稳定的测试数据

测试数据是测试里最容易写、也最容易让人读不懂和让测试变脆的部分。这篇把四种常见写法都跑了:依赖现在、依赖随机、依赖默认值、依赖大而全的数据,量出各自什么时候会坏、坏多少。

300 / 731
夹具里写 now().minusDays(30) 的测试在 731 个模拟日期里失败的天数
4.1%
随机夹具的 bug 在一次运行里被发现的概率(记录种子后 10/10 可重放)
4–5 → 0–1
改一个默认夹具值时,断言总价的测试与只断言目标项的测试各坏几个

依赖现在

实测 · 本机执行输出
  共 731 天
  夹具 now().minusDays(30),断言“相差不到 1 个月”:失败 300 天;集中在 {MARCH=60, MAY=60, JULY=60, OCTOBER=60, DECEMBER=60};例如 [2024-03-01, 2024-03-02, 2024-03-03]
  夹具 now().minusDays(1),断言“相差 1 个工作日”:失败 208 天;按星期 {SATURDAY=104, SUNDAY=104};例如 [2024-01-06, 2024-01-07, 2024-01-13]
  同样两个测试改用固定日期 2025-04-16(星期三):失败 0 + 0 天

依赖随机

实测 · 本机执行输出
  种子 1..1000 各跑一次:失败 41 次(4.1%);前 5 个失败种子 [53, 57, 68, 72, 140]
  记录下失败种子 53 后重放 10 次:失败 10 次(每次数量都是 97)
  每个测试用全新随机种子,连续跑 1 次发现 bug 的概率 3.8%;连续跑 3 次发现 bug 的概率 10.8%;连续跑 10 次发现 bug 的概率 34.2%;连续跑 50 次发现 bug 的概率 87.8%;连续跑 100 次发现 bug 的概率 98.1%;

依赖默认值

实测 · 本机执行输出
  基线:A 失败 [],B 失败 []
  国内运费 1000 → 1200:A 失败 4 个 [A1, A2, A3, A6];B 失败 0 个 []
  默认国家 CN → DE:A 失败 4 个 [A1, A2, A3, A6];B 失败 0 个 []
  默认商品额 10000 → 12000:A 失败 5 个 [A1, A2, A3, A5, A6];B 失败 0 个 []
  默认等级 STANDARD → SILVER:A 失败 5 个 [A1, A3, A4, A5, A6];B 失败 1 个 [B3]

夹具成本

实测 · 本机执行输出
  5000 个测试各构造一次(7 轮取中位数):重夹具每次 8.9 微秒(共 44 ms),轻夹具每次 0.01 微秒(共 0.0 ms),约 973 倍
  按 2 万个测试估算:重夹具 178 ms,轻夹具 0 ms(只是对象构造,不含数据库或文件)

分步演示

测试夹具
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;所有测试都是自写的最小函数(不使用 JUnit,以便在 731 个日期、1000 个种子下反复运行);“今天”是传入参数,没有改系统时钟
    • 没有验证:Instancio / Easy Random / Fixture Monkey 等数据生成库、JUnit @MethodSource 与 @TestFactory 的数据驱动写法、数据库夹具(SQL 脚本、DbUnit)、测试容器里的数据加载;夹具构造成本只测了对象构造,没含 I/O
    京ICP备2026067502号-1京公网安备11010502063487号