必须启用pytest-asyncio插件并配置asyncio_mode = auto,或用@pytest.mark.asyncio标记async def测试函数;tornado 6.0+需避免混用gen.coroutine与async def,推荐继承asynchttptestcase进行集成测试。

pytest如何运行Tornado异步测试函数
pytest默认不支持async def测试函数,直接写会报TypeError: object async_generator can't be used in 'await' expression或静默跳过。必须启用asyncio插件并正确标记协程。
安装pytest-asyncio后,在测试文件顶部加@pytest.mark.asyncio装饰器,或在pytest.ini中配置asyncio_mode = auto(推荐后者,避免漏标):
[tool:pytest] asyncio_mode = auto
注意:Tornado 6.0+ 使用async/await,不要混用gen.coroutine和async def——pytest无法处理旧式生成器协程。
如何启动Tornado应用进行集成测试
不能直接调用app.listen(),因为会阻塞事件循环。必须用AsyncHTTPTestCase或手动构造AsyncHTTPClient配合IOStream模拟请求。
推荐方式是继承tornado.testing.AsyncHTTPTestCase,它自动管理IOLoop、HTTP服务器和客户端:
class TestMyHandler(AsyncHTTPTestCase):
def get_app(self):
return make_app() # 返回你的Application实例
<pre class="brush:python;toolbar:false;">def test_homepage(self):
response = self.fetch("/")
self.assertEqual(response.code, 200)
关键点:
-
self.fetch()是异步的,但AsyncHTTPTestCase内部已封装为同步调用,无需await - 测试结束后,框架自动关闭server和IOLoop,不用手动清理
- 若需自定义请求头或body,传入
headers或body参数给self.fetch()
如何测试带数据库或外部依赖的异步Handler
Tornado Handler里常含await db.query()这类调用,直接测会连真实DB——必须mock或替换为内存实例。
两种常用做法:
- 用
unittest.mock.patch打桩异步方法:@patch("myapp.handlers.UserHandler.get_user", return_value=mock_user) - 在
get_app()中注入测试用的DB连接池,例如用aiosqlite替代asyncpg,或用momoko的MockConnection
特别注意:Tornado的RequestHandler对象本身不是协程,它的get/post方法才是。mock时要确保patch的是实际被await的对象,而不是Handler类本身。
为什么test_client.fetch()返回的是HTTPResponse而非awaitable
因为AsyncHTTPTestCase重写了fetch(),内部调用了self.io_loop.run_sync()把协程转为同步结果。这看起来像“同步调用”,但底层仍是异步执行。
这意味着:
- 不能在
fetch()后直接await,会报RuntimeError: await wasn't used with a coroutine - 如果需要原始响应体字节,用
response.body;JSON解析用json.loads(response.body.decode()) - 若要测试超时或连接异常,需改用
AsyncHTTPClient手动发起请求,并显式await——这时才真正暴露底层协程行为
最易忽略的是:Tornado测试中self.fetch()的路径不带协议和域名,只写"/api/v1/user",写成"http://localhost:8888/..."会导致404。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











