1.概述。
相信接觸過Java語言的朋友一定對Junit單元測試框架不陌生,對於Python語言,同樣有類似的單元測試框架Unittest。
Unittest是Python內部自帶的一個單元測試的模塊,它設計的靈感來源於Junit,具有和Junit類似的結構,有過Junit經驗的朋友可以很快上手。Unittest具備完整的測試結構,支持自動化測試的執行,對測試用例集進行組織,並且提供了豐富的斷言方法,最后生成測試報告。Unittest框架的初衷是用於單元測試,但也不限於此,在實際工作中,由於它強大的功能,提供的完整的測試流程,我們往往將其用於自動化測試的各個方面,例如在本書中大量的接口測試實例都會用到Unittest。
所謂知己知彼百戰不殆,首先我們來一起看下Unittest大家庭里的成員。首先導入unittest模塊,使用dir()函數獲取Unittest的所有成員,並輸出到界面上。
import unittest print(dir(unittest)) |
執行結果如下:
['BaseTestSuite', 'FunctionTestCase', 'SkipTest', 'TestCase', 'TestLoader', 'TestProgram', 'TestResult', 'TestSuite', 'TextTestResult', 'TextTestRunner', '_TextTestResult', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__unittest', 'case', 'defaultTestLoader', 'expectedFailure', 'findTestCases', 'getTestCaseNames', 'installHandler', 'load_tests', 'loader', 'main', 'makeSuite', 'registerResult', 'removeHandler', 'removeResult', 'result', 'runner', 'signals', 'skip', 'skipIf', 'skipUnless', 'suite', 'util'] |
結果里顯示了unittest模塊的各個成員,看起來非常多,不知道如何下手。其實一個模塊里往往包含了大量的成員,很大一部分我們使用的頻率並不高,這時需要有重點的去攻克核心的部分,其他的稍作了解即可。
下面先簡單介紹最常用的一些成員,后續的章節中我們會詳細剖析。
(1) TestCase:可以說是unittest中最重要的一個類,也是測試用例類的父類,通過對其繼承,使子類具備了執行測試的能力。下例中MainTest是需要執行的測試類。
Class MainTest(unittest.TestCase): |
(2) TestSuite: TestSuite類用於創建測試套件。最常見的用法是,使用該類將多個測試用例添加到用例集,通過運行用例集,實現多個測試用例的執行。
(3) main:調用unittest.main()方法可以方便的將測試類里的以“test”命名開頭的測試方法以腳本的形式自動執行。
(4) TextTestRunner:主要使用該類的run()方法來運行TestSuite添加好的測試用例。
(5) skipXX:裝飾器。有時我們的測試只想運行其中的一部分用例,那么我們可以使用skip裝飾器來跳過執行。最常見的場景是:在不同的系統環境上運行時,某些用例是不能通過的,但這並不是我們的產品或用例導致,而是環境的不兼容等問題,此時我們可以使用skip裝飾器來處理。
2.重要概念。
在繼續學習之前,我們需要掌握四個Unittest的重要概念。以下是官方網站上通過面向對象大的方式進行的解釋。
To achieve this, unittest supports some important concepts in an object-oriented way:
test fixture A test fixture represents the preparation needed to perform one or more tests, and any associate cleanup actions. This may involve, for example, creating temporary or proxy databases, directories, or starting a server process.
test case A test case is the individual unit of testing. It checks for a specific response to a particular set of inputs. unittest provides a base class, TestCase, which may be used to create new test cases.
test suite A test suite is a collection of test cases, test suites, or both. It is used to aggregate tests that should be executed together.
test runner A test runner is a component which orchestrates the execution of tests and provides the outcome to the user. The runner may use a graphical interface, a textual interface, or return a special value to indicate the results of executing the tests. |
(1) test fixture:翻譯過來是測試固定裝置的意思。形象的說,把整個測試過程看作大的裝置,這個裝置里不僅具有測試執行部件,還有測試之前環境准備和測試之后環境清理的部件,有機的結合起來就是一個更大的測試裝置,即test fixture。
(2) test case:測試用例,注意與前面的TestCase類不是同一個概念。一個完整的測試流程就是一個測試用例,通過一些特定的輸入得到響應,並對響應進行校驗的過程。我們通過去繼承TestCase這個父類,可以創建新的測試用例。
(3) test suite:測試套件,也稱為測試集合。多個測試用例組合在一起就形成了測試集,當然測試集里不僅能包含測試用例,也可以再次嵌套測試集,測試集可以用於代碼的組織和運行。
(4) test runner:是Unittest中的重要組成部分,主要職責為執行測試,通過圖形、文本或者返回一些特殊值的方式來呈現最終的運行結果。例如執行的用例數、成功和失敗的用例數。
圖3-1展示了他們之間的關系,test fixture是包含了以test case為核心的整個組件,多個test case可以集合到一個test suite中,最后調用test runner執行並生成結果。
(Unittest組成部分)
3.實例。
下面我們通過一個簡單的實例讓大家對Unittest的基本使用有一個直觀的認識。首先創建一個項目,項目里有兩個文件,Calculator.py是被測試的代碼,Demo.py是執行測試的代碼。項目結構如下:
(1) 被測代碼准備。在Calculator類中定義了一個方法為divide,該方法接收x和y兩個參數分別作為分子和分母進行除法運算,並返回運算結果。
class Calculator: def divide(self,x,y): return x / y |
(2) 測試代碼。測試代碼先通過from和import兩種方式分別導入了Calculator和unittest模塊,然后定義了一個測試類TestCalculator,並繼承於unittest.TestCase模塊。測試類中有一個測試方法test_divide,調用了被測試類Calculator中的divide方法,並將結果進行斷言。最后調用unittest.main方法來執行當前類中所有以test開頭的方法來直接運行,而不必再專門對TestCalculator進行實例化。
import unittest from my_unittest.unittest_demo.Calculator import Calculator
class TestCalculator(unittest.TestCase): def testDivide01(self): cal = Calculator() result = cal.divide(10,2) self.assertEqual(result,5)
def testDivide02(self): cal = Calculator() result = cal.divide(20,0.5) self.assertEqual(result,40)
if __name__ == '__main__': unittest.main() |
(3)運行結果如下。兩個測試總共運行了0.001秒,測試結果為OK。
.. --------------------------------------------------------------------- Ran 2 tests in 0.001s
OK |
(4)接下來我們再看一個測試不通過的例子,依然使用Calculator作為待測試的類,測試類中包含3個測試方法,即3條測試用例:
from C03_Unittest.Ex01_SimpleExample.Calculator import Calculator
class TestCalculator(unittest.TestCase): def testDivide01(self): cal = Calculator() result = cal.divide(10,2) self.assertEqual(result,5)
def testDivide02(self): cal = Calculator() result = cal.divide(10,0.5) self.assertEqual(result,10)
def testDivide03(self): cal = Calculator() result = cal.divide(10,0) self.assertEqual(result,0)
if __name__ == '__main__': unittest.main() |
(5) 第一個方法(即第一條測試用例)用10除以2,預期結果為5,測試結果通過;第二條測試用例用10除以0.5,為了觀察運行結果,刻意設置錯誤的預期結果為10,但實際結果為20,出現“AssertionError: 0.01 != 0.001”,斷言錯誤,測試失敗;第三條用屬於異常測試,使用10除以0,出現運行錯誤“ZeroDivisionError: division by zero”,提示不能使用0作為分母。結果中的第一行“.FE”分別代表運行中的三條用例,“.”為通過,“F”為不通過,“E”為錯誤。
.FE ===================================================================== ERROR: testDivide03 (__main__.TestCalculator) --------------------------------------------------------------------- Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/python364/C03_Unittest/Ex01_SimpleExample/Test02.py", line 17, in testDivide03 result = cal.divide(10,0) File "C:\Users\Administrator\PycharmProjects\python364\C03_Unittest\Ex01_SimpleExample\Calculator.py", line 3, in divide return x / y ZeroDivisionError: division by zero
===================================================================== FAIL: testDivide02 (__main__.TestCalculator) --------------------------------------------------------------------- Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/python364/C03_Unittest/Ex01_SimpleExample/Test02.py", line 13, in testDivide02 self.assertEqual(result,10) AssertionError: 20.0 != 10
--------------------------------------------------------------------- Ran 3 tests in 0.003s
FAILED (failures=1, errors=1) |
(6) 通過上面的測試,可以得出一個結論:Calculator類的divide方法具有明顯的Bug,該方法中沒有對輸入參數進行校驗,導致在y為0時程序運行錯誤。當測試人員發現此Bug后應及時通知開發人員進行修復,避免造成更嚴重的損失。這個實例不僅展現了Unittest框架的實際價值,也進一步體現了測試工作在研發過程中的重要意義。