Tornado 对异步代码的单元测试支持

2022-03-11 14:16 更新

AsyncTestCase ​和 ​AsyncHTTPTestCase​:unittest.TestCase 的子类,额外支持测试异步(基于 ​IOLoop​)代码。

ExpectLog​:减少测试日志的垃圾邮件。

main()​:一个简单的测试运行器(围绕 ​unittest.main()​ 的包装),支持 ​tornado.autoreload​ 模块,以便在代码更改时重新运行测试。

异步测试用例

class tornado.testing.AsyncTestCase(methodName: str = 'runTest')

TestCase子类,用于测试基于 ​IOLoop的异步代码。

unittest框架是同步的,所以测试必须在测试方法返回时完成。 这意味着异步代码不能像往常一样以完全相同的方式使用,必须进行调整以适应。 要使用协程编写测试,请使用 ​tornado.testing.gen_test​ 而不是 ​tornado.gen.coroutine​ 装饰您的测试方法。

此类还提供了(已弃用的)​stop()​ 和 ​wait()​ 方法,用于更手动的测试风格。 测试方法本身必须调用 ​self.wait()​,异步回调应该调用 ​self.stop()​ 来表示完成。

默认情况下,会为每个测试构建一个新的 ​IOLoop​,并以 ​self.io_loop​ 的形式提供。 如果正在测试的代码需要全局 ​IOLoop​,子类应该覆盖 ​get_new_ioloop​ 以返回它。

不应直接调用 ​IOLoop​ 的启动和停止方法。 相反,使用 ​self.stop​ 和 ​self.wait​。 传递给 ​self.stop​ 的参数从 ​self.wait​ 返回。 在同一个测试中可以有多个等待或者停止周期。

例如:

# This test uses coroutine style.
class MyTestCase(AsyncTestCase):
    @tornado.testing.gen_test
    def test_http_fetch(self):
        client = AsyncHTTPClient()
        response = yield client.fetch("http://www.tornadoweb.org")
        # Test contents of response
        self.assertIn("FriendFeed", response.body)

# This test uses argument passing between self.stop and self.wait.
class MyTestCase2(AsyncTestCase):
    def test_http_fetch(self):
        client = AsyncHTTPClient()
        client.fetch("http://www.tornadoweb.org/", self.stop)
        response = self.wait()
        # Test contents of response
        self.assertIn("FriendFeed", response.body)

get_new_ioloop() → tornado.ioloop.IOLoop

返回要用于此测试的 ​IOLoop​。

默认情况下,为每个测试创建一个新的 ​IOLoop​。 如果不适合在每个测试中使用新的 ​IOLoop​(例如,如果有使用默认 ​IOLoop的全局单例)或者如果正在提供每个测试的事件循环,则子类可以覆盖此方法以返回 ​IOLoop.current()​ 由另一个系统(例如 ​pytest-asyncio​)。

stop(_arg: Any = None, **kwargs) → None

停止 ​IOLoop​,导致对 ​wait()​ 的一个挂起(或者​future​)调用返回。

传递给 ​stop()​ 的关键字参数或单个位置参数被保存并由 ​wait()​ 返回。

5.1 版后已弃用:​stop​和​wait​已弃用; 改用​@gen_test​。

wait(condition: Optional[Callable[[...], bool]] = None, timeout: Optional[float] = None) → Any

运行 ​IOLoop直到调用 ​stop或​timeout​。

如果​timeout​,将抛出异常。 默认​timeout​为 5 秒; 它可以被​timeout​关键字参数或全局的 ​ASYNC_TEST_TIMEOUT​ 环境变量覆盖。

如果 ​condition不是 ​None,则 ​IOLoop将在 ​stop()​ 之后重新启动,直到 ​condition()​ 返回 ​True​。

在 3.1 版更改: 添加了 ​ASYNC_TEST_TIMEOUT​ 环境变量。

5.1 版后已弃用:停止和等待已弃用; 改用​@gen_test​。

class tornado.testing.AsyncHTTPTestCase(methodName: str = 'runTest')

启动 HTTP 服务器的测试用例。

子类必须覆盖 ​get_app()​,它返回要测试的 ​tornado.web.Application​(或其他 ​HTTPServer回调)。 测试通常会使用提供的 ​self.http_client​ 从该服务器获取 URL。

示例,假设用户指南中的“Hello, world”示例在 hello.py 中:

import hello

class TestHelloApp(AsyncHTTPTestCase):
    def get_app(self):
        return hello.make_app()

    def test_homepage(self):
        response = self.fetch('/')
        self.assertEqual(response.code, 200)
        self.assertEqual(response.body, 'Hello, world')

对 ​self.fetch()​ 的调用等效于

self.http_client.fetch(self.get_url('/'), self.stop)
response = self.wait()

这说明了 ​AsyncTestCase如何将异步操作(如 ​http_client.fetch()​)转换为同步操作。 如果您需要在测试中执行其他异步操作,您可能需要自己使用 ​stop()​ 和 ​wait()​。

get_app() → tornado.web.Application

应该被子类覆盖以返回 ​tornado.web.Application​ 或其他 ​HTTPServer回调。

fetch(path: str, raise_error: bool = False, **kwargs) → tornado.httpclient.HTTPResponse

同步获取 URL 的便捷方法。

给定的路径将附加到本地服务器的主机和端口。任何其他关键字参数都将直接传递给 ​AsyncHTTPClient.fetch​(因此可用于传递 ​method="POST"​、​body="..."​ 等)。

如果路径以 ​http://​ 或 ​https://​ 开头,则会将其视为完整 URL 并按原样获取。

