研发协作
当测试需要覆盖整个输入域而非手选样例时…
在代码具有代数形状(逆运算、不变量、oracle)时,用生成器搜索反例来替代逐例断言。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
当测试需要覆盖整个输入域而非手选样例时,通用
解决思路
- 先判断代码是否具备代数形状(逆运算、不变量、oracle);若没有,则改用示例测试,并明确说明这是合理结论。
- 在断定无可断言之前,先检查形状是缺失还是被埋没:被 I/O 包裹的计算、字符串拼接、原地修改都可能有属性但缺少断言接缝,参考 references/refactoring.md。
- 按属性目录选择属性:Roundtrip(decode(encode(x)) == x,用于序列化、转换对)、Inverse(f(g(x)) == x,用于加密/解密、压缩/解压)、Oracle(new(x) == reference(x),用于优化、重构、重实现)、Idempotence(f(f(x)) == f(x),用于归一化、格式化、排序)、Invariant(变换前后保持不变,用于任意变换、合约状态)、Easy to verify(is_sorted(sort(x)),用于复杂算法配廉价检查器)、Commutativity(f(a, b) == f(b, a),用于二元与集合运算)、Associativity(f(f(a,b), c) == f(a, f(b,c)),用于组合运算)、Identity(f(x, e) == x,用于带单位元的运算)。
- 按强度从弱到强排序断言:no crash → type preservation → invariant → idempotence → roundtrip / oracle;断言代码支持的最强属性。
- 若只能找到 no crash,先尝试小幅重构暴露更强属性;若仍不行,如实报告该代码不适合 PBT。
- 避免重言式(tautology):assert add(a, b) == a + b 只是复述实现,共享的 bug 无法使其失败;应选择约束函数而不重算它的属性。例外:当 f 明显非纯时(如对 dict/set 的序列化、哈希、读取时钟),f(x) == f(x) 是真正的确定性属性。
- 避免空泛(vacuity):过滤掉几乎所有输入的 assume() 会不执行任何内容就通过,自相矛盾的 assume() 会零用例通过;应把约束推进到 strategy 中,让生成器直接产生合法输入。
- 按任务加载对应参考文件:编写新测试与设计 strategy 用 references/generating.md;代码尚无属性可断言用 references/refactoring.md;审查已有属性测试用 references/reviewing.md;属性测试刚失败用 references/interpreting-failures.md;库选择、Echidna 与 Medusa 用 references/libraries.md。
- 若项目已使用 PBT 库,直接在其中编写测试;若没有,添加依赖属于用户的决策——带着你打算写的具体属性提议一次,并接受任何答复。
- 在向已有 @given、fast-check 或 proptest 套件添加用例、判断现有属性测试是否断言了真实内容、以及生成器已缩小出反例需要区分错误属性与真实 bug 时,也使用本技能。
适用边界 · 注意事项
- 不适用于覆盖率引导的二进制模糊测试(libFuzzer、AFL)。
- 不适用于变异测试活动、静态分析、基准测试或端到端 UI 测试。
- 当代码不具备代数形状且无法通过重构暴露属性时,应改用示例测试。