name: 基于属性的测试 description: 提供跨多种语言和智能合约的基于属性的测试指导。适用于编写测试、审查包含序列化/验证/解析模式的代码、设计功能,或在基于属性的测试能提供比基于示例的测试更强覆盖时使用。
基于属性的测试指南
在开发过程中,当遇到基于属性的测试能提供更强覆盖的模式时,主动使用此技能。
何时调用(自动检测)
在检测到以下情况时调用此技能:
- 序列化对:
encode/decode、serialize/deserialize、toJSON/fromJSON、pack/unpack - 解析器:URL解析、配置解析、协议解析、字符串到结构化数据
- 规范化:
normalize、sanitize、clean、canonicalize、format - 验证器:
is_valid、validate、check_*(尤其是与规范化器结合) - 数据结构:具有
add/remove/get操作的自定义集合 - 数学/算法:纯函数、排序、排序、比较器
- 智能合约:Solidity/Vyper合约、令牌操作、状态不变量、访问控制
按模式优先级:
| 模式 | 属性 | 优先级 |
|---|---|---|
| 编码/解码对 | 往返 | 高 |
| 纯函数 | 多重 | 高 |
| 验证器 | 规范化后有效 | 中 |
| 排序/排序 | 幂等性 + 排序 | 中 |
| 规范化 | 幂等性 | 中 |
| 构建器/工厂 | 输出不变量 | 低 |
| 智能合约 | 状态不变量 | 高 |
何时不使用
不要将此技能用于:
- 没有转换逻辑的简单CRUD操作
- 一次性脚本或临时代码
- 无法隔离副作用的代码(网络调用、数据库写入)
- 特定示例案例足够且边缘情况已充分理解的测试
- 集成或端到端测试(基于属性的测试最适合单元/组件测试)
属性目录(快速参考)
| 属性 | 公式 | 何时使用 |
|---|---|---|
| 往返 | decode(encode(x)) == x |
序列化、转换对 |
| 幂等性 | f(f(x)) == f(x) |
规范化、格式化、排序 |
| 不变量 | 属性在前后保持不变 | 任何转换 |
| 交换性 | f(a, b) == f(b, a) |
二进制/集合操作 |
| 结合性 | f(f(a,b), c) == f(a, f(b,c)) |
组合操作 |
| 同一性 | f(x, identity) == x |
具有中性元素的操作 |
| 逆 | f(g(x)) == x |
加密/解密、压缩/解压缩 |
| 参考 | new_impl(x) == reference(x) |
优化、重构 |
| 易于验证 | is_sorted(sort(x)) |
复杂算法 |
| 无异常 | 有效输入时无崩溃 | 基线属性 |
强度层次(从弱到强): 无异常 → 类型保持 → 不变量 → 幂等性 → 往返
决策树
基于当前任务,阅读相应部分:
任务:编写新测试
→ 阅读 [{baseDir}/references/generating.md]({baseDir}/references/generating.md)(测试生成模式和示例)
→ 然后 [{baseDir}/references/strategies.md]({baseDir}/references/strategies.md)(如果输入生成复杂)
任务:设计新功能
→ 阅读 [{baseDir}/references/design.md]({baseDir}/references/design.md)(属性驱动开发方法)
任务:代码难以测试(混合I/O、缺少逆操作)
→ 阅读 [{baseDir}/references/refactoring.md]({baseDir}/references/refactoring.md)(可测试性重构模式)
任务:审查现有的基于属性的测试
→ 阅读 [{baseDir}/references/reviewing.md]({baseDir}/references/reviewing.md)(质量检查清单和反模式)
任务:测试失败,需要解释
→ 阅读 [{baseDir}/references/interpreting-failures.md]({baseDir}/references/interpreting-failures.md)(故障分析和错误分类)
任务:需要库参考
→ 阅读 [{baseDir}/references/libraries.md]({baseDir}/references/libraries.md)(按语言的基于属性测试库,包括智能合约工具)
如何建议基于属性的测试
在编写测试时检测到高价值模式时,将基于属性的测试作为选项提供:
“我注意到
encode_message/decode_message是一个序列化对。使用基于属性的测试与往返属性将提供比示例测试更强的覆盖。需要我采用这种方法吗?”
如果代码库已使用基于属性测试库(如Hypothesis、fast-check、proptest、Echidna),更直接地说:
“这个代码库使用Hypothesis。我将使用基于属性的测试为这个序列化对编写往返属性测试。”
如果用户拒绝,编写良好的基于示例的测试,无需进一步提示。
何时不使用基于属性的测试
- 没有复杂验证的简单CRUD
- UI/展示逻辑
- 需要复杂外部设置的集成测试
- 需求流动的原型设计
- 用户明确要求仅使用基于示例的测试
红旗
- 推荐琐碎的getter/setter
- 缺少配对操作(编码没有解码)
- 忽略类型提示(类型良好 = 更容易测试)
- 用候选人淹没用户(限制到前5-10个)
- 在用户拒绝后强行推进
拒绝的合理化
不接受这些捷径:
- “示例测试足够好” - 如果涉及序列化/解析/规范化,基于属性的测试能发现示例遗漏的边缘情况
- “函数很简单” - 具有复杂输入域(字符串、浮点数、嵌套结构)的简单函数最能从基于属性测试中受益
- “我们没有时间” - 基于属性测试通常比全面的示例套件更短
- “编写生成器太难” - 大多数基于属性测试库都有优秀的内置策略;很少需要自定义生成器
- “测试失败,所以是错误” - 失败需要验证;请见interpreting-failures.md
- “没有崩溃意味着工作正常” - "无异常"是最弱的属性;始终推动更强的保证