关于 property-based-testing
一项指导如何应用基于属性的测试的技能,适用于具有不变量、往返关系或参考结果等可验证规则的代码。它帮助开发者设计有意义的属性、避免空洞测试,并判断何时不适用这种方法。
主要功能
- 01属性模式目录
- 02测试输入生成指南
- 03识别恒真与空洞测试
- 04通过重构显露可验证属性
- 05失败分析与库选择参考
适用场景
- 01为序列化或转换设计属性
- 02审查现有属性测试套件
- 03调查生成测试失败
- 04判断代码是否适合属性测试
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ trailofbits/ skills --skill property-based-testing安装后在 Claude Code 中输入/property-based-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdagents/openai.yamlassets/trail-of-bits-mark.svgreferences/generating.mdinterpreting-failures.mdlibraries.mdrefactoring.mdreviewing.md详细介绍
基于属性的测试
示例测试断言一个点。属性则断言整个输入域上的规则,并让生成器寻找反例。当代码具有代数结构——逆运算、不变量、预言机——时,这种权衡值得;否则就不值得。没有这类结构的代码应使用示例测试;如实说明这一点也是有效结论。
先检查这种结构是真的缺失,还是仅仅被掩盖了。被 I/O 包装的计算、通过拼接构造的字符串、原地修改——这些都有可用的属性,只是没有断言它们的切入点。在认定没有可断言的内容之前,请参阅 references/refactoring.md。
属性目录
| 属性 | 公式 | 适用场景 |
|---|---|---|
| 往返 | decode(encode(x)) == x | 序列化、成对转换 |
| 逆运算 | f(g(x)) == x | 加密/解密、压缩/解压 |
| 预言机 | new(x) == reference(x) | 优化、重构、重新实现 |
| 幂等性 | f(f(x)) == f(x) | 规范化、格式化、排序 |
| 不变量 | 转换前后均成立 | 任意转换、契约状态 |
| 易于验证 | is_sorted(sort(x)) | 检查器开销较低的复杂算法 |
| 交换律 | f(a, b) == f(b, a) | 二元运算和集合运算 |
| 结合律 | f(f(a,b), c) == f(a, f(b,c)) | 组合运算 |
| 单位元 | f(x, e) == x | 存在中性元素的运算 |
强度排序,从弱到强:
不崩溃 → 保持类型 → 不变量 → 幂等性 → 往返 / 预言机。
断言代码所支持的最强属性。仅仅“不崩溃”很少足以证明引入依赖是合理的——如果你只能找到这一点,那么要么稍作调整就能暴露更强的属性,要么诚实地说明这段代码不适合基于属性的测试。在选择后者之前,先排除前者。
属性测试不会断言任何内容的两种情况
- 同义反复。
assert add(a, b) == a + b只是重述实现;它们共同存在的任何 bug 都无法让测试失败。应选择能约束函数但不重新计算函数结果的属性。注意一个例外:当f并非显然纯函数时,f(x) == f(x)是真正的确定性属性——例如处理字典或集合的序列化器、哈希,以及任何读取时钟的操作。 - 空洞测试。 过滤掉几乎所有输入的
assume()会在没有实际测试的情况下通过;自相矛盾的assume()也会在运行零个用例后通过。应将约束放入策略中,让生成器直接生成有效输入。
后续阅读
加载与当前任务相符的文档:
| 任务 | 文件 |
|---|---|
| 编写新测试、设计策略 | references/generating.md |
| 代码目前没有可断言的属性 | references/refactoring.md |
| 审查现有属性测试 | references/reviewing.md |
| 属性测试刚刚失败 | references/interpreting-failures.md |
| 库的选择、Echidna 和 Medusa | references/libraries.md |
在尚未使用基于属性的测试的项目中引入 PBT
如果项目已经使用 PBT 库,直接用它编写测试即可。如果项目尚未使用,引入库属于依赖决策,应由用户决定——提出一次你会编写的具体属性测试,并无论用户如何回答都接受。
评论
支持 Markdown