property-based-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Property-Based Testing

基于属性的测试(Property-Based Testing,PBT)

Overview

概述

Property-based testing (PBT) generates random inputs and verifies that properties hold for all of them. Instead of testing specific examples, you test invariants.
When PBT beats example-based tests:
  • Serialization pairs (encode/decode)
  • Pure functions with clear contracts
  • Validators and normalizers
  • Data structure operations
基于属性的测试(PBT)会生成随机输入,并验证所有输入是否都满足特定属性。与测试特定示例不同,你测试的是不变量。
PBT优于基于示例的测试的场景:
  • 序列化对(编码/解码)
  • 具有明确契约的纯函数
  • 验证器和规范化器
  • 数据结构操作

Property Catalog

属性目录

PropertyFormulaWhen to Use
Roundtrip
decode(encode(x)) == x
Serialization, conversion pairs
Idempotence
f(f(x)) == f(x)
Normalization, formatting, sorting
InvariantProperty holds before/afterAny transformation
Commutativity
f(a, b) == f(b, a)
Binary/set operations
Associativity
f(f(a,b), c) == f(a, f(b,c))
Combining operations
Identity
f(x, identity) == x
Operations with neutral element
Inverse
f(g(x)) == x
encrypt/decrypt, compress/decompress
Oracle
new_impl(x) == reference(x)
Optimization, refactoring
Easy to Verify
is_sorted(sort(x))
Complex algorithms
No ExceptionNo crash on valid inputBaseline (weakest)
Strength hierarchy (weakest to strongest):
No Exception -> Type Preservation -> Invariant -> Idempotence -> Roundtrip
Always aim for the strongest property that applies.
属性公式适用场景
往返一致性(Roundtrip)
decode(encode(x)) == x
序列化、转换对
幂等性(Idempotence)
f(f(x)) == f(x)
规范化、格式化、排序
不变性(Invariant)属性在转换前后保持成立任何转换操作
交换性(Commutativity)
f(a, b) == f(b, a)
二元/集合操作
结合性(Associativity)
f(f(a,b), c) == f(a, f(b,c))
组合操作
恒等性(Identity)
f(x, identity) == x
包含中性元素的操作
逆运算(Inverse)
f(g(x)) == x
加密/解密、压缩/解压缩
参照验证(Oracle)
new_impl(x) == reference(x)
优化、重构
易验证性(Easy to Verify)
is_sorted(sort(x))
复杂算法
无异常(No Exception)合法输入下无崩溃基线测试(最弱要求)
强度层级(从弱到强):
No Exception -> Type Preservation -> Invariant -> Idempotence -> Roundtrip
始终优先选择适用的最强属性。

Pattern Detection

模式检测

Use PBT when you see:
PatternPropertyPriority
encode
/
decode
,
serialize
/
deserialize
RoundtripHIGH
toJSON
/
fromJSON
,
pack
/
unpack
RoundtripHIGH
Pure functions with clear contractsMultipleHIGH
normalize
,
sanitize
,
canonicalize
IdempotenceMEDIUM
is_valid
,
validate
with normalizers
Valid after normalizeMEDIUM
Sorting, ordering, comparatorsIdempotence + orderingMEDIUM
Custom collections (add/remove/get)InvariantsMEDIUM
Builder/factory patternsOutput invariantsLOW
当你遇到以下模式时使用PBT:
模式属性优先级
encode
/
decode
serialize
/
deserialize
往返一致性
toJSON
/
fromJSON
pack
/
unpack
往返一致性
具有明确契约的纯函数多个属性
normalize
sanitize
canonicalize
幂等性
带规范化器的
is_valid
validate
规范化后验证通过
排序、排序规则、比较器幂等性 + 排序性
自定义集合(添加/删除/获取)不变性
构建器/工厂模式输出不变性

When NOT to Use

不适用场景

  • Simple CRUD without transformation logic
  • UI/presentation logic
  • Integration tests requiring complex external setup
  • Code with side effects that cannot be isolated
  • Prototyping where requirements are fluid
  • Tests where specific examples suffice and edge cases are understood
  • 无转换逻辑的简单CRUD操作
  • UI/展示层逻辑
  • 需要复杂外部环境配置的集成测试
  • 无法隔离副作用的代码
  • 需求频繁变动的原型开发
  • 特定示例已足够且边缘情况已明确的测试

Library Quick Reference

库快速参考

LanguageLibraryImport
PythonHypothesis
from hypothesis import given, strategies as st
TypeScript/JSfast-check
import fc from 'fast-check'
Rustproptest
use proptest::prelude::*
Gorapid
import "pgregory.net/rapid"
Javajqwik
@Property
annotations
HaskellQuickCheck
import Test.QuickCheck
For library-specific syntax and patterns: Use
@ed3d-research-agents:internet-researcher
to get current documentation.
语言导入语句
PythonHypothesis
from hypothesis import given, strategies as st
TypeScript/JSfast-check
import fc from 'fast-check'
Rustproptest
use proptest::prelude::*
Gorapid
import "pgregory.net/rapid"
Javajqwik
@Property
注解
HaskellQuickCheck
import Test.QuickCheck
如需库的特定语法和模式: 使用
@ed3d-research-agents:internet-researcher
获取最新文档。

