Hypothesis 属性测试入门指南(Python)
Hypothesis 是 Python 的属性测试(Property-based Testing)库,能自动生成海量测试用例并自动缩小反例。本文讲解 @given、strategies、与 pytest 集成及有状态测试的用法。
本文依据官方文档整理,未执行运行验证或性能基准。代码片段展示局部用法,业务函数、数据和环境需按项目补齐;版本与配置以所引文档为准。
直接回答:Hypothesis 是 Python 生态中最知名的属性测试库:你不再手写一个个具体用例,而是描述"输入应该满足什么约束、输出应该满足什么性质",由库自动生成成百上千个用例去验证,一旦发现反例还会自动缩小到较简单的形式。
从例子测试到属性测试
传统单元测试是"举例式"的:给 add(1, 2) 断言等于 3。问题在于你只能验证想到的那几个例子。属性测试换了个思路——断言性质:
- 对任意两个数,
add(a, b) == add(b, a)(交换律) - 对任意列表,
sorted(sorted(xs)) == sorted(xs)(幂等性) - 对任意合法输入,解析再序列化应当等价(往返一致性)
快速上手
pip install hypothesis
from hypothesis import given, strategies as st
@given(st.lists(st.integers()))
def test_sort_idempotent(xs):
assert sorted(sorted(xs)) == sorted(xs)
运行时 Hypothesis 会自动生成空列表、单元素、极大极小整数、重复元素等各种"刁钻"输入。如果断言失败,它会不断简化输入,尝试给出较小反例;受测试预算和策略影响,不保证数学意义的全局最小。
strategies:描述输入空间
常用策略:
| 策略 | 含义 |
|---|---|
st.integers(min_value=0, max_value=100) |
限定范围的整数 |
st.text(alphabet="abc", max_size=10) |
限定字符集与长度的字符串 |
st.datetimes(timezones=st.timezones()) |
带时区的日期时间;默认 st.datetimes() 生成 naive datetime |
st.builds(MyClass, field=st.integers()) |
构造自定义对象 |
st.one_of(a, b) / s.filter(...) / s.map(...) |
组合、过滤、变换策略 |
策略定义得越贴近真实数据分布,测试价值越高。
与 pytest 集成
Hypothesis 天然兼容 pytest,@given 装饰的函数可以像普通测试一样被收集;配合 @settings(max_examples=500, deadline=None) 可以控制用例预算和 deadline 检查(不是强制中断死循环的硬超时)。函数级 fixture 不会为每个生成样本重新初始化;数据库资源须在每个样本内创建/回滚,避免状态污染:
from hypothesis import settings
@settings(max_examples=200)
@given(email=st.emails())
def test_create_user(email):
# isolated_test_db 是项目实现的上下文管理器,退出时回滚/清理。
with isolated_test_db() as db:
assert create_user(db, email).email == email
有状态测试(Rule-based)
对于"一连串操作"的系统(缓存、队列、状态机),Hypothesis 提供 RuleBasedStateMachine:定义若干操作规则和不变量,库会自动生成随机操作序列,尝试把系统搞坏——这是普通参数化测试很难覆盖的维度。
常见问题(FAQ)
Q:属性测试会不会很慢?
A:比单条用例慢是肯定的,但 max_examples 可调,默认 max_examples 通常为100,是否足够取决于输入空间;CI 可增加预算。它不是穷举,也不保证覆盖率固定倍数提升。
Q:找不到"性质"可断言怎么办?
A:最常见的万能性质是"不抛异常"和"往返一致"(parse∘serialize = identity)。从这两个入手,慢慢就能发现更多业务不变量。
Q:Hypothesis 发现的反例能固定下来吗?
A:可以。库自带 example 数据库会记住失败用例,修复后建议顺手补一条普通的参数化测试做回归。
官方参考
资料核对日期:2026-09-29。