如何为Python Tornado应用编写单元测试?

16

我希望尝试遵循TDD实践编写代码。我想基于Python的tornado框架创建简单的应用程序。我在网上查找了人们如何为tornado编写测试,并发现了类似于这样的内容:

class TestSomeHandler(AsyncHTTPTestCase):
    def test_success(self):
        response = self.fetch('/something')
        self.assertEqual(response.code, 200)

如果我错了,请纠正我,但这更像是集成测试。相反,我正在尝试为一些虚拟处理程序编写简单的单元测试,例如:

class SomeHandler(BaseHandler):
    @gen.coroutine
    def get(self):
        try:
            from_date = self.get_query_argument("from", default=None)
            datetime.datetime.strptime(from_date, '%Y-%m-%d')
        except ValueError:
            raise ValueError("Incorrect argument value for from_date = %s, should be YYYY-MM-DD" % from_date)

测试如下:

class TestSomeHandler(AsyncHTTPTestCase):

    def test_no_from_date_param(self):
        handler = SomeHandler()
        with self.assertRaises(ValueError):
            handler.get()

我知道我错过了get()应用程序和请求。还不知道如何创建它们。

但我的问题是,人们是否像第一个例子中那样为tornado编写测试,还是有人在应用程序内调用处理程序?要遵循哪种模式?如果有人有相关的代码共享,那就太好了。

2个回答

11

AsyncHTTPTestCase 模式的使用主要是因为它会为您处理所有的请求事项。当然,也可以使用 AsyncTestCase 并手动处理。

以下是 AsyncTestCase 的示例。由于它将测试协程函数的 get 方法,我们将使用 gen_test 使其变得更加简单。由于我们不需要应用程序的设置、ui_methods等,所以 RequestHandler 需要 ApplicationHTTPRequest 对象。因此,这里使用的是一个简单的模拟对象 Application

from tornado.testing import AsyncTestCase, gen_test
from tornado.web import Application
from tornado.httpserver import HTTPRequest
from unittest.mock import Mock

class TestSomeHandler(AsyncTestCase):

    @gen_test
    def test_no_from_date_param(self):
        mock_application = Mock(spec=Application)
        payload_request = HTTPRequest(
            method='GET', uri='/test', headers=None, body=None
        )
        handler = SomeHandler(mock_application, payload_request)
        with self.assertRaises(ValueError):
            yield handler.get()

我的看法是,使用哪种模式取决于你。对于http-verb方法(如get、post等),我选择AsyncHTTPTestCase,因为:

  • 它更容易实现
  • 这个方法的消费者是HTTP客户端,所以断言响应代码和正文非常有意义
  • 它可以防止出现过度复杂的方法

当然,处理程序的其余方法会使用AsyncTestCase进行测试。


据我理解,这种流程并不理想。但在其他情况下,我将无法测试异常情况。 - Viacheslav Kondratiuk
@viakondratiuk 这个方法的消费者是一些 HTTP 客户端(用户),你抛出的任何异常都只是带有一些消息的 50x 异常,可能是内部错误。这样可以吗?我认为不行 - 这显示了糟糕的错误处理,尝试返回“有意义”的错误(甚至是一些统一的消息),进一步的服务调用和调试将会更容易进行。 - kwarunek
Tornado 6.1 版本在 HTTPRequest 中没有 uri。第一个参数应该是 url。https://www.tornadoweb.org/en/stable/httpclient.html#request-objects - NuclearPeon

2

AsyncHTTPTestCase 是为像您第一个示例那样的测试设计的,使用 self.fetch 而不是直接实例化 Handlers。

RequestHandler 不应该在没有使用 Application 的情况下手动实例化,因此,如果您有一些功能片段希望在没有完整 HTTP 栈的情况下进行测试,这些代码通常应该是静态函数或非 RequestHandler 类。


所以,我的理解是,我应该使用AsyncHTTPTestCase和其他可能会抛出异常的代码来测试处理程序,我应该将其隔离并单独测试? - Viacheslav Kondratiuk
是的。界限由您决定:对于此处提供的异常示例,我个人会通过HTTP进行测试并验证其是否生成正确的状态代码和错误消息,而不是异常类型。 - Ben Darnell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接