python之Unittest單元測(cè)試框架

參考來(lái)源:Mushishi_xu博主huilan_same同行的分享

前言

unittest是一個(gè)python版本的junit,junit是java中的單元測(cè)試框架,對(duì)java的單元測(cè)試,有一句話很貼切:Keep the bar green,相信使用eclipse寫(xiě)過(guò)java單元測(cè)試的都心領(lǐng)神會(huì)。unittest實(shí)現(xiàn)了很多junit中的概念,作為標(biāo)準(zhǔn)python中的一個(gè)模塊,是其它框架和工具的基礎(chǔ),參考資料是它的官方文檔:http://docs.python.org/2.7/library/unittest.html和源代碼,比如我們非常熟悉的test case, test suite等,總之,原理都是相通的,只是用不同的語(yǔ)言表達(dá)出來(lái)。

一、unittest工作原理

unittest中最核心的四個(gè)概念是:test case, test suite, test runner, test fixture。

工作原理

一個(gè)TestCase的實(shí)例就是一個(gè)測(cè)試用例。什么是測(cè)試用例呢?就是一個(gè)完整的測(cè)試流程,包括測(cè)試前準(zhǔn)備環(huán)境的搭建(setUp),執(zhí)行測(cè)試代碼(run),以及測(cè)試后環(huán)境的還原(tearDown)。元測(cè)試(unit test)的本質(zhì)也就在這里,一個(gè)測(cè)試用例是一個(gè)完整的測(cè)試單元,通過(guò)運(yùn)行這個(gè)測(cè)試單元,可以對(duì)某一個(gè)問(wèn)題進(jìn)行驗(yàn)證。

而多個(gè)測(cè)試用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。

TestLoader是用來(lái)加載TestCase到TestSuite中的,其中有幾個(gè)loadTestsFrom__()方法,就是從各個(gè)地方尋找TestCase,創(chuàng)建它們的實(shí)例,然后add到TestSuite中,再返回一個(gè)TestSuite實(shí)例。

TextTestRunner是來(lái)執(zhí)行測(cè)試用例的,其中的run(test)會(huì)執(zhí)行TestSuite/TestCase中的run(result)方法。測(cè)試的結(jié)果會(huì)保存到TextTestResult實(shí)例中,包括運(yùn)行了多少測(cè)試用例,成功了多少,失敗了多少等信息。

而對(duì)一個(gè)測(cè)試用例環(huán)境的搭建和銷(xiāo)毀,是一個(gè)fixture。

一個(gè)class繼承了unittest.TestCase,便是一個(gè)測(cè)試用例,但如果其中有多個(gè)以?test?開(kāi)頭的方法,那么每有一個(gè)這樣的方法,在load的時(shí)候便會(huì)生成一個(gè)TestCase實(shí)例,如:一個(gè)class中有四個(gè)test_xxx方法,最后在load到suite中時(shí)也有四個(gè)測(cè)試用例。

到這里整個(gè)流程就清楚了:

寫(xiě)好TestCase,然后由TestLoader加載TestCase到TestSuite,然后由TextTestRunner來(lái)運(yùn)行TestSuite,運(yùn)行的結(jié)果保存在TextTestResult中,我們通過(guò)命令行或者unittest.main()執(zhí)行時(shí),main會(huì)調(diào)用TextTestRunner中的run來(lái)執(zhí)行,或者我們可以直接通過(guò)TextTestRunner來(lái)執(zhí)行用例。這里加個(gè)說(shuō)明,在Runner執(zhí)行時(shí),默認(rèn)將執(zhí)行結(jié)果輸出到控制臺(tái),我們可以設(shè)置其輸出到文件,在文件中查看結(jié)果(通過(guò)HTMLTestRunner將結(jié)果輸出到HTML中,生成漂亮的報(bào)告,它跟TextTestRunner是一樣的)。

二、unittest實(shí)例-test case

1.準(zhǔn)備測(cè)試方法

mathfunc.py

#coding:utf-8

import math

def add(a,b):

????return a + b

def minus(a,b):

????return a - b

def multi(a,b):

????return a * b

def divide(a,b):

????return a / b

