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框架是同步的,所以測試必須在測試方法返回時完成。 這意味著異步代碼不能像往常一樣以完全相同的方式使用,必須進行調整以適應。 要使用協(xié)程編寫測試,請使用 ?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?。

默認情況下,為每個測試創(chuàng)建一個新的 ?IOLoop?。 如果不適合在每個測試中使用新的 ?IOLoop?(例如,如果有使用默認 ?IOLoop的全局單例)或者如果正在提供每個測試的事件循環(huán),則子類可以覆蓋此方法以返回 ?IOLoop.current()? 由另一個系統(tǒng)(例如 ?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? 環(huán)境變量覆蓋。

如果 ?condition不是 ?None,則 ?IOLoop將在 ?stop()? 之后重新啟動,直到 ?condition()? 返回 ?True?。

在 3.1 版更改: 添加了 ?ASYNC_TEST_TIMEOUT? 環(huán)境變量。

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()?)轉換為同步操作。 如果您需要在測試中執(zhí)行其他異步操作,您可能需要自己使用 ?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,則會引發(fā) ?tornado.httpclient.HTTPError?。這與 ?AsyncHTTPClient.fetch? 的 ?raise_error? 參數的行為相同,但此處默認為 ?False?(在 ?AsyncHTTPClient中為 ?True),因為測試經常需要處理非 200 響應碼。

在 5.0 版更改: 添加了對絕對 URL 的支持。

在 5.1 版更改: 添加 ?raise_error參數。

5.1 版后已棄用:此方法當前將任何異常轉換為狀態(tài)碼為 599 的 ?HTTPResponse。在 Tornado 6.0 中,將傳遞除 ?tornado.httpclient.HTTPError? 以外的錯誤,并且 ?raise_error=False? 只會抑制由于以下原因引發(fā)的錯誤非 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環(huán)境變量全局覆蓋超時,或者使用 ?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 語句的末尾但沒有匹配任何日志條目,則會引發(fā)異常。
  • ?level- 來自日志記錄模塊的常量,指示預期的日志級別。 如果提供此參數,則僅此級別的日志消息將被視為匹配。 此外,提供的記錄器將在必要時調整其級別(在 ?ExpectLog的持續(xù)時間內啟用預期的消息)。

在 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

公眾號
微信公眾號

編程獅公眾號