Python必會的單元測試框架 —— unittest

http://blog.csdn.net/huilan_same/article/details/52944782html

unittest是xUnit系列框架中的一員,若是你瞭解xUnit的其餘成員,那你用unittest來應該是很輕鬆的,它們的工做方式都差很少。python

unittest核心工做原理

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

下面咱們分別來解釋這四個概念的意思,先來看一張unittest的靜態類圖(下面的類圖以及解釋均來源於網絡,原文連接):網絡

unittest類圖

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

  • 而多個測試用例集合在一塊兒,就是TestSuite,並且TestSuite也能夠嵌套TestSuite。less

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

  • 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實例

下面咱們經過一些實例來更好地認識一下unittest。

咱們先來準備一些待測方法:

mathfunc.py

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

 

簡單示例

接下來咱們爲這些方法寫一個測試:

test_mathfunc.py

# -*- coding: utf-8 -*- import unittest from mathfunc import * class TestMathFunc(unittest.TestCase): """Test mathfuc.py""" def test_add(self): """Test method add(a, b)""" self.assertEqual(3, add(1, 2)) self.assertNotEqual(3, add(2, 2)) def test_minus(self): """Test method minus(a, b)""" self.assertEqual(1, minus(3, 2)) def test_multi(self): """Test method multi(a, b)""" self.assertEqual(6, multi(2, 3)) def test_divide(self): """Test method divide(a, b)""" self.assertEqual(2, divide(6, 3)) self.assertEqual(2.5, divide(5, 2)) if __name__ == '__main__': unittest.main()

 

執行結果:

.F.. ====================================================================== FAIL: test_divide (__main__.TestMathFunc) Test method divide(a, b) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:/py/test_mathfunc.py", line 26, in test_divide self.assertEqual(2.5, divide(5, 2)) AssertionError: 2.5 != 2 ---------------------------------------------------------------------- Ran 4 tests in 0.000s FAILED (failures=1)

可以看到一共運行了4個測試,失敗了1個,而且給出了失敗緣由,2.5 != 2 也就是說咱們的divide方法是有問題的。

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

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

  2. 每一個測試方法均以 test 開頭,不然是不被unittest識別的。

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

test_add (__main__.TestMathFunc)
Test method add(a, b) ... ok test_divide (__main__.TestMathFunc) Test method divide(a, b) ... FAIL test_minus (__main__.TestMathFunc) Test method minus(a, b) ... ok test_multi (__main__.TestMathFunc) Test method multi(a, b) ... ok ====================================================================== FAIL: test_divide (__main__.TestMathFunc) Test method divide(a, b) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:/py/test_mathfunc.py", line 26, in test_divide self.assertEqual(2.5, divide(5, 2)) AssertionError: 2.5 != 2 ---------------------------------------------------------------------- Ran 4 tests in 0.002s FAILED (failures=1)

 

能夠看到,每個用例的詳細執行狀況以及用例名,用例描述均被輸出了出來(在測試方法下加代碼示例中的」「」Doc String」「」,在用例執行時,會將該字符串做爲此用例的描述,加合適的註釋可以使輸出的測試報告更加便於閱讀)

組織TestSuite

上面的代碼示例瞭如何編寫一個簡單的測試,但有兩個問題,咱們怎麼控制用例執行的順序呢?(這裏的示例中的幾個測試方法並無必定關係,但以後你寫的用例可能會有前後關係,須要先執行方法A,再執行方法B),咱們就要用到TestSuite了。咱們添加到TestSuite中的case是會按照添加的順序執行的。

問題二是咱們如今只有一個測試文件,咱們直接執行該文件便可,但若是有多個測試文件,怎麼進行組織,總不能一個個文件執行吧,答案也在TestSuite中。

下面來個例子:

在文件夾中咱們再新建一個文件,test_suite.py:

# -*- coding: utf-8 -*- import unittest from test_mathfunc import 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)

 

執行結果:

test_add (test_mathfunc.TestMathFunc)
Test method add(a, b) ... ok test_minus (test_mathfunc.TestMathFunc) Test method minus(a, b) ... ok test_divide (test_mathfunc.TestMathFunc) Test method divide(a, b) ... FAIL ====================================================================== FAIL: test_divide (test_mathfunc.TestMathFunc) Test method divide(a, b) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\py\test_mathfunc.py", line 26, in test_divide self.assertEqual(2.5, divide(5, 2)) AssertionError: 2.5 != 2 ---------------------------------------------------------------------- Ran 3 tests in 0.001s FAILED (failures=1)

 

能夠看到,執行狀況跟咱們預料的同樣:執行了三個case,而且順序是按照咱們添加進suite的順序執行的。

