如何利用Hypothesis测试函数对NaN等越界值的断言行为,同时保障合法值的测试覆盖
如何利用Hypothesis测试函数对NaN等越界值的断言行为,同时保障合法值的测试覆盖
刚好我在项目里也经常碰到类似的需求,既要验证函数对合法输入的正常处理,又要确保非法输入能触发预期的断言,还不能让这两部分测试互相拖后腿。Hypothesis其实有很顺手的方案来解决这个问题,核心思路就是拆分测试用例,针对性生成测试样本,这样两边的覆盖都能保障。
先给你拆解下具体的做法:
第一步:单独测试合法输入,优化样本生成效率
原来的测试里用assume(not math.isnan(value))来过滤NaN,其实有点浪费Hypothesis的采样资源——毕竟生成的NaN样本都会被直接跳过,反而挤占了合法样本的测试机会。
我们可以直接用Hypothesis的策略参数,精准生成符合要求的合法输入,这样不用过滤,测试效率和覆盖度都会更高:
import math from hypothesis import given, strategies as st def inc(value: float) -> float: assert not math.isnan(value) return value + 1 # 专门测试合法输入的正常行为:允许无穷大,排除NaN @given(st.floats(allow_nan=False, allow_infinity=True)) def test_inc_valid_inputs(value: float): if value in (math.inf, -math.inf): assert inc(value) == value else: assert inc(value) > value
这样Hypothesis会把所有采样资源都用在合法的浮点数上,不管是普通有限值还是正负无穷,都能被充分测试,完全不用担心覆盖打折扣。
第二步:单独测试非法输入,验证断言触发逻辑
对于NaN这种越界值,我们可以专门写一个测试用例,针对性生成这类非法样本,然后验证它们确实会触发AssertionError:
import pytest from hypothesis import given, strategies as st # 专门测试NaN输入触发断言 @given(st.floats(allow_nan=True, allow_infinity=False)) def test_inc_invalid_nan_raises_assertion(value: float): # 这里生成的样本都是NaN,直接验证调用函数时会抛出断言错误 with pytest.raises(AssertionError): inc(value)
这个测试的目标非常明确,Hypothesis会集中生成NaN样本,确保我们的断言逻辑确实生效,不会出现“漏测”或者“误测”的情况。
为什么这种拆分方式更优?
- 职责清晰:每个测试只干一件事,后期维护起来也方便,一眼就能看懂每个测试的目标
- 覆盖充分:合法输入的测试不用再浪费采样机会在NaN上,Hypothesis可以把更多精力放在合法值的边缘场景(比如接近浮点数上下限的数值),保障覆盖质量
- 精准验证:非法输入的测试专门针对NaN生成样本,不会出现“漏测”或者“误测”的情况
如果你实在想把两种逻辑放在同一个测试里(虽然我不太推荐,会让测试逻辑变复杂),也可以用st.one_of把合法和非法策略组合起来,然后在测试里分支处理,但这样Hypothesis会把采样资源分配给两种样本,反而可能降低合法值的测试密度,所以还是拆分的方案更稳妥。
备注:内容来源于stack exchange,提问作者Carl Patenaude Poulin
相关产品推荐
相关产品推荐

