【发布时间】:2018-02-08 19:14:12
【问题描述】:
我在使用 AsyncHTTPTestCase 为使用 asyncio 事件循环的现有 Tornado 应用程序编写测试时遇到了一些麻烦。 在这里,我准备了可以重现问题的简短模型:
app.py
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio
import tornado.web
class MainHandler(tornado.web.RequestHandler):
async def get(self, *args, **kwargs):
self.write("200 OK")
async def post(self, *args, **kwargs):
self.write("201 OK")
def make_app():
AsyncIOMainLoop().install() # here is how to asyncio loop installed in app I already have
return tornado.web.Application([
(r"/", MainHandler),
], debug=True)
def start_app():
app = make_app()
app.listen(8888)
loop = asyncio.get_event_loop()
loop.set_debug(True)
loop.run_forever()
start.py
#!/usr/bin/env python3
import app
if __name__ == "__main__":
app.start_app()
test_app.py
import json
from tornado.testing import AsyncHTTPTestCase
import app
class TestHelloApp(AsyncHTTPTestCase):
def get_app(self):
return app.make_app()
def test_get(self):
response = self.fetch('/')
self.assertEqual(response.code, 200)
self.assertEqual(response.body.decode(), '200 OK')
def test_post(self):
response = self.fetch('/', method="POST",
body=json.dumps({"key": "value"}))
self.assertEqual(response.code, 200)
self.assertEqual(response.body.decode(), '201 OK')
使用这种安装异步循环应用程序的方法可以正常工作(我的意思是我可以发出请求并得到响应),但是像这样的测试失败并出现错误:
======================================================================
FAIL: test_post (test_app.TestHelloApp)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/biceps/work/torn/.venv/lib/python3.6/site-packages/tornado/testing.py", line 380, in setUp
self._app = self.get_app()
File "/home/biceps/work/torn/test_app.py", line 8, in get_app
return app.make_app()
File "/home/biceps/work/torn/app.py", line 14, in make_app
tornado.platform.asyncio.AsyncIOMainLoop().install()
File "/home/biceps/work/torn/.venv/lib/python3.6/site-packages/tornado/ioloop.py", line 181, in install
assert not IOLoop.initialized()
AssertionError
----------------------------------------------------------------------
Ran 2 tests in 0.006s
FAILED (failures=1)
似乎由 AsyncIOMainLoop().install() 命令安装的循环在测试之间没有停止,第一个测试通过 OK,但第二个总是失败。
当我将 AsyncIOMainLoop().install() 移动到 start_app() 方法时 - 测试正常通过,但我担心在测试期间我使用一个事件循环,但在实际运行的应用程序中我使用 asyncio 循环。
因此,针对该代码的测试可以通过:
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio
import tornado.web
class MainHandler(tornado.web.RequestHandler):
async def get(self, *args, **kwargs):
self.write("200 OK")
async def post(self, *args, **kwargs):
self.write("201 OK")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
], debug=True)
def start_app():
AsyncIOMainLoop().install()
app = make_app()
app.listen(8888)
loop = asyncio.get_event_loop()
loop.set_debug(True)
loop.run_forever()
问:我的问题是 - 如何在该用例中正确编写测试? Tornado 应用程序使用 AsyncIOMainLoop 时如何使用 AsyncHTTPTestCase 编写测试? 我是否决定将 AsyncIOMainLoop().install() 放入 start_app(),而不是 make_app() 函数中?
附:我已将 self.io_loop.clear_instance() 添加到 tearDown() - 它看起来可能很脏,但适用于从 make_app() 代码调用 AsyncIOMainLoop().install() 的情况。
def tearDown(self):
self.io_loop.clear_instance()
super().tearDown()
【问题讨论】:
-
假设
AsyncHTTPTestCase是unitest.TestCase的子类,在TestHelloApp中,将get_app重命名为setUp或setUpClass,这样它将在Test 类启动时运行。 -
@Gang 谢谢你,但 get_app() 也是从 setUp() 方法调用的。问题是 IOloop 在测试运行之间没有停止(据我所知,我需要在测试之间停止和关闭该循环)。
标签: python unit-testing tornado python-asyncio