2.為測(cè)試方法寫(xiě)測(cè)試用例

run_mathfunc.py

#coding:utf-8

import unittest

from python_ceshikuangjia.mathfuncimport *

class TestMathFunc(unittest.TestCase):

????def test_add(self):

????????self.assertEqual(5,add(3.2))

????????self.assertNotEqual(3,add(2,2))

????def test_minus(self):

????????self.assertEqual(2,minus(4,2))

????def test_multi(self):

????????self.assertEqual(6,multi(2,3))

????def test_divide(self):

????????self.assertEqual(2,divide(6,3))

????????self.assertEqual(2.5,divide(5,2))

if __name__ =='__main__':

????unittest.main()

3.查看運(yùn)行結(jié)果

success


這就是一個(gè)簡(jiǎn)單的測(cè)試,有幾點(diǎn)需要說(shuō)明的:

>在第一行給出了每一個(gè)用例執(zhí)行的結(jié)果的標(biāo)識(shí),成功是?.,失敗是?F,出錯(cuò)是?E,跳過(guò)是?S。從上面也可以看出,測(cè)試的執(zhí)行跟方法的順序沒(méi)有關(guān)系,test_divide寫(xiě)在了第4個(gè),但是卻是第2個(gè)執(zhí)行的。

>每個(gè)測(cè)試方法均以?test?開(kāi)頭,否則是不被unittest識(shí)別的。

>在unittest.main()中加?verbosity?參數(shù)可以控制輸出的錯(cuò)誤報(bào)告的詳細(xì)程度,默認(rèn)是?1,如果設(shè)為?0,則不輸出每一用例的執(zhí)行結(jié)果,即沒(méi)有上面的結(jié)果中的第1行;如果設(shè)為?2,則輸出詳細(xì)的執(zhí)行結(jié)果,如下:

詳細(xì)結(jié)果輸出

三、組織TestSuite

上面的代碼示例了如何編寫(xiě)一個(gè)簡(jiǎn)單的測(cè)試,但有兩個(gè)問(wèn)題,我們?cè)趺纯刂朴美龍?zhí)行的順序呢?(這里的示例中的幾個(gè)測(cè)試方法并沒(méi)有一定關(guān)系,但之后你寫(xiě)的用例可能會(huì)有先后關(guān)系,需要先執(zhí)行方法A,再執(zhí)行方法B),我們就要用到TestSuite了。我們添加到TestSuite中的case是會(huì)按照添加的順序執(zhí)行的。

問(wèn)題二是我們現(xiàn)在只有一個(gè)測(cè)試文件,我們直接執(zhí)行該文件即可,但如果有多個(gè)測(cè)試文件,怎么進(jìn)行組織,總不能一個(gè)個(gè)文件執(zhí)行吧,答案也在TestSuite中。

請(qǐng)看run_suite.py

#coding:utf-8

import unittest

from python_ceshikuangjia.run_mathfuncimport TestMathFunc

if __name__ =='__main__':

????suite = unittest.TestSuite()

????tests = [TestMathFunc("test_add"),TestMathFunc("test_minus"),TestMathFunc("test_divide")]

????suite.addTests(tests)

????runner = unittest.TextTestRunner(verbosity=2)

????runner.run(suite)

運(yùn)行結(jié)果:

運(yùn)行結(jié)果

可以看到,執(zhí)行情況跟我們預(yù)料的一樣:執(zhí)行了三個(gè)case,并且順序是按照我們添加進(jìn)suite的順序執(zhí)行的。那么,如何將結(jié)果輸出到文件呢,請(qǐng)看下面操作方法,修改run_suite.py代碼,如下:

#coding:utf-8

import unittest

from python_ceshikuangjia.run_mathfuncimport TestMathFunc

if __name__ =='__main__':

????suite = unittest.TestSuite()

????tests = [TestMathFunc("test_add"),TestMathFunc("test_minus"),TestMathFunc("test_divide")]

????suite.addTests(tests)

????with open('D:/work/python_ceshikuangjia/run_suite_log.txt','a')as f:

????????runner = unittest.TextTestRunner(stream=f,verbosity=2)

????????runner.run(suite)

運(yùn)行結(jié)果

