
本文介绍在使用 hypothesis 进行属性测试时,如何系统性验证函数对合法输入(如有限浮点数、±inf)的正确行为,同时确保非法输入(如 nan)能可靠触发预期异常(如 assertionerror),且不牺牲主测试用例的覆盖率。
本文介绍在使用 hypothesis 进行属性测试时,如何系统性验证函数对合法输入(如有限浮点数、±inf)的正确行为,同时确保非法输入(如 nan)能可靠触发预期异常(如 assertionerror),且不牺牲主测试用例的覆盖率。
在防御式编程中,函数通常通过断言(assert)主动拒绝非法输入(如 NaN),而非静默处理或依赖下游校验。Hypothesis 的优势在于能自动生成大量边缘值,但默认策略倾向于生成“典型有效”数据——这意味着 st.floats() 生成 NaN 的概率极低,导致 assert not math.isnan(value) 这类断言几乎不会被触发,从而掩盖了异常路径未被充分验证的风险。
要兼顾功能正确性与异常鲁棒性,推荐采用“分层测试”策略:
✅ 1. 主测试:专注合法输入行为验证(保持高覆盖率)
保留原有 @given(st.floats()) 测试,但移除 assume(not math.isnan(value)) —— 因为 assume() 会丢弃不满足条件的数据,反而降低发现 NaN 触发断言失败的机会。改为显式分类处理,并利用 hypothesis.event() 记录数据分布:
from hypothesis import given, event, settings
import hypothesis.strategies as st
import math
@given(st.floats())
def test_inc_behavior(value: float):
if math.isnan(value):
event("input: NaN")
# 此处不 assert,因为 NaN 应该触发 AssertionError
elif math.isinf(value):
event("input: infinity")
assert inc(value) == value
else:
event("input: finite")
assert inc(value) > value
运行后可通过 hypothesis statistics 查看各事件触发频次:
pytest test_module.py --hypothesis-show-statistics
若长期未见 "input: NaN",说明生成不足——此时需补充专项测试。
✅ 2. 专项测试:强制覆盖非法输入(保障异常路径)
为确保 NaN 等关键非法值必被测试,应单独编写高确定性测试用例,避免依赖随机采样:
from hypothesis import example, given, settings
# 显式指定 NaN,100% 覆盖
@example(float('nan'))
@given(st.just(float('nan')))
def test_inc_rejects_nan(value: float):
with pytest.raises(AssertionError, match="not math.isnan"):
inc(value)
# 同理可扩展至其他非法值(如非数字字符串,若类型允许)
@example("invalid")
def test_inc_type_safety(value):
with pytest.raises(TypeError):
inc(value)
⚠️ 注意事项:
- 避免在主测试中用
assume(math.isnan(value))单独验证 NaN,因其可能因采样失败而跳过,导致 CI 中“假绿”;- 对极低概率桶(如
sNaN,subnormal floats),建议结合derandomize=True和max_examples=1000提升稳定性;- 若需自动化检测覆盖缺口,可在 CI 中解析
hypothesis/observability/*.jsonl,校验"input: NaN"是否出现 ≥1 次。
✅ 总结
Hypothesis 本身不提供“自动强制覆盖所有输入类别”的内置模式,但通过 event() + 统计分析(观测)与 @example/st.just() + 专项断言(保障)双轨并行,即可在不干扰主测试有效性前提下,严谨验证防御式断言的健壮性。这是工程实践中平衡自动化与可靠性最务实的方案。











