[Twisted] Part 15: Tested Poetry
本文由Dave的Part 15: Tested Poetry翻譯而成,你可以由Part 1開始閱讀這個系列的文章,也可以在這裡找到整個系列的目錄。
幸運的是,Twisted包含了自己的測試框架,叫做trial,它支援測試非同步程式碼(而且你也可以用它測試同步程式碼)。
我們將假設你已經熟悉了unittest與類似測試框架的基本機制,這些框架中你透過定義一個繼承指定父類別(通常叫做TestCase之類的)的類別來建立測試,而類別中每個以「test」開頭的方法就會被認定是一項測試。這些框架負責找出所有的測試方法,配合上選擇性的setUp和tearDown,一起依序執行它們,最後報告結果。
這個範例透過由用戶端從測試伺服器取回詩歌,來測試詩歌用戶端與伺服器。為了提供用於測試的詩歌伺服器,我們在我們的測試案例中實現了setUp方法:
setUp方法以測試詩歌建立詩歌伺服器,並監聽一個隨機的開放埠口。我們會保存這個埠號,這樣實際測試時如果我們需要就可以使用它。當然,我們在測試完成時,在tearDown中清理掉測試伺服器:
讓我們來看看我們第一個測試test_client,我們使用get_poetry從測試伺服器取回詩歌,並驗證這首詩歌是我們所要的:
注意我們的測試函式是回傳deferred實例。在trail框架中,每個測試方法都作為callback執行。這表示reactor正在執行,而我們能在測試過程中執行非同步的操作。我們只需要讓框架知道我們的測試是非同步的,所以我們用常見Twisted的方法讓框架知道-回傳deferred實例。
trial框架在呼叫tearDown方法之前,會一直等待直到deferred實例被觸發,並且如果deferred實例失敗那測試也會失敗(即最後一對callback/errback失敗)。如果我們的deferred觸發的時間太長,測試也會失敗,預設是兩分鐘。這也表示如果測試完成,我們就知道我們的deferred被觸發,因此我們的callback被觸發並執行assertEquals測試方法。
我們的第二個測試test_failure,驗證如果我們無法連接到伺服器,get_poetry會以適當的方式失敗:
這裡我們嘗試連接到無效埠口,然後使用trial提供的assertFailure方法。這個方法類似我們熟悉的assertRaises方法,但是是給非同步程式碼用的。如果給定的deferred因給定的例外而失敗,它回傳成功結果的deferred實例,否則回傳失敗結果的deferred實例。
你可以自行像這樣使用trail script執行這個測試:
你應該會看到一些輸出顯示每個測試案例,以及一個OK告訴你每個測試都通過了。
任何來自測試的log訊息都會被收集在_trial_temp資料夾的檔案中,如果資料夾不存在trial會自動建立它。除了輸出到螢幕的錯誤之外,log對於debug失敗的測試是個有用的開始點。
圖33顯示了一個正在進行中的假想測試:
如果你之前使用過類似的框架,這張圖應該是個熟悉的模型,除了所有測試相關的方法都回傳defered之外。
trial框架也很好的說明「進行非同步」是如何涉及串聯(cascade)整個程式的改變。為了測試(或任何函式或方法)成為非同步的,它必須:
但這表示無論用什麼去呼叫,那個函式必須願意接收deferred實例,也不能阻塞(因此也可能回傳deferred實例)。如此一層一層的往上推論。因此就有了需要一個像trial的框架的需求,能處理這些回傳deferreds的非同步測試。
在Part 16中,我們將使用Twisted工具將我們的詩歌伺服器轉化為一個真正的daemon。
Introduction
我們在探索Twisted時寫了很多程式碼,但目前為止我們忽略了寫一些很重要的東西-測試。而你可能很好奇你如何用同步框架像是Python附帶的unittest,來測試非同步的程式碼。簡單來說,你不能。像我們已經發現的,同步與非同步程式碼不能混用,至少不容易。幸運的是,Twisted包含了自己的測試框架,叫做trial,它支援測試非同步程式碼(而且你也可以用它測試同步程式碼)。
我們將假設你已經熟悉了unittest與類似測試框架的基本機制,這些框架中你透過定義一個繼承指定父類別(通常叫做TestCase之類的)的類別來建立測試,而類別中每個以「test」開頭的方法就會被認定是一項測試。這些框架負責找出所有的測試方法,配合上選擇性的setUp和tearDown,一起依序執行它們,最後報告結果。
The Example
你可以在tests/test_poetry.py找到一些測試的範例。為了確保所有我們的範例都是獨立運作(因此你不必擔心有關PYTHONPATH的設定),我們已經將所有所需的程式碼複製到測試模組中。當然,通常你只需要import你想要測試的模組就好。這個範例透過由用戶端從測試伺服器取回詩歌,來測試詩歌用戶端與伺服器。為了提供用於測試的詩歌伺服器,我們在我們的測試案例中實現了setUp方法:
class PoetryTestCase(TestCase):
def setUp(self):
factory = PoetryServerFactory(TEST_POEM)
from twisted.internet import reactor
self.port = reactor.listenTCP(0, factory, interface="127.0.0.1")
self.portnum = self.port.getHost().port
setUp方法以測試詩歌建立詩歌伺服器,並監聽一個隨機的開放埠口。我們會保存這個埠號,這樣實際測試時如果我們需要就可以使用它。當然,我們在測試完成時,在tearDown中清理掉測試伺服器:
def tearDown(self):
port, self.port = self.port, None
return port.stopListening()
讓我們來看看我們第一個測試test_client,我們使用get_poetry從測試伺服器取回詩歌,並驗證這首詩歌是我們所要的:
def test_client(self):
"""The correct poem is returned by get_poetry."""
d = get_poetry('127.0.0.1', self.portnum)
def got_poem(poem):
self.assertEquals(poem, TEST_POEM)
d.addCallback(got_poem)
return d
注意我們的測試函式是回傳deferred實例。在trail框架中,每個測試方法都作為callback執行。這表示reactor正在執行,而我們能在測試過程中執行非同步的操作。我們只需要讓框架知道我們的測試是非同步的,所以我們用常見Twisted的方法讓框架知道-回傳deferred實例。
trial框架在呼叫tearDown方法之前,會一直等待直到deferred實例被觸發,並且如果deferred實例失敗那測試也會失敗(即最後一對callback/errback失敗)。如果我們的deferred觸發的時間太長,測試也會失敗,預設是兩分鐘。這也表示如果測試完成,我們就知道我們的deferred被觸發,因此我們的callback被觸發並執行assertEquals測試方法。
我們的第二個測試test_failure,驗證如果我們無法連接到伺服器,get_poetry會以適當的方式失敗:
def test_failure(self):
"""The correct failure is returned by get_poetry when
connecting to a port with no server."""
d = get_poetry('127.0.0.1', 0)
return self.assertFailure(d, ConnectionRefusedError)
這裡我們嘗試連接到無效埠口,然後使用trial提供的assertFailure方法。這個方法類似我們熟悉的assertRaises方法,但是是給非同步程式碼用的。如果給定的deferred因給定的例外而失敗,它回傳成功結果的deferred實例,否則回傳失敗結果的deferred實例。
你可以自行像這樣使用trail script執行這個測試:
trial tests/test_poetry.py
你應該會看到一些輸出顯示每個測試案例,以及一個OK告訴你每個測試都通過了。
Discussion
在基本的API方面,trial與unittest非常相似,所以開始去撰寫測試非常容易。如果你的測試使用非同步程式碼,只需要回傳deferred實例,而trial會處理其他的部分。你也可以從setUp或tearDown方法回傳deferred實例,如果它們也需要使用非同步。任何來自測試的log訊息都會被收集在_trial_temp資料夾的檔案中,如果資料夾不存在trial會自動建立它。除了輸出到螢幕的錯誤之外,log對於debug失敗的測試是個有用的開始點。
圖33顯示了一個正在進行中的假想測試:
如果你之前使用過類似的框架,這張圖應該是個熟悉的模型,除了所有測試相關的方法都回傳defered之外。
trial框架也很好的說明「進行非同步」是如何涉及串聯(cascade)整個程式的改變。為了測試(或任何函式或方法)成為非同步的,它必須:
- 沒有阻塞。
- 而且一般來說回傳deferred實例。
但這表示無論用什麼去呼叫,那個函式必須願意接收deferred實例,也不能阻塞(因此也可能回傳deferred實例)。如此一層一層的往上推論。因此就有了需要一個像trial的框架的需求,能處理這些回傳deferreds的非同步測試。
Summary
這就是我們對單元測試的看法。如果想看到多如何用Twisted程式碼撰寫單元測試的範例,你只需要看看Twisted本身的程式碼。Twisted框架附帶了一套非常大的單元測試,每個發布的版本又會加入新的測試。由於這些測試在確定能放入codebase之前的程式碼審查(code review)期間,都被Twisted專家仔細的審查過,因此它們提供了如何以正確的方式測試Twisted程式碼的優秀範例。在Part 16中,我們將使用Twisted工具將我們的詩歌伺服器轉化為一個真正的daemon。
Suggested Exercises
- 更改其中一個測試讓它失敗,然後再次執行trial來查看它的輸出。
- 閱讀線上的trial documentation。
- 為我們在這個系列建立的其他詩歌服務撰寫測試。
- 探索Twisted中的some of the tests(譯註:原文中連結已經失效,所以我也不知道原作者指的是哪個網頁)。
留言
張貼留言