← 中文资源

简体中文版本

为业务规则编写自定义 Arbitrary

组合 fast-check 自定义 Arbitrary 生成满足金额、日期范围与实体图不变量的数据,让属性测试越过原始类型。

阅读英文原文

目标

默认生成器产出的是领域本来就会拒绝的值,属性测试大部分时间在重测校验逻辑。自定义 Arbitrary 反转比例:每个生成值都是合法业务对象,任何失败都是真正的逻辑违规。组合器写一次很便宜,却让后续所有属性都更锋利。

步骤要点

  1. 把原始类型约束到业务边界:数量 1-999、金额以分为单位的整数区间。
  2. 用 record+map 构造保序日期范围:start 加 1-30 天得 end,构造即满足 start<end。
  3. 组合实体图:订单由 1-20 行数量组成,totalCents 由行推导,内部恒一致。
  4. 在域 Arbitrary 上声明属性:checkout 保持总额与行数,这才是业务不变量。
  5. 失败时固定种子复跑,让 fast-check 收缩到最小反例再定位。

排错提示

属性全绿却仍有 bug 说明生成器太窄,应另写属性专测边界与非法输入的拒绝路径;复杂 record 收缩卡顿就换 fc.record 加数组长度上限,约束生成收缩更快;随机日期踩到时区敏感代码时统一生成 UTC 瞬间并在测试内显式转换,绝不让本地时区泄漏进数据。

适用边界

自定义 Arbitrary 编码的是你对领域的理解,理解错了不变量就会把 bug 生成掉;属性测试补充而非替代验收用例的示例测试。

代码示例

const dateRange = fc.record({
  start: fc.date({ min: new Date("2026-01-01"), max: new Date("2026-12-31") }),
  days: fc.integer({ min: 1, max: 30 }),
}).map(({ start, days }) => ({ start, end: new Date(start.getTime() + days * 86400_000) }));