參考來(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é)果
這就是一個(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é)果,如下:
三想括、組織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é)果:
可以看到精置,執(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)
四蝶押、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é)果如下:
可以看到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é)果:
可以看到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é)果:
>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é)果:
通過(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é)果:
這下漂亮的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)告怕吴。