上面用了TestSuite的 addTests() 方法,並直接傳入了TestCase列表,咱們還能夠:

# 直接用addTest方法添加單個TestCase suite.addTest(TestMathFunc("test_multi")) # 用addTests + TestLoader # loadTestsFromName(),傳入'模塊名.TestCase名' suite.addTests(unittest.TestLoader().loadTestsFromName('test_mathfunc.TestMathFunc')) suite.addTests(unittest.TestLoader().loadTestsFromNames(['test_mathfunc.TestMathFunc'])) # loadTestsFromNames(),相似,傳入列表 # loadTestsFromTestCase(),傳入TestCase suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))

 

注意,用TestLoader的方法是沒法對case進行排序的,同時,suite中也能夠套suite。

將結果輸出到文件中

用例組織好了,但結果只能輸出到控制檯,這樣沒有辦法查看以前的執行記錄,咱們想將結果輸出到文件。很簡單,看示例:

修改test_suite.py:

# -*- coding: utf-8 -*- import unittest from test_mathfunc import TestMathFunc if __name__ == '__main__': suite = unittest.TestSuite() suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc)) with open('UnittestTextReport.txt', 'a') as f: runner = unittest.TextTestRunner(stream=f, verbosity=2) runner.run(suite)

 

執行此文件,能夠看到,在同目錄下生成了UnittestTextReport.txt,全部的執行報告均輸出到了此文件中,這下咱們便有了txt格式的測試報告了。

test fixture之setUp() tearDown()

上面整個測試基本跑了下來,但可能會遇到點特殊的狀況:若是個人測試須要在每次執行以前準備環境,或者在每次執行完以後須要進行一些清理怎麼辦?好比執行前須要鏈接數據庫,執行完成以後須要還原數據、斷開鏈接。總不能每一個測試方法中都添加準備環境、清理環境的代碼吧。

這就要涉及到咱們以前說過的test fixture了,修改test_mathfunc.py:

# -*- coding: utf-8 -*- import unittest from mathfunc import * class TestMathFunc(unittest.TestCase): """Test mathfuc.py""" def setUp(self): print "do something before test.Prepare environment." def tearDown(self): print "do something after test.Clean up." def test_add(self): """Test method add(a, b)""" print "add" self.assertEqual(3, add(1, 2)) self.assertNotEqual(3, add(2, 2)) def test_minus(self): """Test method minus(a, b)""" print "minus" self.assertEqual(1, minus(3, 2)) def test_multi(self): """Test method multi(a, b)""" print "multi" self.assertEqual(6, multi(2, 3)) def test_divide(self): """Test method divide(a, b)""" print "divide" self.assertEqual(2, divide(6, 3)) self.assertEqual(2.5, divide(5, 2))

 

咱們添加了 setUp() 和 tearDown() 兩個方法(實際上是重寫了TestCase的這兩個方法),這兩個方法在每一個測試方法執行前以及執行後執行一次,setUp用來爲測試準備環境,tearDown用來清理環境,已備以後的測試。

咱們再執行一次:

test_add (test_mathfunc.TestMathFunc)
Test method add(a, b) ... ok test_divide (test_mathfunc.TestMathFunc) Test method divide(a, b) ... FAIL test_minus (test_mathfunc.TestMathFunc) Test method minus(a, b) ... ok test_multi (test_mathfunc.TestMathFunc) Test method multi(a, b) ... ok ====================================================================== FAIL: test_divide (test_mathfunc.TestMathFunc) Test method divide(a, b) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\py\test_mathfunc.py", line 36, in test_divide self.assertEqual(2.5, divide(5, 2)) AssertionError: 2.5 != 2 ---------------------------------------------------------------------- Ran 4 tests in 0.000s FAILED (failures=1) do something before test.Prepare environment. add do something after test.Clean up. do something before test.Prepare environment. divide do something after test.Clean up. do something before test.Prepare environment. minus do something after test.Clean up. do something before test.Prepare environment. multi do something after test.Clean up.

 

能夠看到setUp和tearDown在每次執行case先後都執行了一次。

若是想要在全部case執行以前準備一次環境,並在全部case執行結束以後再清理環境,咱們能夠用 setUpClass() 與 tearDownClass():

...

class TestMathFunc(unittest.TestCase): """Test mathfuc.py""" @classmethod def setUpClass(cls): print "This setUpClass() method only called once." @classmethod def tearDownClass(cls): print "This tearDownClass() method only called once too." ...

 

執行結果以下:

... This setUpClass() method only called once. do something before test.Prepare environment. add do something after test.Clean up. ... do something before test.Prepare environment. multi do something after test.Clean up. This tearDownClass() method only called once too.

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

