目錄[-]
unittest 概覽舉個(gè)例子啟動(dòng)測(cè)試Test Discovery測(cè)試環(huán)境unittest 與 doctest 一樣也是 Python 發(fā)行版自帶的包。如果你聽說過 PyUnit(OSC 開源項(xiàng)目頁面中就有 PyUnit 的頁面),那么這倆其實(shí)是同一個(gè)東西——PyUnit 是 unittest 的曾用名,因?yàn)?PyUnit 最早也是來源于 Kent 和 Erich 的 JUnit(xUnit 測(cè)試框架系列的 java 版本)
上一篇介紹的 doctest 不管是看起來還是用起來都顯得十分簡(jiǎn)單,可以與源碼寫在一起,比較適合用作驗(yàn)證性的功能測(cè)試。而本篇的 unittest 從名字上看,它是一個(gè)單元測(cè)試框架;從官方文檔的字?jǐn)?shù)上看,它的能力應(yīng)該比 doctest 強(qiáng)一些。
使用 unittest 的標(biāo)準(zhǔn)流程為:
從 unittest.TestCase 派生一個(gè)子類在類中定義各種以 “test_” 打頭的方法通過 unittest.main() 函數(shù)來啟動(dòng)測(cè)試unittest 的一個(gè)很有用的特性是 TestCase 的 setUp()
和 tearDown()
方法,它們提供了為測(cè)試進(jìn)行準(zhǔn)備和掃尾工作的功能,聽起來就像上下文管理器一樣。這種功能很適合用在測(cè)試對(duì)象需要復(fù)雜執(zhí)行環(huán)境的情況下。
這里依舊使用上篇中那個(gè)極簡(jiǎn)的例子:unnecessary_math.py
文件中有一個(gè)multiply()
函數(shù),功能與 *
操作符完全一樣。
test_um_test.py:
import unittestfrom unnecessary_math import multiplyclass TestUM(unittest.TestCase): def setUp(self): pass def test_number_3_4(self): self.assertEqual(multiply(3,4),12) def test_string_a_3(self): self.assertEqual(multiply('a',3),'aaa')if __name__ == '__main__': unittest.main()這個(gè)例子里,我們使用了 assertEqual()
方法。unittest 中還有很多類似的 assert 方法,比如 NotEqual
、Is(Not)None
、True(False)
、Is(Not)Instance
等針對(duì)變量值的校驗(yàn)方法;另外還有一些如 assertRaises()
、assertRaisesRegex()
等針對(duì)異常、警告和 log 的檢查方法;以及如assertAlmostEqual()
等一些奇怪的方法。
較詳細(xì)的 assert 方法可以參考 unittest 的文檔頁面。
啟動(dòng)測(cè)試
上例中的結(jié)尾處,我們定義了一個(gè)對(duì) unittest.main()
的調(diào)用,因此這個(gè)腳本是可以直接運(yùn)行的:
$ python test_um_test.py..--------------------------------------Ran 2 tests in 0.01sOK同樣 -v
參數(shù)是可選的,也可以在 unittest.main()
函數(shù)里直接指定:verbosity=1
。
Test Discovery
這個(gè)分段標(biāo)題我暫時(shí)沒想到好的翻譯方法,就先不翻了。
Test Discovery 的作用是:假設(shè)你的項(xiàng)目文件夾里面四散分布著很多個(gè)測(cè)試文件。當(dāng)你做回歸測(cè)試的時(shí)候,一個(gè)一個(gè)地執(zhí)行這些測(cè)試文件就太麻煩了。TestLoader.discover()
提供了一個(gè)可以在項(xiàng)目目錄下自動(dòng)搜索并運(yùn)行測(cè)試文件的功能,并可以直接從命令行調(diào)用:
$ cd PRoject_directory$ python -m unittest discoverdiscover
可用的參數(shù)有 4 個(gè)(-v -s -p -t
),其中 -s
和 -t
都與路徑有關(guān),如上例中提前 cd 到項(xiàng)目路徑的話這倆參數(shù)都可以無視;-v
喜聞樂見;-p
是 --pattern
的縮寫,可用于匹配某一類文件名。
測(cè)試環(huán)境
當(dāng)類里面定義了 setUp()
方法的時(shí)候,測(cè)試程序會(huì)在執(zhí)行每條測(cè)試項(xiàng)前先調(diào)用此方法;同樣地,在全部測(cè)試項(xiàng)執(zhí)行完畢后,tearDown()
方法也會(huì)被調(diào)用。驗(yàn)證如下:
import unittestclass simple_test(unittest.TestCase): def setUp(self): self.foo = list(range(10)) def test_1st(self): self.assertEqual(self.foo.pop(),9) def test_2nd(self): self.assertEqual(self.foo.pop(),9)if __name__ == '__main__': unittest.main()注意這里兩次測(cè)試均對(duì)同一個(gè)實(shí)例屬性 self.foo
進(jìn)行了 pop()
調(diào)用,但測(cè)試結(jié)果均為 pass,即說明,test_1st
和test_2nd
在調(diào)用前都分別調(diào)用了一次 setUp()
。
那如果我們想全程只調(diào)用一次 setUp/tearDown
該怎么辦呢?就是用 setUpClass()
和 tearDownClass()
類方法啦。注意使用這兩個(gè)方法的時(shí)候一定要用 @classmethod
裝飾器裝飾起來:
import unittestclass simple_test(unittest.TestCase): @classmethod def setUpClass(self): self.foo = list(range(10)) def test_1st(self): self.assertEqual(self.foo.pop(),9) def test_2nd(self): self.assertEqual(self.foo.pop(),8)if __name__ == '__main__': unittest.main()這個(gè)例子里我們使用了一個(gè)類級(jí)別的 setUpClass()
類方法,并修改了第二次 pop()
調(diào)用的預(yù)期返回值。運(yùn)行結(jié)果顯示依然是全部通過,即說明這次在全部測(cè)試項(xiàng)被調(diào)用前只調(diào)用了一次 setUpClass()
。
再往上一級(jí),我們希望在整個(gè)文件級(jí)別上只調(diào)用一次 setUp/tearDown
,這時(shí)候就要用 setUpModule()
和 tearDownModule()
這兩個(gè)函數(shù)了,注意是函數(shù),與 TestCase 類同級(jí):
import unittestdef setUpModule(): passclass simple_test(inittest.TestCase): ...一般 assert*()
方法如果拋出了未被捕獲的異常,那么這條測(cè)試用例會(huì)被記為 fail,測(cè)試?yán)^續(xù)進(jìn)行。但如果異常發(fā)生在 setUp()
里,就會(huì)認(rèn)為測(cè)試程序自身存在錯(cuò)誤,后面的測(cè)試用例和tearDown()
都不會(huì)再執(zhí)行。即,tearDown()
僅在setUp()
成功執(zhí)行的情況下才會(huì)執(zhí)行,并一定會(huì)被執(zhí)行。
最后,這兩個(gè)方法的默認(rèn)實(shí)現(xiàn)都是什么都不做(只有一句 pass
),所以覆蓋的時(shí)候直接寫新內(nèi)容就可以了,不必再調(diào)用父類的此方法。
新聞熱點(diǎn)
疑難解答
網(wǎng)友關(guān)注