如果 ​raise_error​ 为 ​True,则如果响应代码不是 200,则会引发 ​tornado.httpclient.HTTPError​。这与 ​AsyncHTTPClient.fetch​ 的 ​raise_error​ 参数的行为相同,但此处默认为 ​False​(在 ​AsyncHTTPClient中为 ​True),因为测试经常需要处理非 200 响应码。

在 5.0 版更改: 添加了对绝对 URL 的支持。

在 5.1 版更改: 添加 ​raise_error参数。

5.1 版后已弃用:此方法当前将任何异常转换为状态码为 599 的 ​HTTPResponse。在 Tornado 6.0 中,将传递除 ​tornado.httpclient.HTTPError​ 以外的错误,并且 ​raise_error=False​ 只会抑制由于以下原因引发的错误非 200 响应代码

get_httpserver_options() → Dict[str, Any]

可以被子类覆盖以返回服务器的附加关键字参数。

get_http_port() → int

返回服务器使用的端口。

为每个测试选择一个新端口。

get_url(path: str) → str

返回测试服务器上给定路径的绝对 url。

class tornado.testing.AsyncHTTPSTestCase(methodName: str = 'runTest')

启动 HTTPS 服务器的测试用例。

接口一般与 ​AsyncHTTPTestCase​ 相同。

get_ssl_options() → Dict[str, Any]

可以被子类覆盖以选择 SSL 选项。

默认情况下包括自签名测试证书。

tornado.testing.gen_test(func: Optional[Callable[[...], Union[collections.abc.Generator, Coroutine]]] = None, timeout: Optional[float] = None) → Union[Callable[[...], None], Callable[[Callable[[...], Union[collections.abc.Generator, Coroutine]]], Callable[[...], None]]]

测试等效于​@gen.coroutine​,应用于测试方法。

@gen.coroutine​ 不能用于测试,因为 ​IOLoop尚未运行。 ​@gen_test​ 应该应用于 ​AsyncTestCase​ 子类的测试方法。

例如:

class MyTest(AsyncHTTPTestCase):
    @gen_test
    def test_something(self):
        response = yield self.http_client.fetch(self.get_url('/'))

默认情况下,​@gen_test​ 在 5 秒后超时。 可以使用 ​ASYNC_TEST_TIMEOUT环境变量全局覆盖超时,或者使用 ​timeout关键字参数对每个测试进行覆盖:

class MyTest(AsyncHTTPTestCase):
    @gen_test(timeout=10)
    def test_something_slow(self):
        response = yield self.http_client.fetch(self.get_url('/'))

请注意,​@gen_test​ 与 ​AsyncTestCase.stop​、​AsyncTestCase.wait​ 和 ​AsyncHTTPTestCase.fetch​ 不兼容。 如上所示,使用 ​yield self.http_client.fetch(self.get_url())​ 代替。

控制日志输出

class tornado.testing.ExpectLog(logger: Union[logging.Logger, str], regex: str, required: bool = True, level: Optional[int] = None)

上下文管理器,用于捕获和抑制预期的日志输出。

有助于减少错误条件测试,同时仍使意外的日志条目可见。 这并不是线程安全的。

如果记录了任何异常堆栈跟踪,则属性 ​logged_stack设置为 ​True​。

例如:

with ExpectLog('tornado.application', "Uncaught exception"):
    error_response = self.fetch("/some_page")

在 4.3 版更改: 添加了 ​logged_stack属性。

构造一个 ​ExpectLog上下文管理器。

参数:

  • logger-- 要观察的 Logger 对象(或 logger 的名称)。 传递一个空字符串来观察根记录器。
  • regex-- 要匹配的正则表达式。 指定记录器上与此正则表达式匹配的任何日志条目都将被禁止。
  • required- 如果为 true,如果到达 with 语句的末尾但没有匹配任何日志条目,则会引发异常。
  • level- 来自日志记录模块的常量,指示预期的日志级别。 如果提供此参数,则仅此级别的日志消息将被视为匹配。 此外,提供的记录器将在必要时调整其级别(在 ​ExpectLog的持续时间内启用预期的消息)。

在 6.1 版更改: 添加了 ​level参数。

测试

tornado.testing.main(**kwargs) → None

一个简单的测试运行器。

这个测试运行器本质上等同于标准库中的 ​unittest.main​,但增加了对 Tornado 样式选项解析和日志格式的支持。 使用 ​AsyncTestCase运行测试不需要使用这个 main 函数; 这些测试是独立的,可以与任何测试运行器一起运行。

运行测试的最简单方法是通过命令行:

python -m tornado.testing tornado.test.web_test

具有许多测试的项目可能希望定义一个测试脚本,例如 tornado/test/runtests.py。 这个脚本应该定义一个方法 ​all()​,它返回一个测试套件,然后调用 ​tornado.testing.main()​。 请注意,即使使用了测试脚本,也可以通过在命令行上命名单个测试来覆盖 ​all()​ 测试套件:

# Runs all tests
python -m tornado.test.runtests
# Runs one test
python -m tornado.test.runtests tornado.test.web_test

传递给 ​unittest.main()​ 的其他关键字参数。 例如,使用 ​tornado.testing.main(verbosity=2)​ 在运行时显示许多测试详细信息。

在 5.0 版更改: 此函数不产生自己的输出; 仅由 ​unittest模块生成(以前它会添加 ​PASS或 ​FAIL日志消息)。

辅助函数

tornado.testing.bind_unused_port(reuse_port: bool = False) → Tuple[socket.socket, int]

将服务器socket绑定到 localhost 上的可用端口。

返回一个元组(socket,port)。

在 4.4 版更改: 始终绑定到 127.0.0.1 而不解析名称 localhost。

tornado.testing.get_async_test_timeout() → float

获取异步测试的全局超时设置。

返回一个浮点数,以秒为单位的超时。


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号