跳過某個case

若是咱們臨時想要跳過某個case不執行怎麼辦?unittest也提供了幾種方法:

  1. skip裝飾器
...

class TestMathFunc(unittest.TestCase): """Test mathfuc.py""" ... @unittest.skip("I don't want to run this case.") def test_divide(self): """Test method divide(a, b)""" print "divide" self.assertEqual(2, divide(6, 3)) self.assertEqual(2.5, divide(5, 2))

執行:

... test_add (test_mathfunc.TestMathFunc) Test method add(a, b) ... ok test_divide (test_mathfunc.TestMathFunc) Test method divide(a, b) ... skipped "I don't want to run this case." test_minus (test_mathfunc.TestMathFunc) Test method minus(a, b) ... ok test_multi (test_mathfunc.TestMathFunc) Test method multi(a, b) ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.000s OK (skipped=1)

能夠看到總的test數量仍是4個,但divide()方法被skip了。

skip裝飾器一共有三個 unittest.skip(reason)unittest.skipIf(condition, reason)unittest.skipUnless(condition, reason),skip無條件跳過,skipIf當condition爲True時跳過,skipUnless當condition爲False時跳過。

  1. TestCase.skipTest()方法
...

class TestMathFunc(unittest.TestCase): """Test mathfuc.py""" ... def test_divide(self): """Test method divide(a, b)""" self.skipTest('Do not run this.') print "divide" self.assertEqual(2, divide(6, 3)) self.assertEqual(2.5, divide(5, 2))

 

輸出:

... test_add (test_mathfunc.TestMathFunc) Test method add(a, b) ... ok test_divide (test_mathfunc.TestMathFunc) Test method divide(a, b) ... skipped 'Do not run this.' test_minus (test_mathfunc.TestMathFunc) Test method minus(a, b) ... ok test_multi (test_mathfunc.TestMathFunc) Test method multi(a, b) ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK (skipped=1)

 

效果跟上面的裝飾器同樣,跳過了divide方法。

進階——用HTMLTestRunner輸出漂亮的HTML報告

咱們可以輸出txt格式的文本執行報告了,可是文本報告太過簡陋,是否是想要更加高大上的HTML報告?但unittest本身可沒有帶HTML報告,咱們只能求助於外部的庫了。

HTMLTestRunner是一個第三方的unittest HTML報告庫,首先咱們下載HTMLTestRunner.py,並放到當前目錄下,或者你的’C:\Python27\Lib’下,就能夠導入運行了。

下載地址:

官方原版:http://tungwaiyip.info/software/HTMLTestRunner.html

灰藍修改版:HTMLTestRunner.py(已調整格式,中文顯示)

修改咱們的 test_suite.py

# -*- coding: utf-8 -*- import unittest from test_mathfunc import TestMathFunc from HTMLTestRunner import HTMLTestRunner if __name__ == '__main__': suite = unittest.TestSuite() suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc)) with open('HTMLReport.html', 'w') as f: runner = HTMLTestRunner(stream=f, title='MathFunc Test Report', description='generated by HTMLTestRunner.', verbosity=2 ) runner.run(suite)

 

這樣,在執行時,在控制檯咱們可以看到執行狀況,以下:

ok test_add (test_mathfunc.TestMathFunc) F test_divide (test_mathfunc.TestMathFunc) ok test_minus (test_mathfunc.TestMathFunc) ok test_multi (test_mathfunc.TestMathFunc) Time Elapsed: 0:00:00.001000

 

 

而且輸出了HTML測試報告,HTMLReport.html,如圖:

html report

這下漂亮的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. 一個class繼承unittest.TestCase便是一個TestCase,其中以 test 開頭的方法在load時被加載爲一個真正的TestCase。
  4. verbosity參數能夠控制執行結果的輸出,0 是簡單報告、1 是通常報告、2 是詳細報告。
  5. 能夠經過addTest和addTests向suite中添加case或suite,能夠用TestLoader的loadTestsFrom__()方法。
  6. 用 setUp()tearDown()setUpClass()以及 tearDownClass()能夠在用例執行前佈置環境,以及在用例執行後清理環境
  7. 咱們能夠經過skip,skipIf,skipUnless裝飾器跳過某個case,或者用TestCase.skipTest方法。
  8. 參數中加stream,能夠將報告輸出到文件:能夠用TextTestRunner輸出txt報告,以及能夠用HTMLTestRunner輸出html報告。

咱們這裏沒有討論命令行的使用以及模塊級別的fixture,感興趣的同窗能夠自行搜索資料學習。

相關文章
相關標籤/搜索