pytest异步测试未await async fixture导致接口偶发404问题
问题根因
偶发404本质是异步fixture生命周期和测试用例事件循环绑定异常,不是fixture完全没被await,而是三类竞态问题导致fixture返回时插入操作实际未完成:
- pytest-asyncio 0.19及以上版本默认不会自动将未显式标记的异步fixture纳入测试用例的同事件循环调度,高并发跑批量用例时,偶发fixture协程被调度到已关闭的事件循环上,拿到inserted_id但插入流程未走完就进入测试逻辑
- 每次执行插入操作都新建AsyncIOMotorClient实例,连接建立、写入确认的异步流程没有和fixture返回时机强绑定,30个用例并行时连接池资源竞争会放大写入延迟,出现「返回了ID但数据还没落库」的情况
- PyCharm内置pytest运行器对原生pytest.fixture定义的异步fixture钩子兼容有问题,部分场景会直接返回未执行的协程对象,而不是等协程跑完再传值给测试用例
解决步骤
1. 配置pytest-asyncio全局自动异步模式
在项目根目录新建或修改pytest.ini,添加如下配置,强制所有异步测试、异步fixture自动在同一事件循环内调度,不需要逐个加@pytest.mark.asyncio标记:
[pytest] asyncio_mode = auto
2. 替换异步fixture的装饰器
所有异步fixture不要用原生@pytest.fixture装饰,改用pytest_asyncio.fixture,从根源解决PyCharm运行器和pytest对异步fixture的识别差异。修改后的测试数据fixture参考:
import pytest_asyncio from motor.motor_asyncio import WriteConcern # 从conftest导入全局复用的测试Mongo客户端,不要每次新建 @pytest_asyncio.fixture async def add_mock_topic(test_mongo_client): database = test_mongo_client[settings.TEST_DATABASE] collection = database.get_collection("topics_collection") # 显式指定写入确认级别,等数据真正写入主节点再返回 res = await collection.with_options(write_concern=WriteConcern(w=1)).insert_one({ "name": "TestName", "description": "test description" }) # 插入后主动校验数据存在,彻底消除竞态窗口 assert await collection.find_one({"_id": res.inserted_id}) is not None yield res # 测试完成后直接清理当前测试数据,比独立的cleanup fixture时序更可靠 await collection.delete_one({"_id": res.inserted_id})
3. 全局复用Mongo测试客户端
不要在每个增删改查的工具函数里新建AsyncIOMotorClient,在conftest.py里定义会话级的共享客户端,整个测试流程复用同一个连接池,避免频繁建连带来的延迟:
# conftest.py import motor.motor_asyncio import pytest_asyncio @pytest_asyncio.fixture(scope="session") async def test_mongo_client(): client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_DETAILS) # 启动时等待连接可用 await client.admin.command("ping") yield client # 所有测试结束后关闭连接 client.close()
4. PyCharm运行器兼容设置
打开PyCharm设置 -> 工具 -> Python集成工具,将默认测试运行器设置为pytest;编辑测试运行配置,将工作目录指向项目根目录,在环境变量中添加PYTEST_ASYNCIO_MODE=auto,避免运行器未读取pytest.ini配置导致的调度异常。
改完之后连续执行多次全量测试即可验证效果。之前把插入逻辑直接写在测试用例内部能稳定运行,本质是插入操作和测试请求在同一个协程调用链上,不存在跨fixture的事件循环调度错位,按上述配置修改后,fixture的执行时序和在测试用例内直接await的效果完全一致。
内容的提问来源于stack exchange,提问作者Jacek
相关产品推荐
相关产品推荐

