Pytest结合Hypothesis嵌套异步函数given装饰器报错如何解决
问题场景
编写测试用例时存在两个约束:
- 待测试的
Foo类实例只能在测试函数内部生成,无法在测试文件顶层初始化 - 使用Hypothesis生成测试值时,需要依赖
Foo实例作为入参生成对应测试数据
运行pytest时固定抛出如下错误,无论是否给内部嵌套的inner函数添加@pytest.mark.asyncio装饰器都无法解决:
hypothesis.errors.InvalidArgument: Hypothesis doesn't know how to run async test functions like inner
复现代码
import pytest from hypothesis import given from hypothesis import strategies as st class Foo: async def bar(value): return value @st.composite def int_datatype(draw, target): ... @pytest.mark.asyncio async def test_write_and_read_int(): targets = [Foo(), Foo(), Foo()] # can only be generated here for target in targets: print(target) @pytest.mark.asyncio @given(int_datatype(target=target)) async def inner(value: int): assert value == await target.bar(value) await inner()
运行环境
- Python 3.9.4
- pytest 7.1.2
- pytest-asyncio 0.15.1
- Hypothesis 6.49.1
修复方案
报错核心原因是Hypothesis在加载@given装饰器时,无法识别嵌套在其他测试函数内部的异步函数,也拿不到pytest-asyncio的异步运行上下文,靠给嵌套函数加装饰器的方式无法解决这个问题,两种可行的修复方式如下:
方案1:用pytest fixture管理Foo实例
把Foo实例的生成逻辑放到pytest fixture中,通过参数化传入测试用例,Hypothesis只负责生成独立的测试值,不需要在装饰器阶段绑定Foo实例,完全避开嵌套定义测试函数的问题:
import pytest from hypothesis import given from hypothesis import strategies as st class Foo: async def bar(self, value): return value @st.composite def int_datatype(draw): # 仅负责生成int类型测试值,不依赖Foo实例 return draw(st.integers()) # 函数级fixture,符合Foo实例只能在测试阶段生成的要求 @pytest.fixture(params=range(3)) def foo_target(request): return Foo() @pytest.mark.asyncio @given(value=int_datatype()) async def test_write_and_read_int(foo_target, value: int): assert value == await foo_target.bar(value)
方案2:用Hypothesis的data策略动态生成依赖实例的测试值
如果自定义的int_datatype策略必须依赖Foo实例才能生成测试数据,就使用st.data()策略,在测试函数执行、Foo实例已经初始化完成后,再动态调用draw方法生成测试值,不需要在循环内嵌套定义测试函数:
import pytest from hypothesis import given from hypothesis import strategies as st class Foo: async def bar(self, value): return value @st.composite def int_datatype(draw, target): # 原有依赖target生成测试值的逻辑保持不变 return draw(st.integers()) @pytest.mark.asyncio @given(data=st.data()) async def test_write_and_read_int(data): targets = [Foo(), Foo(), Foo()] for target in targets: # 运行时再生成依赖target的测试值 value = data.draw(int_datatype(target=target)) assert value == await target.bar(value)
两种方案都不需要在测试函数内部动态定义新的测试函数,从根源上避开Hypothesis无法识别嵌套异步测试的问题,第二种方案完全保留了原代码中在测试函数内生成Foo实例列表的逻辑,不需要调整实例生成的位置。
内容的提问来源于stack exchange,提问作者Phil997
相关产品推荐
相关产品推荐

