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類中添加如下代碼

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類中添加如下代碼

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)行此用例L男Α!.")

? ? 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)行此用例G芑腐晾!")

????????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ì)比一下,就是把類圖中的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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市县踢,隨后出現(xiàn)的幾起案子转绷,更是在濱河造成了極大的恐慌,老刑警劉巖硼啤,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件议经,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谴返,警方通過(guò)查閱死者的電腦和手機(jī)爸业,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)亏镰,“玉大人,你說(shuō)我怎么就攤上這事拯爽∷髯ィ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵毯炮,是天一觀的道長(zhǎng)逼肯。 經(jīng)常有香客問(wèn)我,道長(zhǎng)桃煎,這世上最難降的妖魔是什么篮幢? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮为迈,結(jié)果婚禮上三椿,老公的妹妹穿的比我還像新娘。我一直安慰自己葫辐,他們只是感情好搜锰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著耿战,像睡著了一般蛋叼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天狈涮,我揣著相機(jī)與錄音狐胎,去河邊找鬼。 笑死歌馍,一個(gè)胖子當(dāng)著我的面吹牛握巢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骆姐,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼镜粤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了玻褪?” 一聲冷哼從身側(cè)響起肉渴,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎带射,沒(méi)想到半個(gè)月后同规,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窟社,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年券勺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灿里。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡关炼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匣吊,到底是詐尸還是另有隱情儒拂,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布色鸳,位于F島的核電站社痛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏命雀。R本人自食惡果不足惜蒜哀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吏砂。 院中可真熱鬧撵儿,春花似錦、人聲如沸狐血。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)氛雪。三九已至房匆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浴鸿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工井氢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岳链。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓花竞,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親掸哑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子约急,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容