如何阻止python-hypothesis重试产生异常Interesting结果丢失原始报错
你配置错参数了:report_multiple_bugs=False 仅用于控制用例收缩阶段遇到多个不同类型bug时是否批量上报,完全不会关闭Hypothesis内置的「失败用例重跑验证」逻辑——只要Hypothesis检测到一次失败,默认至少会重跑1次对应用例,确认失败可稳定复现,之后才会进入用例收缩阶段找最小复现输入。
你现在把数据库初始化、清理逻辑全写在测试函数体内,只要某一次执行时add_column或之前的步骤抛异常,后面的await end_database(database)根本不会执行,残留的库表、连接、测试数据会直接污染后续所有重跑、收缩阶段的执行环境,才会出现第一次报PostgresSyntaxError、重跑时报InternalServerError的Flaky错误。
按优先级从高到低选择:
1. 用pytest fixture做强制资源清理(根治方案)
把数据库初始化、清理逻辑挪到函数级fixture里,pytest会保证fixture的清理逻辑无论测试成功、抛错、中途中断都一定会执行,从根源避免状态污染。示例写法:
import pytest @pytest.fixture async def test_database(): # 初始化逻辑 db = await get_database() yield db # 清理逻辑:任何场景下都会执行 await end_database(db) # 保险起见可以在这里加硬删除逻辑,直接删掉本次测试创建的所有project/source相关库表
测试代码改成直接注入fixture拿数据库实例,不要自己在测试体内写初始化/清理:
@given(st.text(min_size=1, max_size=128)) @pytest.mark.asyncio async def test_hypothesis_add_column(test_database, name): assume('\x00' not in name) project = await test_database.create_project('test_project') source = await project.create_source( 'test_source', [ lib.ColumnDefinition( name='external_id', type=lib.ColumnTypes.NUMERIC, is_key=True ) ] ) await source.add_column(lib.ColumnDefinition( name=name, type=lib.ColumnTypes.TEXT )) assert len(source.columns) == 2 assert await source.column(name) is not None assert (await source.column(name)).internal_name.isidentifier()
注意fixture的作用域必须是默认的function级,这样Hypothesis每一次执行测试(包括生成用例、重跑验证、收缩最小用例的所有尝试)都会拿到全新的干净环境,不会出现状态污染。
2. 临时开详细日志拿首次报错
如果你暂时不想改fixture逻辑,只想先拿到第一次触发PostgresSyntaxError的输入和栈,直接给Hypothesis开verbose级别的日志,它会打印每一次执行的输入、对应异常,哪怕后续重跑出别的错,你也能从控制台日志里翻到首次失败的完整信息:
from hypothesis import settings, Verbosity @given(st.text(min_size=1, max_size=128)) @settings( verbosity=Verbosity.verbose, report_multiple_bugs=False ) @pytest.mark.asyncio async def test_hypothesis_add_column(name): # 原有测试逻辑不变
3. 临时关闭收缩阶段快速定位
如果开了verbose日志还是被后续报错刷得找不到原始错误,可以临时把重跑、收缩阶段关掉,Hypothesis找到第一个失败用例后不会做重复验证,直接抛当前错误:
from hypothesis import settings, Phase @given(st.text(min_size=1, max_size=128)) @settings( phases=[Phase.generate], # 只保留用例生成阶段,去掉重跑、收缩逻辑 report_multiple_bugs=False ) @pytest.mark.asyncio async def test_hypothesis_add_column(name): # 原有测试逻辑不变
注意:这个方法仅适合临时调试用,因为关掉收缩阶段后Hypothesis不会给你返回最小复现用例,调试完记得改回默认配置,再配合fixture清理做稳定测试。
内容的提问来源于stack exchange,提问作者Yann PIQUET

