pytest默认不运行async测试函数,因其原生仅识别同步函数;async def test_返回协程对象,而pytest测试发现机制未启用协程调度逻辑,故不装pytest-asyncio插件或未配置asyncio_mode则无法正确执行。

直接结论:不用 pytest-asyncio,async def test_ 函数会被静默跳过或报 "not a coroutine" 错误;装了插件但没配 asyncio_mode = auto,效果等同于没装。
为什么 pytest 默认不运行 async 测试函数
pytest 原生只识别同步函数。当你写了一个 async def test_something(),它返回的是一个协程对象,不是可执行的测试用例。pytest 会把它当普通函数处理——要么忽略,要么在收集阶段就报错,比如 TypeError: object MagicMock can't be used in 'await' expression(如果中间混用了 mock),或者更常见的 not a coroutine。
根本原因不是语法错,而是 pytest 的测试发现机制压根没启用协程调度逻辑。
- 不装
pytest-asyncio→ 强制你手动调用asyncio.run()或管理 event loop,繁琐且易出错 - 装了插件但没配置
asyncio_mode→ 插件不生效,行为和没装一样 - 配了
asyncio_mode = strict→ 所有async def都必须加@pytest.mark.asyncio,否则跳过;新手容易漏标
必须做的三件事:安装、配置、标记(可选)
缺一不可,顺序不能乱:
立即学习“Python免费学习笔记(深入)”;
- 安装:
pip install pytest-asyncio(注意不是pytest_asyncio,下划线是错的) - 配置:在项目根目录放
pyproject.toml,内容必须含:[tool.pytest.ini_options] asyncio_mode = "auto"
(pytest.ini也行,但pyproject.toml是当前推荐) - 标记:在
asyncio_mode = auto下,async def test_函数自动被识别为异步测试;若用strict模式,则每个函数前必须加@pytest.mark.asyncio
验证是否生效:运行 pytest --co,看输出里是否有 async 字样;再跑一次 pytest -v,确认测试函数状态是 . 而不是 skipped。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
常见错误:mock 异步函数时忘 await 或用错 Mock 类型
比如你要 mock 一个 async def fetch_data(url),直接用 Mock() 会失败,因为 Mock 返回的是普通对象,不是协程。
正确做法是让 mock 返回协程:
- 最简方式:
AsyncMock(Python 3.8+ 内置):from unittest.mock import AsyncMock mock_fetch = AsyncMock(return_value={"ok": True}) - 兼容旧版本(CoroutineMock 来自
pytest-mock,或手写:from unittest.mock import Mock import asyncio def make_coro(result): async def _coro(): return result return _coro() mock_fetch = Mock(return_value=make_coro({"ok": True})) - 错误示范:
Mock(return_value=...)直接赋值,没包装成协程 →RuntimeError: no running event loop或TypeError: object Mock can't be used in 'await' expression
event loop 隔离问题:多个测试间共享 loop 导致状态污染
默认情况下,pytest-asyncio 用的是 module 级 event loop,即同一个模块下的所有 async def test_ 共享一个 loop 实例。如果你在某个测试里调用了 asyncio.set_event_loop(),或手动创建了 asyncio.Queue、asyncio.Lock 并没清理,下一个测试可能拿到残留状态。
解决办法是显式控制 fixture 作用域:
- 加
@pytest.fixture(scope="function")修饰一个async def event_loopfixture,每次测试新建 loop - 或直接在
pyproject.toml里加:[tool.pytest.ini_options] asyncio_mode = "auto" asyncio_fixture_loop_scope = "function"
(pytest-asyncio ≥ 0.24 支持) - 不推荐改全局 loop,尤其在 CI 环境下,容易因并发导致随机失败
这个点非常隐蔽:本地跑 100 次都过,CI 上偶发失败,日志里只显示 RuntimeError: Task got bad yield: <future pending ...></future> —— 很可能就是 loop 没隔离干净。

















