python 3.12 中必须显式使用 @pytest.mark.asyncio 标记 async def 测试函数,否则 pytest 会跳过且无提示;需升级 pytest≥8.2.0 和 pytest-asyncio≥0.24.0,asyncio_mode=auto 已弃用。

pytest-asyncio 在 Python 3.12 中必须显式标记 @pytest.mark.asyncio
Python 3.12 不再静默运行 async def test_* 函数,即使配置了 asyncio_mode = auto,也会跳过测试且无提示。这不是 bug,是 pytest 对协程对象类型检查更严格后的行为变化。
常见错误现象:pytest -v 输出里完全看不到你的 async def test_xxx(),或者显示 collected 0 items;用 --collect-only 查看,会发现它被归类为 Function 而非 Coroutine,但 pytest 拒绝收集。
- 必须加
@pytest.mark.asyncio,缺一不可 -
asyncio_mode = auto在 pytest 8.2+ 已被弃用,配置了也无效,建议直接删掉 - 不要依赖 IDE 的“自动识别 async test”功能——它可能基于旧版逻辑,在 3.12 下误判
pytest 和 pytest-asyncio 版本必须同时达标
低于 pytest 7.4.0 会在 Python 3.12 下直接报 ImportError: cannot import name 'Mapping' from 'collections';而 pytest-asyncio 会导致 <code>@pytest.mark.asyncio 装饰器失效或测试卡在事件循环初始化阶段。
- 执行
pip install "pytest>=8.2.0" "pytest-asyncio>=0.24.0"(当前最新稳定版) - 验证:运行
python -m pytest --version,输出应含pytest 8.x;再运行python -c "import pytest_asyncio; print(pytest_asyncio.__version__)",确认 ≥0.24.0 - 如果项目用了
pyproject.toml,确保[build-system]和[project.optional-dependencies]里没锁死旧版本
async fixture 写法不变,但调用链必须全异步
自定义异步 fixture(如数据库连接、HTTP client 初始化)仍用 @pytest.fixture + async def,但它只能被同样带 @pytest.mark.asyncio 的测试函数消费。混用同步 fixture 和异步测试会触发 RuntimeError: no running event loop。
示例错误写法:
@pytest.fixture
async def db():
conn = await connect_db()
yield conn
await conn.close()
<p>def test_query(db): # ❌ 同步函数,db fixture 不会被 await,loop 未启动
assert db.execute("SELECT 1")
</p>
正确写法:
@pytest.mark.asyncio
async def test_query(db): # ✅ 测试函数异步,fixture 自动 await
result = await db.execute("SELECT 1")
assert result == [1]
- 不要在 fixture 里手动调用
asyncio.run()—— 它会新建 loop,与 pytest-asyncio 管理的 loop 冲突 - 若 fixture 需要复用(比如多个测试共用一个连接),记得加
scope="session"或scope="function",否则每次测试都重建连接
避免在测试类中重写 __getattr__
Python 3.12 修改了描述符协议顺序,导致测试类里自定义的 __getattr__ 可能拦截 pytest 注入的 fixture 属性(如 self.tmp_path、self.request),表现为 AttributeError 或返回 None。
典型现象:用了 tmp_path fixture,却报 AttributeError: 'TestFoo' object has no attribute 'tmp_path',而单独跑这个测试又正常。
- 临时缓解:把
__getattr__改成只响应特定前缀,例如只处理mock_*开头的属性名 - 根本解法:把动态属性逻辑抽到独立 helper 类,用组合而非继承,例如
self.mocks = MockHelper(),而不是让测试类自己承担该职责 - 如果必须保留
__getattr__,务必在末尾加raise AttributeError(...),不能漏掉
最易被忽略的一点:Python 3.12 的改动不报错,只让测试“变安静”——跳过、不执行、fixture 返回 None,错误信息模糊。一旦发现某个测试突然不跑了,先检查装饰器和版本,再查 __getattr__ 是否误伤了 pytest 内部属性。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











