week12html
1 核心工做原理python
unittest中最核心的四個概念是:test case, test suite, test runner, test fixture。數據庫
其餘與unittest相似的單元測試庫:nose/pytest框架
命令行less
從命令行中能夠運行單元測試的模塊,類,甚至單獨的測試方法 。ide
python -m unittest test_module1 test_module2 #同時測試多個module python -m unittest test_module.TestClass python -m unittest test_module.TestClass.test_method
顯示更詳細的測試結果的說明使用 -v
flag:單元測試
python -m unittest -v test_module
查看全部的命令行選項使用命令:測試
python -m unittest -h
2 測試流程ui
說明:this
a:經過命令行或者unittest.main()執行時,main會調用TextTestRunner中的run來執行,或者能夠直接經過TextTestRunner來執行用例
b:Runner執行時,默認將結果輸出到控制檯,咱們能夠設置其輸出到文件,在文件中查看結果,也能夠經過HTMLTestRunner將結果輸出到HTML
3 unittest實例
3.1 準備待測方法
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
3.2 爲以上方法寫測試用例
test_mathfunc.py
import unittest from mathfunc import * class TestMathFunc(unittest.TestCase): """Test mathfunc.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, divide(5, 2)) if __name__ == '__main__': unittest.main()
執行結果:
這裏有個插曲如何從unittest run變成普通的run?
.F..
======================================================================
FAIL: test_divide (__main__.TestMathFunc)
Test method divide(a, b)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:/Users/zhouxy/PycharmProjects/untitled/utest/test_mathfunc.py", line 25, in test_divide
self.assertEqual(2, divide(5, 2))
AssertionError: 2 != 2.5
----------------------------------------------------------------------
Ran 4 tests in 0.004s
FAILED (failures=1)
這就是一個簡單的測試,有幾點須要說明的:
.
,失敗是 F
,出錯是 E
,跳過是 S
。從上面也能夠看出,測試的執行跟方法的順序沒有關係,test_divide寫在了第4個,可是倒是第2個執行的。test
開頭,不然是不被unittest識別的。verbosity
參數能夠控制輸出的錯誤報告的詳細程度,默認是 1
,若是設爲 0
,則不輸出每一用例的執行結果,即沒有上面的結果中的第1行;若是設爲 2
,則輸出詳細的執行結果。if __name__ == '__main__': unittest.main(verbosity=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 "C:/Users/zhouxy/PycharmProjects/untitled/utest/test_mathfunc.py", line 25, in test_divide self.assertEqual(2, divide(5, 2)) AssertionError: 2 != 2.5 ---------------------------------------------------------------------- Ran 4 tests in 0.001s FAILED (failures=1)
4 組織TestSuite
a:肯定測試用例的順序,哪一個先執行哪一個後執行?
b:若是測試文件有多個,怎麼進行組織?
TestLoder加載TestCase有幾種方法,在文件夾中咱們再新建一個文件,test_suite.py:
import unittest from test_mathfunc import TestMathFunc #構建測試集 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 "C:\Users\zhouxy\PycharmProjects\untitled\utest\test_mathfunc.py", line 25, in test_divide self.assertEqual(2, divide(5, 2)) AssertionError: 2 != 2.5 ---------------------------------------------------------------------- Ran 3 tests in 0.002s FAILED (failures=1)
上面用了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 用來從clases和modules建立test suites,一般也須要建立一個該類的實例,注意,用TestLoader的方法是沒法對case進行排序的(一個module執行的順序是根據測試用例的名稱),同時,suite中也能夠套suite。
discover加載測試用例
找到指定目錄下全部測試模塊,並可遞歸查到子目錄下的測試木塊,只有匹配到的文件名纔會被加載。若是啓動的不是頂層目錄,那麼頂層目錄必然單獨指定。
discover方法有三個參數:
- case_dir:這個是待執行用例的目錄;
- pattern:這個是匹配腳本名稱的規則,test*.py意思是匹配test開頭的全部腳本;
- top_level_dir:這個是頂層目錄的名稱,通常默認等於None。
discover加載到的用例是一個list集合,須要從新寫入到一個list對象testcase裏,這樣就能夠用unittest裏面的TextTestRunner這裏類的run方法去執行
import unittest import os #測試用例路徑 test_dir = os.path.join(os.getcwd(),'_test') discover = unittest.defaultTestLoader.discover(test_dir,pattern="test*.py",top_level_dir=None) if __name__ == "__main__": runner = unittest.TextTestRunner() runner.run(discover)
5 將結果輸出到文件中
用例組織好了,但結果只能輸出到控制檯,這樣沒有辦法查看以前的執行記錄,咱們想將結果輸出到文件。
修改test_suite.py:
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) with open('UnittestTextReport.txt','a') as f: runner = unittest.TextTestRunner(stream=f,verbosity=2) runner.run(suite)
6 test fixture之setUp() tearDown()
上面整個測試基本跑了下來,但可能會遇到點特殊的狀況:若是個人測試須要在每次執行以前準備環境,或者在每次執行完以後須要進行一些清理怎麼辦?好比執行前須要鏈接數據庫,執行完成以後須要還原數據、斷開鏈接。總不能每一個測試方法中都添加準備環境、清理環境的代碼吧。
這就要涉及到咱們以前說過的test fixture了,修改test_mathfunc.py:
from mathfunc import * import unittest class TestMathFunc(unittest.TestCase): """Test mathfunc.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)""" 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, divide(5, 2)) if __name__ == '__main__': unittest.main(verbosity=2)
添加 setUp()
和 tearDown()
兩個方法(實際上是重寫了TestCase的這兩個方法),這兩個方法在每一個測試方法執行前以及執行後執行一次,setUp用來爲測試準備環境,tearDown用來清理環境,已備以後的測試。
執行結果:
do something before test.Prepare environment
add
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
divide
do something after test.Clean up
若是想要在全部case執行以前準備一次環境,並在全部case執行結束以後再清理環境,咱們能夠用setUpClass()
與tearDownClass()
:
class TestMathFunc(unittest.TestCase): """Test mathfunc.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.
add
minus
divide
This tearDownClass() method only called once too.
7 跳過某個case
unittest提供了幾種方法:
7.1 skip裝飾器
@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, divide(5, 2))
執行結果:
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) ... skipped "I don't want to run this case" ---------------------------------------------------------------------- Ran 3 tests in 0.001s OK (skipped=1)
divide()方法被skip了。
skip裝飾器一共有三個 unittest.skip(reason)
、unittest.skipIf(condition, reason)
、unittest.skipUnless(condition, reason)
,skip無條件跳過,skipIf當condition爲True時跳過,skipUnless當condition爲False時跳過。
7.2 TestCase.skipTest()方法
def test_divide(self): """Test method divide(a, b)""" print('divide') self.assertEqual(2, divide(6, 3)) self.skipTest('skip this exmple') self.assertEqual(2, divide(5, 2))
執行結果:
This setUpClass() method only called once.
add
minus
divide
This tearDownClass() method only called once too.
8 用HTMLTestRunner輸出HTML報告
HTMLTestRunner是一個第三方的unittest HTML報告庫,首先咱們下載HTMLTestRunner.py,並放到當前目錄下或者環境變量。
import unittest from test_mathfunc import TestMathFunc from HTMLTestRunner import HTMLTestRunner import time if __name__ == '__main__': suite = unittest.TestSuite() tests = [TestMathFunc('test_add'),TestMathFunc('test_minus'),TestMathFunc('test_divide')] suite.addTests(tests) now = time.strftime('%Y%m%d%H%M%S') with open(now+'HTMLReport.html','wb') as f: runner = HTMLTestRunner(stream=f, title='計算器測試', description = '測試報告', verbosity=2) runner.run(suite)
在同目錄下生產測試報告文件:20180718193134HTMLReport.html
9 斷言
總結
test
開頭的方法在load時被加載爲一個真正的TestCase。0
是簡單報告、1
是通常報告、2
是詳細報告。setUp()
、tearDown()
、setUpClass()
以及 tearDownClass()
能夠在用例執行前佈置環境,以及在用例執行後清理環境