必须安装pytest-asyncio并配置asyncio_mode=strict,且测试函数需用@pytest.mark.asyncio标记为async def,否则pytest会跳过或报runtimewarning;禁用asyncio.run(),直接await协程。

直接用 pytest-asyncio 跑异步测试函数是可行的,但必须显式标记协程函数并启用事件循环管理,否则会报 RuntimeWarning: coroutine 'test_xxx' was never awaited 或直接跳过测试。
如何让 pytest 正确执行 async def 测试函数
pytest 默认不识别 async def 函数为测试用例。需同时满足两个条件:
- 安装
pytest-asyncio(>=0.21.0),并确保 pytest 配置中启用了插件 - 在测试函数上加
@pytest.mark.asyncio装饰器,或在pytest.ini中全局配置asyncio_mode = auto - 函数名仍需以
test_开头,且不能带参数(除非用pytest.mark.parametrize)
推荐用装饰器方式,更显式、更可控。例如:
import pytest
@pytest.mark.asyncio
async def test_fetch_data():
result = await some_async_function()
assert result == "expected"
为什么 test 函数里 await 会报 RuntimeError: no running event loop
pytest-asyncio 默认为每个标记的测试函数创建并管理一个新事件循环,但该循环只在测试函数体内部有效。常见陷阱包括:
- 在
setup_method/teardown_method等生命周期方法中直接await—— 这些方法不是被@pytest.mark.asyncio包裹的协程,无法访问 pytest 创建的事件循环 - 手动调用
asyncio.run()—— 会冲突,因为 pytest 已在管理循环;应直接await - 在非测试函数(如 fixture)中 await 却没声明
@pytest.fixture的scope和autouse,或没加@pytest.mark.asyncio
fixture 若需异步初始化,应定义为普通函数并返回协程对象,再在测试中 await;或使用 @pytest.fixture + @pytest.mark.asyncio 组合(注意:fixture 本身不能是 async def,但可返回 awaitable)。
asyncio_mode = auto 和 strict 有什么实际区别
这是 pytest.ini 或 pyproject.toml 中的关键配置项,影响匹配逻辑:
-
asyncio_mode = auto:对所有名字含test_且是async def的函数自动加@pytest.mark.asyncio—— 看似方便,但容易误触发,比如你写了async def test_helper()本意是工具函数,结果被当成测试运行 -
asyncio_mode = strict:仅当显式标记@pytest.mark.asyncio才执行,其余async def test_*会被 pytest 忽略(并提示collected 0 items)—— 更安全,推荐生产环境使用
配置示例(pytest.ini):
[tool:pytest] asyncio_mode = strict
最易被忽略的是:pytest-asyncio 不支持嵌套 await(比如 fixture 返回协程,测试里又 await 它),也不兼容某些老版本 asyncpg 或自定义事件循环策略;如果遇到 RuntimeError: There is no current event loop in thread,大概率是跨线程调用或循环已被关闭,这时别硬等,改用 asyncio.create_task() + asyncio.wait_for() 显式控制生命周期更稳妥。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