四、test fixture之setUp() tearDown()

1.假如我的測(cè)試需要在每次執(zhí)行之前準(zhǔn)備環(huán)境,或者在每次執(zhí)行完之后需要進(jìn)行一些清理怎么辦?比如執(zhí)行前需要連接數(shù)據(jù)庫(kù),執(zhí)行完成之后需要還原數(shù)據(jù)、斷開(kāi)連接??偛荒苊總€(gè)測(cè)試方法中都添加準(zhǔn)備環(huán)境、清理環(huán)境的代碼吧,這時(shí),就輪到test fixture之setUp() tearDown()大展身手的時(shí)候了,請(qǐng)看如下代碼:在run_mathfunc.py下的class TestMathFunc類(lèi)中添加如下代碼

class TestMathFunc(unittest.TestCase):

????def setUp(self):

????????print("do something before test.Prepare environment.")

????def tearDown(self):

????????print("do something after test.Clean up.")

?setUp()?和?tearDown()?兩個(gè)方法(其實(shí)是重寫(xiě)了TestCase的這兩個(gè)方法),這兩個(gè)方法在每個(gè)測(cè)試方法執(zhí)行前以及執(zhí)行后執(zhí)行一次,setUp用來(lái)為測(cè)試準(zhǔn)備環(huán)境,tearDown用來(lái)清理環(huán)境,已備之后的測(cè)試。

運(yùn)行結(jié)果如下:

運(yùn)行結(jié)果

可以看到setUp和tearDown在每次執(zhí)行case前后都執(zhí)行了一次。

2.如果想要在所有case執(zhí)行之前準(zhǔn)備一次環(huán)境,并在所有case執(zhí)行結(jié)束之后再清理環(huán)境,我們可以用?setUpClass()?與?tearDownClass():請(qǐng)看如下代碼:在run_mathfunc.py下的class?TestMathFunc類(lèi)中添加如下代碼

class TestMathFunc(unittest.TestCase):

????@classmethod

? ? def setUpClass(cls):

????????print("This setUpClass() method only called once.")

????@classmethod

? ? def tearDownClass(cls):

????????print("This tearDownClass() method only called once too.")

運(yùn)行結(jié)果:

運(yùn)行結(jié)果

可以看到setUpClass以及tearDownClass均只執(zhí)行了一次。

3.運(yùn)行測(cè)試用例時(shí)不想全部運(yùn)行,或者說(shuō)想跳過(guò)某一個(gè)用例,那么這時(shí)skip裝飾器就起作用了。

skip裝飾器一共有三個(gè)?unittest.skip(reason)、unittest.skipIf(condition, reason)、unittest.skipUnless(condition, reason),skip無(wú)條件跳過(guò),skipIf當(dāng)condition為T(mén)rue時(shí)跳過(guò),skipUnless當(dāng)condition為False時(shí)跳過(guò)。以下分兩種情況進(jìn)行解析。

>skip裝飾器

class TestMathFunc(unittest.TestCase):

????@classmethod

? ? def setUpClass(cls):

????????print("This setUpClass() method only called once.")

????@classmethod

? ? def tearDownClass(cls):

????????print("This tearDownClass() method only called once too.")

? ? @unittest.skip(u"我不想運(yùn)行此用例!!.")

????def test_add(self):

????????self.assertEqual(5,add(3,2))

????????self.assertNotEqual(3,add(2,2))

運(yùn)行結(jié)果:

運(yùn)行結(jié)果

>TestCase.skipTest()方法

class TestMathFunc(unittest.TestCase):

????@classmethod

? ? def setUpClass(cls):

????????print("This setUpClass() method only called once.")

????@classmethod

? ? def tearDownClass(cls):

????????print("This tearDownClass() method only called once too.")

#? ? @unittest.skip(u"我不想運(yùn)行此用例!!.")

? ? def test_add(self):

????????self.assertEqual(5,add(3,2))

????????self.assertNotEqual(3,add(2,2))

????def test_minus(self):

????????self.skipTest(u"我不想運(yùn)行此用例!!")

????????self.assertEqual(2,minus(4,2))

運(yùn)行結(jié)果:

運(yùn)行結(jié)果

