pytest-asyncio 可运行 async 测试函数,需安装并配置 asyncio_mode=auto;须 mock await 调用链中协程;coverage.py 支持 async 但需确保分支执行;避免手动管理事件循环以防测试中断。

用 pytest-asyncio 运行 async 测试函数
Python 默认的 pytest 不支持直接执行 async def 测试函数,会报 RuntimeWarning: coroutine 'test_something' was never awaited。必须显式启用异步支持。
安装并配置 pytest-asyncio 是最稳妥的方式:
- 运行
pip install pytest-asyncio - 在测试文件顶部或
pytest.ini中添加[tool:pytest]配置段,写入asyncio_mode = auto - 测试函数保持
async def test_xxx()形式,pytest会自动用事件循环执行它
不配 asyncio_mode 或设为 strict 时,只有加了 @pytest.mark.asyncio 的函数才被识别——漏标就会跳过,导致覆盖率虚高。
覆盖 await 调用链里的协程调用
单纯让测试函数能跑起来还不够。如果被测函数里 await 了外部协程(比如 HTTP 请求、数据库查询),而你没 mock 它们,测试会真实发起 I/O,既慢又不可控,还可能因网络失败导致覆盖率统计中断。
立即学习“Python免费学习笔记(深入)”;
正确做法是用 unittest.mock.AsyncMock(Python 3.8+)或 asynctest(旧版本)替换依赖:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 对返回协程的函数,用
AsyncMock(return_value="ok")替代Mock - 避免用
patch(..., return_value=coroutine),因为return_value不会自动 await,要改用side_effect返回可等待对象 - 示例:
mock_fetch.side_effect = AsyncMock(return_value={"data": 1}),这样await mock_fetch()才真能返回值
漏掉某一层 await 的 mock,测试可能抛 TypeError: object X can't be used in 'await' expression,或者走到真实代码分支,掩盖未覆盖路径。
确保 coverage.py 能统计 async/await 行
coverage.py 本身支持 async 语法,但有两个常见陷阱会导致覆盖率“漏算”:
- 协程函数定义行(
async def xxx():)默认不计入覆盖率——这是设计行为,不是 bug;关注的是函数体内的执行行 - 如果测试没真正进入某个
await分支(比如条件判断绕过了异步调用),那对应代码块不会被标记为已执行 - 使用
pytest-cov时,确保没加--cov-fail-under之类参数误判,先用coverage report -m看明细,确认是逻辑未覆盖还是工具问题
特别注意 async for 和 async with 块:它们内部的每行代码都需有测试路径触发,否则即使函数被调用,那些行仍显示为未覆盖。
别忽略事件循环生命周期管理
多个异步测试共用一个事件循环时,若某个测试提前 loop.close() 或引发未处理异常,后续测试可能卡住或报 RuntimeError: Event loop is closed,导致部分测试根本没跑,覆盖率自然偏低。
推荐做法:
- 不要手动创建/关闭
asyncio.get_event_loop(),让pytest-asyncio管理 - 避免在
teardown阶段调用loop.stop()或loop.close() - 如果必须清理资源(如启动后台任务),用
async def pytest_asyncio_cleanup()钩子,或在测试函数末尾await asyncio.sleep(0)让 pending task 有机会完成
覆盖率报告里突然出现大片灰色“未执行”区域,往往不是代码问题,而是某些测试崩溃后,后续测试被跳过所致——先检查 pytest 输出里有没有 ERROR 或 INTERNALERROR。

















