在我們探索Twisted的過(guò)程中寫了很多代碼, 但目前我們卻忽略了一些重要的東西 —— 測(cè)試. 你也許會(huì)疑問(wèn)怎樣用像 unittest 這樣Python自帶的同步框架測(cè)試異步代碼. 答案是你不能. 正如我們已經(jīng)發(fā)現(xiàn)的,同步代碼和異步代碼是不能混合的, 至少不容易.
幸運(yùn)地是, Twisted包含自己的測(cè)試框架, 叫 trial, 它支持測(cè)試異步代碼(當(dāng)然你也可以用它測(cè)試同步代碼).
我們假設(shè)你已經(jīng)熟悉了 unittest和相似的測(cè)試框架的原理, 它允許你通過(guò)定義類創(chuàng)建測(cè)試. 這個(gè)類通常是繼承一個(gè)一個(gè)特殊的父類(通常叫"TestCase"), 類中的方法以"test"開(kāi)頭并被視作一個(gè)測(cè)試. 框架負(fù)責(zé)發(fā)現(xiàn)所有的測(cè)試, 一個(gè)接一個(gè)地運(yùn)行它們, 并伴有可選項(xiàng) setUp 和 tearDown 步驟, 然后報(bào)告測(cè)試結(jié)果.
你可以在 tests/test_poetry.py 中找到一些關(guān)于測(cè)試的例子.為了確保我們所有的例子是自包含的(以便你不用擔(dān)心PYTHONPAYH設(shè)置),我們將所有需要的代碼拷貝到測(cè)試模塊中.當(dāng)然正常情況,你只需導(dǎo)入需要測(cè)試的模塊.
通過(guò)使用客戶端從測(cè)試服務(wù)器抓取一首詩(shī), 這個(gè)例子既測(cè)試了詩(shī)歌客戶端又測(cè)試了服務(wù)器. 為了提供一個(gè)可供測(cè)試的詩(shī)歌服務(wù)器, 我們?cè)跍y(cè)試案例中實(shí)現(xiàn) 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
這個(gè) setUp 方法用一首測(cè)試詩(shī)建立詩(shī)歌服務(wù)器,然后監(jiān)聽(tīng)一個(gè)隨機(jī)開(kāi)放端口.我們保存了端口號(hào),以便實(shí)際測(cè)試需要時(shí)可以利用.當(dāng)然測(cè)試結(jié)束時(shí)我們會(huì)用 tearDown 清除測(cè)試服務(wù)器:
def tearDown(self):
port, self.port = self.port, None
return port.stopListening()
test_client把我們帶到了第一個(gè)測(cè)試, 用 get_poetry 從測(cè)試服務(wù)器獲取詩(shī)歌并且驗(yàn)證這就是我們所期望的詩(shī)歌:
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
注意我們的測(cè)試函數(shù)返回一個(gè) deferred.在 trial 中,每個(gè)測(cè)試方法都以回調(diào)的方式運(yùn)行.這意味著 reactor 正在運(yùn)行并且我們可以以測(cè)試的一部分執(zhí)行異步操作.我們僅僅需要讓框架知道測(cè)試是異步的,這可以通過(guò)采用常規(guī)的Twisted方式 —— 返回 deferred 來(lái)實(shí)現(xiàn).
trial 框架在調(diào)用 tearDown 方法之前將等待直到 deferred 激發(fā),并且當(dāng) deferred 失敗時(shí)將使測(cè)試失敗(如,最后一個(gè)callback/errback對(duì)失敗).如果我們的 deferred 太長(zhǎng)時(shí)間才被激活調(diào)用(默認(rèn)2分鐘), 它同樣會(huì)使測(cè)試失敗.這意味著如果測(cè)試完成,我們知道 deferred 激發(fā)了, 那我們的回調(diào)就會(huì)激發(fā)并且運(yùn)行 assertEquals 測(cè)試方法.
我們的第二個(gè)測(cè)試, test_failure, 證實(shí) get_poetry 如果不能連接到服務(wù)器會(huì)以適當(dāng)?shù)姆绞绞?
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', -1)
return self.assertFailure(d, ConnectionRefusedError)
這里我們打算連接到一個(gè)無(wú)效端口,之后使用trial提供的 assertFailure 方法.這個(gè)方法類似于熟悉的 assertRaises 方法,但是assertFailure是針對(duì)異步代碼的.它返回一個(gè) deferred,如果這個(gè) deferred 失敗則返回成功,否則返回失敗.
你可以用trial腳本自己運(yùn)行這些測(cè)試,如下:
trial tests/test_poetry.py
你將看到每個(gè)測(cè)試案例的輸出, OK表示測(cè)試通過(guò)了.
由于當(dāng)談到基本API時(shí),trial與unittest十分相似,所以開(kāi)始寫測(cè)試十分容易.如果你的測(cè)試使用異步代碼,僅僅返回 deferred 就可以了,trial將負(fù)責(zé)其余的事情.你也可以從 setUp 或 tearDown 方法返回一個(gè) deferred,如果它們也需要異步.
任何來(lái)自測(cè)試的日志消息將被收集到當(dāng)前文件夾下的一個(gè)文件中,即"_trial_temp", trial會(huì)自動(dòng)創(chuàng)建它. 除了打印到屏幕的錯(cuò)誤,日志是調(diào)試失敗測(cè)試的實(shí)用入口.
圖33顯示了一個(gè)正在進(jìn)行中的假想測(cè)試:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p15_test-1.png" alt="" />
如果你之前使用過(guò)類似的框架,這是一個(gè)熟悉的模型,除了所有測(cè)試相關(guān)的方法可能返回 deferreds.
trial框架是一個(gè)關(guān)于如何"異步運(yùn)作"的很好例子,包括級(jí)聯(lián)在整個(gè)程序中的變化.為了使一個(gè)測(cè)試(或任何函數(shù),方法)是異步的,它必須:
deferred.但這意味著無(wú)論什么調(diào)用,那個(gè)函數(shù)必須愿意接收一個(gè) deferred,并且非阻塞(如此又好像返回了一個(gè) deferred).如此這般一層又一層.這樣就呼喚出現(xiàn)trial一樣的框架,可以處理返回 deferreds 的異步測(cè)試.
這就是關(guān)于單元測(cè)試的內(nèi)容.如果你想了解更多關(guān)于如何為Twisted代碼寫單元測(cè)試的例子,你只需要看看Twisted代碼本身.Twisted框架自帶了一套非常龐大的單元測(cè)試,而且每個(gè)新的發(fā)布又會(huì)加入很多.由于這些測(cè)試在被接受入代碼庫(kù)之前,經(jīng)過(guò)嚴(yán)格的代碼評(píng)論以及Twisted專家們的仔細(xì)審查,故而它們是告訴你如何以正確方式測(cè)試Twisted代碼的極好例子.
在第十六節(jié)中,我們將使用Twisted工具將詩(shī)歌服務(wù)器轉(zhuǎn)化為一個(gè)真正的守護(hù)進(jìn)程.
trial 看看輸出結(jié)果.本部分原作參見(jiàn): dave @ http://krondo.com/blog/?p=2273
本部分翻譯內(nèi)容參見(jiàn)luocheng @ https://github.com/luocheng/twisted-intro-cn/blob/master/p15.rst