通過(guò)以上兩種不同方式,可以看到總的test數(shù)量還是3個(gè),但add()和minus()方法都被skip了。

五、用HTMLTestRunner輸出HTML報(bào)告

HTMLTestRunner是一個(gè)第三方的unittest HTML報(bào)告庫(kù),首先我們下載HTMLTestRunner.py,并放到當(dāng)前目錄下,或者你的’python’安裝目錄下,就可以導(dǎo)入運(yùn)行了。

下載地址:HTMLTestRunner模板? (下載的模板只支持python2.x,要想在python3.x中使用可以看下這個(gè):HTMLTestRunner修改成Python3版本

修改我們的 run_suite.py:

#coding:utf-8

import unittest

import HTMLTestRunner

from python_ceshikuangjia.run_mathfuncimport TestMathFunc

if __name__ =='__main__':

????suite = unittest.TestSuite()

????tests = [TestMathFunc("test_add"),TestMathFunc("test_minus"),TestMathFunc("test_divide"),TestMathFunc('test_multi')]

????suite.addTests(tests)

# with open('D:/work/python_ceshikuangjia/run_suite_log.txt','a') as f:

#? ? runner = unittest.TextTestRunner(stream=f,verbosity=2)

#? ? runner.run(suite)

#輸出HTML格式報(bào)告

? ? with open('D:/work/python_ceshikuangjia/HTMLReport.html','wb')as f:

????????runner = HTMLTestRunner.HTMLTestRunner(stream=f,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? title=u'軟件測(cè)試報(bào)告 Test Report',

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? description=u'用例執(zhí)行情況',

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? verbosity =2)

????????runner.run(suite)

運(yùn)行結(jié)果:

運(yùn)行結(jié)果1
運(yùn)行結(jié)果2

這下漂亮的HTML報(bào)告也有了。其實(shí)你能發(fā)現(xiàn),HTMLTestRunner的執(zhí)行方法跟TextTestRunner很相似,你可以跟我上面的示例對(duì)比一下,就是把類(lèi)圖中的runner換成了HTMLTestRunner,并將TestResult用HTML的形式展現(xiàn)出來(lái),如果你研究夠深,可以寫(xiě)自己的runner,生成更復(fù)雜更漂亮的報(bào)告。

單元測(cè)試小結(jié):

1.unittest是Python自帶的單元測(cè)試框架,我們可以用其來(lái)作為我們自動(dòng)化測(cè)試框架的用例組織執(zhí)行框架。

2.unittest的流程:寫(xiě)好TestCase,然后由TestLoader加載TestCase到TestSuite,然后由TextTestRunner來(lái)運(yùn)行TestSuite,運(yùn)行的結(jié)果保存在TextTestResult中,我們通過(guò)命令行或者unittest.main()執(zhí)行時(shí),main會(huì)調(diào)用TextTestRunner中的run來(lái)執(zhí)行,或者我們可以直接通過(guò)TextTestRunner來(lái)執(zhí)行用例。

3.項(xiàng)目命名不可用小寫(xiě)‘test’開(kāi)頭(大寫(xiě)無(wú)影響),否則會(huì)出錯(cuò),一個(gè)class繼承unittest.TestCase即是一個(gè)TestCase,其中以?test?開(kāi)頭的方法在load時(shí)被加載為一個(gè)真正的TestCase。

4.verbosity參數(shù)可以控制執(zhí)行結(jié)果的輸出,0?是簡(jiǎn)單報(bào)告、1?是一般報(bào)告、2?是詳細(xì)報(bào)告。

5.可以用?setUp()、tearDown()、setUpClass()以及?tearDownClass()可以在用例執(zhí)行前布置環(huán)境,以及在用例執(zhí)行后清理環(huán)境

6.我們可以通過(guò)skip,skipIf,skipUnless裝飾器跳過(guò)某個(gè)case,或者用TestCase.skipTest方法。

7.參數(shù)中加stream,可以將報(bào)告輸出到文件:可以用TextTestRunner輸出txt報(bào)告,以及可以用HTMLTestRunner輸出html報(bào)告,或者自己研究生成更復(fù)雜更漂亮的報(bào)告。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容