pytest-tornado插件需适配tornado 5.0+的async/await语法,旧版fixture因仍依赖yield from导致http_client.fetch()不返回或抛出assertionerror。

pytest-tornado 插件能省掉手动管理 IOLoop 和 AsyncHTTPClient 的大部分胶水代码,但直接装上就用容易报错——比如 RuntimeError: IOLoop is already running 或测试卡死无响应。核心问题不是“能不能测”,而是“怎么让 pytest 和 Tornado 的事件循环不打架”。
为什么 pytest-tornado 的 fixture 会失效
常见现象是:测试函数里用了 @pytest.mark.gen_test,但 http_client.fetch() 永远不返回,或抛出 AssertionError: yield from wasn't used with a future。
- 根本原因是:Tornado 5.0+ 默认使用
async/await,而pytest-tornado的旧版(tornado.gen.coroutine 和回调风格,和 Python 3.7+ 的原生协程不兼容 - 如果你的 handler 用的是
async def get(self),但测试里还写yield http_client.fetch(...),就会类型错配 -
appfixture 返回的 Application 如果没调用settings.setdefault("autoreload", False),pytest 多次运行时可能触发热重载冲突
@pytest.mark.gen_test 和 async def test_... 到底该选哪个
答案取决于你用的 Tornado 版本和 handler 写法:
- Tornado ≥6.0 + handler 用
async def→ 直接写async def test_xxx(),**不要加@pytest.mark.gen_test**,否则会套两层协程调度器 - Tornado 4.x–5.x + handler 用
@gen.coroutine→ 必须用@pytest.mark.gen_test,且内部用yield调用异步操作 - 混用会出错:比如 handler 是
async def,测试却加了@gen_test,self.fetch()返回的是Future,但await它会报RuntimeError: cannot reuse already awaited coroutine
如何正确 mock 异步依赖(比如数据库调用)
别用 unittest.mock.patch 直接 patch 一个 async def 函数——它返回的是 coroutine 对象,不是可 await 的结果。
- 正确做法是 patch 后返回一个已封装好的协程:
mock.patch("myapp.db.query", return_value=mock.AsyncMock(return_value=[{"id": 1}])) - 或者更稳妥:用
asyncio.ensure_future()包一层真实返回值:return_value=asyncio.ensure_future(asyncio.sleep(0) or [{"id": 1}]) - 如果被测 handler 里有
await db.find(),而你 mock 返回的是普通 list,会报TypeError: object list can't be used in 'await' expression
测试 WebSocket handler 时最常漏掉的一件事
AsyncHTTPTestCase 和 pytest-tornado 的 http_client 都不支持 WebSocket 升级请求。你不能用 http_client.fetch("ws://...")。
- 必须改用
tornado.websocket.WebSocketClientConnection手动建连 - 连接后要显式调用
await conn.write_message(...),再用await conn.read_message()等响应 - 别忘了在测试结尾调用
conn.close(),否则IOLoop可能残留未关闭的 socket 导致后续测试失败
assert response.code == 200,而是 handler 里混着 await、yield、线程池 run_on_executor,又调了外部 API 和 DB——这时测试里的事件循环、协程生命周期、资源清理,哪一环松了都会让断言通过但逻辑已偏移。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











