python測試系列教程 —— 單元測試unittest
全棧工程師開發手冊 (作者:欒鵬)
unittest是xUnit系列框架中的一員,如果你瞭解xUnit的其他成員,那你用unittest來應該是很輕鬆的,它們的工作方式都差不多。
unittest核心工作原理
unittest是Python自帶的單元測試框架,我們可以用其來作為我們自動化測試框架的用例組織執行框架。
unittest中最核心的四個概念是:test case, test suite, test runner, test fixture。
下面我們分別來解釋這四個概念的意思,先來看一張unittest的靜態類圖。
一個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中時也有四個測試用例。
在每一個測試用例中可以重寫 以下函式
setUp()該測試用例執行前的設定工作、
tearDown()該測試用例執行後的清理工作、
setUpClass()所有測試用例前的設定工作、
tearDownClass()所有測試用例執行後的清洗工作
在每一個測試用例中可以通過skip,skipIf,skipUnless裝飾器跳過某個測試函式,或者用TestCase.skipTest方法跳過測試函式。
到這裡整個流程就清楚了:
寫好TestCase,然後由TestLoader載入TestCase到TestSuite,然後由TextTestRunner來執行TestSuite,執行的結果儲存在TextTestResult中,我們通過命令列或者unittest.main()執行時,main會呼叫TextTestRunner中的run來執行,或者我們可以直接通過TextTestRunner來執行用例。
加個說明:
1、TestLoader載入TestCase到TestSuite可以通過TestSuite例項物件的addTest()和addTests()方法向suite中新增case或suite
2、在Runner執行時,預設將執行結果輸出到控制檯,我們可以設定其輸出到檔案,在檔案中檢視結果(你可能聽說過HTMLTestRunner,是的,通過它可以將結果輸出到HTML中,生成漂亮的報告,它跟TextTestRunner是一樣的,從名字就能看出來,這個我們後面再說)。
3、在進行測試時可以傳遞verbosity引數,用以控制執行結果的輸出,0 是簡單報告、1 是一般報告、2 是詳細報告。
案例
業務函式
這裡我們隨意寫幾個業務函式,表示我們將要進行測試的功能函式。將功能函式檔案儲存成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
TestCase測試用例
我們通過測試用例用程式碼來實現每一個測試的詳細過程和針對測試目標要測試的內容。同目錄下建立test_mathfunc.py
import unittest
from mathfunc import *
class TestMathFunc(unittest.TestCase):
# TestCase基類方法,所有case執行之前自動執行
@classmethod
def setUpClass(cls):
print("這裡是所有測試用例前的準備工作")
# TestCase基類方法,所有case執行之後自動執行
@classmethod
def tearDownClass(cls):
print("這裡是所有測試用例後的清理工作")
# TestCase基類方法,每次執行case前自動執行
def setUp(self):
print("這裡是一個測試用例前的準備工作")
# TestCase基類方法,每次執行case後自動執行
def tearDown(self):
print("這裡是一個測試用例後的清理工作")
@unittest.skip("我想臨時跳過這個測試用例.")
def test_add(self):
self.assertEqual(3, add(1, 2))
self.assertNotEqual(3, add(2, 2)) # 測試業務方法add
def test_minus(self):
self.skipTest('跳過這個測試用例')
self.assertEqual(1, minus(3, 2)) # 測試業務方法minus
def test_multi(self):
self.assertEqual(6, multi(2, 3)) # 測試業務方法multi
def test_divide(self):
self.assertEqual(2, divide(6, 3)) # 測試業務方法divide
self.assertEqual(2.5, divide(5, 2))
if __name__ == '__main__':
unittest.main(verbosity=2)
注意:
skip裝飾器一共有三個 unittest.skip(reason)、unittest.skipIf(condition,reason)、unittest.skipUnless(condition,reason),skip無條件跳過,skipIf當condition為True時跳過,skipUnless當condition為False時跳過。
每個測試方法均以 test 開頭,否則是不被unittest識別的。
其實每一個test開頭的方法都會載入為獨立的測試用例。
在unittest.main()中加 verbosity 引數可以控制輸出的錯誤報告的詳細程度,預設是 1,如果設為 0,則不輸出每一用例的執行結果。如果引數為2則表示輸出詳細結果。
TestSuite測試組
TestSuite用來控制多個測試用例和多個測試檔案之間的測試順序。(這裡的示例中的幾個測試方法並沒有一定關係,但之後你寫的用例可能會有先後關係,需要先執行方法A,再執行方法B)
我們新增到TestSuite中的case是會按照新增的順序執行的。
下面我們就來試下將測試用例載入到測試組中。進行測試並將測試結果輸出。
下面的程式碼儲存成
import unittest
from test_mathfunc import TestMathFunc
from HTMLTestRunner import HTMLTestRunner
if __name__ == '__main__':
suite = unittest.TestSuite()
tests = [TestMathFunc("test_add"), TestMathFunc("test_minus"), TestMathFunc("test_divide")] # 新增測試用例列表
suite.addTests(tests) # 將測試用例列表新增到測試組中
suite.addTest(TestMathFunc("test_multi")) # 直接用addTest方法新增單個TestCase
# 用addTests + TestLoader。不過用TestLoader的方法是無法對case進行排序的
# 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))
# suite中也可以套suite
# 將測試結果輸出到測試報告中
# with open('UnittestTextReport.txt', 'a') as f:
# runner = unittest.TextTestRunner(stream=f, verbosity=2)
# runner.run(suite)
# 將測試結果輸出到測試報告html中
# with open('HTMLReport.html', 'w') as f:
# runner = HTMLTestRunner(stream=f,
# title='MathFunc Test Report',
# description='generated by HTMLTestRunner.',
# verbosity=2
# )
# runner.run(suite)
# 直接將結果輸出到控制檯
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
測試報告
三種輸出結果只能選擇一種,不能全部選擇。
控制檯輸出:
txt輸出:
html輸出: