目标
默认生成器产出的是领域本来就会拒绝的值,属性测试大部分时间在重测校验逻辑。自定义 Arbitrary 反转比例:每个生成值都是合法业务对象,任何失败都是真正的逻辑违规。组合器写一次很便宜,却让后续所有属性都更锋利。
步骤要点
- 把原始类型约束到业务边界:数量 1-999、金额以分为单位的整数区间。
- 用 record+map 构造保序日期范围:start 加 1-30 天得 end,构造即满足 start<end。
- 组合实体图:订单由 1-20 行数量组成,totalCents 由行推导,内部恒一致。
- 在域 Arbitrary 上声明属性:checkout 保持总额与行数,这才是业务不变量。
- 失败时固定种子复跑,让 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) }));