python之Unittest單元測試框架

參考來源:Mushishi_xu博主huilan_same同行的分享

前言

unittest是一個python版本的junit,junit是java中的單元測試框架,對java的單元測試,有一句話很貼切:Keep the bar green,相信使用eclipse寫過java單元測試的都心領神會。unittest實現了很多junit中的概念,作為標準python中的一個模塊,是其它框架和工具的基礎,參考資料是它的官方文檔:http://docs.python.org/2.7/library/unittest.html和源代碼,比如我們非常熟悉的test case, test suite等,總之,原理都是相通的,只是用不同的語言表達出來。

一、unittest工作原理

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

工作原理

一個TestCase的實例就是一個測試用例。什么是測試用例呢?就是一個完整的測試流程,包括測試前準備環境的搭建(setUp),執行測試代碼(run),以及測試后環境的還原(tearDown)。元測試(unit test)的本質也就在這里,一個測試用例是一個完整的測試單元,通過運行這個測試單元,可以對某一個問題進行驗證。

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

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

TextTestRunner是來執行測試用例的,其中的run(test)會執行TestSuite/TestCase中的run(result)方法。測試的結果會保存到TextTestResult實例中,包括運行了多少測試用例,成功了多少,失敗了多少等信息。

而對一個測試用例環境的搭建和銷毀,是一個fixture。

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

到這里整個流程就清楚了:

寫好TestCase,然后由TestLoader加載TestCase到TestSuite,然后由TextTestRunner來運行TestSuite,運行的結果保存在TextTestResult中,我們通過命令行或者unittest.main()執行時,main會調用TextTestRunner中的run來執行,或者我們可以直接通過TextTestRunner來執行用例。這里加個說明,在Runner執行時,默認將執行結果輸出到控制臺,我們可以設置其輸出到文件,在文件中查看結果(通過HTMLTestRunner將結果輸出到HTML中,生成漂亮的報告,它跟TextTestRunner是一樣的)。

二、unittest實例-test case

1.準備測試方法

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.為測試方法寫測試用例

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.查看運行結果

success


這就是一個簡單的測試,有幾點需要說明的:

>在第一行給出了每一個用例執行的結果的標識,成功是?.,失敗是?F,出錯是?E,跳過是?S。從上面也可以看出,測試的執行跟方法的順序沒有關系,test_divide寫在了第4個,但是卻是第2個執行的。

>每個測試方法均以?test?開頭,否則是不被unittest識別的。

>在unittest.main()中加?verbosity?參數可以控制輸出的錯誤報告的詳細程度,默認是?1,如果設為?0,則不輸出每一用例的執行結果,即沒有上面的結果中的第1行;如果設為?2,則輸出詳細的執行結果,如下:

詳細結果輸出

三、組織TestSuite

上面的代碼示例了如何編寫一個簡單的測試,但有兩個問題,我們怎么控制用例執行的順序呢?(這里的示例中的幾個測試方法并沒有一定關系,但之后你寫的用例可能會有先后關系,需要先執行方法A,再執行方法B),我們就要用到TestSuite了。我們添加到TestSuite中的case是會按照添加的順序執行的。

問題二是我們現在只有一個測試文件,我們直接執行該文件即可,但如果有多個測試文件,怎么進行組織,總不能一個個文件執行吧,答案也在TestSuite中。

請看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)

運行結果:

運行結果

可以看到,執行情況跟我們預料的一樣:執行了三個case,并且順序是按照我們添加進suite的順序執行的。那么,如何將結果輸出到文件呢,請看下面操作方法,修改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.假如我的測試需要在每次執行之前準備環境,或者在每次執行完之后需要進行一些清理怎么辦?比如執行前需要連接數據庫,執行完成之后需要還原數據、斷開連接。總不能每個測試方法中都添加準備環境、清理環境的代碼吧,這時,就輪到test fixture之setUp() tearDown()大展身手的時候了,請看如下代碼:在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()?兩個方法(其實是重寫了TestCase的這兩個方法),這兩個方法在每個測試方法執行前以及執行后執行一次,setUp用來為測試準備環境,tearDown用來清理環境,已備之后的測試。

運行結果如下:

運行結果

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

2.如果想要在所有case執行之前準備一次環境,并在所有case執行結束之后再清理環境,我們可以用?setUpClass()?與?tearDownClass():請看如下代碼:在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.")

運行結果:

運行結果

可以看到setUpClass以及tearDownClass均只執行了一次。

3.運行測試用例時不想全部運行,或者說想跳過某一個用例,那么這時skip裝飾器就起作用了。

skip裝飾器一共有三個?unittest.skip(reason)、unittest.skipIf(condition, reason)、unittest.skipUnless(condition, reason),skip無條件跳過,skipIf當condition為True時跳過,skipUnless當condition為False時跳過。以下分兩種情況進行解析。

>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"我不想運行此用例!!.")

????def test_add(self):

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

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

運行結果:

運行結果

>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"我不想運行此用例!!.")

? ? def test_add(self):

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

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

????def test_minus(self):

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

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

運行結果:

運行結果

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

五、用HTMLTestRunner輸出HTML報告

HTMLTestRunner是一個第三方的unittest HTML報告庫,首先我們下載HTMLTestRunner.py,并放到當前目錄下,或者你的’python’安裝目錄下,就可以導入運行了。

下載地址:HTMLTestRunner模板? (下載的模板只支持python2.x,要想在python3.x中使用可以看下這個: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格式報告

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

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

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? title=u'軟件測試報告 Test Report',

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? description=u'用例執行情況',

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

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

運行結果:

運行結果1
運行結果2

這下漂亮的HTML報告也有了。其實你能發現,HTMLTestRunner的執行方法跟TextTestRunner很相似,你可以跟我上面的示例對比一下,就是把類圖中的runner換成了HTMLTestRunner,并將TestResult用HTML的形式展現出來,如果你研究夠深,可以寫自己的runner,生成更復雜更漂亮的報告。

單元測試小結:

1.unittest是Python自帶的單元測試框架,我們可以用其來作為我們自動化測試框架的用例組織執行框架。

2.unittest的流程:寫好TestCase,然后由TestLoader加載TestCase到TestSuite,然后由TextTestRunner來運行TestSuite,運行的結果保存在TextTestResult中,我們通過命令行或者unittest.main()執行時,main會調用TextTestRunner中的run來執行,或者我們可以直接通過TextTestRunner來執行用例。

3.項目命名不可用小寫‘test’開頭(大寫無影響),否則會出錯,一個class繼承unittest.TestCase即是一個TestCase,其中以?test?開頭的方法在load時被加載為一個真正的TestCase。

4.verbosity參數可以控制執行結果的輸出,0?是簡單報告、1?是一般報告、2?是詳細報告。

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

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

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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容