Input Strategy Best Practices

输入策略最佳实践

  1. Constrain early: Build constraints INTO the strategy, not via
    assume()
    python
    # GOOD
    st.integers(min_value=1, max_value=100)
    
    # BAD - high rejection rate
    st.integers().filter(lambda x: 1 <= x <= 100)
  2. Size limits: Prevent slow tests
    python
    st.lists(st.integers(), max_size=100)
    st.text(max_size=1000)
  3. Realistic data: Match real-world constraints
    python
    st.integers(min_value=0, max_value=150)  # Real ages, not arbitrary ints
  4. Reuse strategies: Define once, use across tests
    python
    valid_users = st.builds(User, ...)
    
    @given(valid_users)
    def test_one(user): ...
    
    @given(valid_users)
    def test_two(user): ...
  1. 尽早约束: 将约束直接内置到策略中,而非使用
    assume()
    python
    # GOOD
    st.integers(min_value=1, max_value=100)
    
    # BAD - 高拒绝率
    st.integers().filter(lambda x: 1 <= x <= 100)
  2. 大小限制: 避免测试过慢
    python
    st.lists(st.integers(), max_size=100)
    st.text(max_size=1000)
  3. 真实数据: 匹配真实世界的约束
    python
    st.integers(min_value=0, max_value=150)  # 真实年龄范围,而非任意整数
  4. 复用策略: 定义一次,跨测试使用
    python
    valid_users = st.builds(User, ...)
    
    @given(valid_users)
    def test_one(user): ...
    
    @given(valid_users)
    def test_two(user): ...

Settings Guide

配置指南

python
undefined
python
undefined

Development (fast feedback)

开发环境(快速反馈)

@settings(max_examples=10)
@settings(max_examples=10)

CI (thorough)

CI环境(全面测试)

@settings(max_examples=200)
@settings(max_examples=200)

Nightly/Release (exhaustive)

夜间/发布版本( exhaustive测试)

@settings(max_examples=1000, deadline=None)
undefined
@settings(max_examples=1000, deadline=None)
undefined

Quality Checklist

质量检查清单

Before committing PBT tests:
  • Not tautological (assertion doesn't compare same expression)
  • Strong assertion (not just "no crash")
  • Not vacuous (inputs not over-filtered by
    assume()
    )
  • Edge cases covered with explicit examples (
    @example
    )
  • No reimplementation of function logic in assertion
  • Strategy constraints are realistic
  • Settings appropriate for context
提交PBT测试前需检查:
  • 非同义反复(断言不是比较相同表达式)
  • 强断言(不只是“无崩溃”)
  • 非空泛(输入未被
    assume()
    过度过滤)
  • 用显式示例覆盖边缘情况(
    @example
  • 断言中未重实现函数逻辑
  • 策略约束符合实际
  • 配置符合场景需求

Red Flags

危险信号

  • Tautological:
    assert sorted(xs) == sorted(xs)
    tests nothing
  • Only "no crash": Always look for stronger properties
  • Vacuous: Multiple
    assume()
    calls filter out most inputs
  • Reimplementation:
    assert add(a, b) == a + b
    if that's how add is implemented
  • Missing edge cases: No
    @example([])
    ,
    @example([1])
    decorators
  • Overly constrained: Many
    assume()
    calls means redesign the strategy
  • 同义反复:
    assert sorted(xs) == sorted(xs)
    未测试任何内容
  • 仅测试“无崩溃”: 始终寻找更强的属性
  • 空泛测试: 多个
    assume()
    调用过滤掉大部分输入
  • 重实现逻辑: 如果
    add(a,b)
    的实现就是
    a+b
    ,那么
    assert add(a, b) == a + b
    毫无意义
  • 缺少边缘情况: 未添加
    @example([])
    @example([1])
    等装饰器
  • 过度约束: 多个
    assume()
    调用意味着需要重新设计策略

Common Mistakes

常见错误

MistakeFix
Testing mock behaviorTest real behavior
Reimplementing function in testUse algebraic properties
Filtering with assume()Build constraints into strategy
No edge case examplesAdd @example decorators
One property onlyAdd multiple properties (length, ordering, etc.)
错误修复方案
测试Mock行为测试真实行为
在测试中重实现函数逻辑使用代数属性
assume()
过滤输入
将约束内置到策略中
无边缘情况示例添加
@example
装饰器
仅测试单个属性添加多个属性(长度、排序性等)