python之Unittest單元測試框架
- 2019 年 10 月 10 日
- 筆記
前言
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報告,或者自己研究生成更複雜更漂亮的報告。