← 中文资源

简体中文版本

用种子让随机化测试可复现

让所有随机性走同一个带种子的生成器,失败时打印种子,一个参数即可重放失败的精确序列。

阅读英文原文

目标

随机输入让测试更强壮,却也让失败失去意义——除非随机性被播种、且种子在失败后依然可查。一个生成器、一个环境变量、一行控制台输出,就能把“CI 里出现过一次的诡异失败”变成任何人在本地都能精确重放的脚本,让不可复现不再是关闭缺陷单的理由。

步骤要点

  1. 先用 rg 审计套件里所有直接调用 Math.random、crypto 随机的地方,这些是可复现性的泄漏点。
  2. 实现一个以环境变量 TEST_SEED 为种子的 xorshift 生成器,启动时把种子打印到 stderr,所有随机辅助函数都从它派生。
  3. 把隐式随机源一并纳入种子体系:随机 UUID、当前时间、测试执行顺序都改成种子的函数,重放才能精确而非近似。
  4. 故意构造只在特定种子下失败的断言,验证用日志里的种子重跑能逐字复现同样的失败。
  5. 让 CI 失败时自动提取日志中的种子并带种子重跑,把可重放的痕迹固化进构建产物。

排错提示

播了种子仍不可复现,说明还剩异步时序、网络或库内随机,继续搜直连调用并在 setup 里全局打桩 Date 与 Math.random;库自带种子机制就转发给它(fast-check 的 seed、jest 的 randomSeed),别维护两套系统,必要时同时打印两个种子;并行 worker 各拿不同种子时,从主种子加 worker 索引确定性派生并记录,单条失败能在对应 worker 语境重放。

适用边界

种子控制输入不控制墙上时钟,要完全确定性需配合假定时器;重放保真到代码读取真熵(加密随机令牌、OS 随机)为止,这些必须在测试 setup 里打桩。

代码示例

# The failure log now carries its own replay key
npx vitest run tests/checkout.test.ts
# [rand] seed=1729664701234  (replay with TEST_SEED=1729664701234)
# × applies coupon to random cart  (expected 850, got 925)

# Replay exactly
TEST_SEED=1729664701234 npx vitest run tests/checkout.test.ts
# × applies coupon to random cart  (expected 850, got 925)  <-- identical