背景
1. 學習資料:
官網: https://docs.python.org/2.7/library/unittest.html
https://docs.python.org/3/library/unittest.html
IBM Python自動單元測試框架: http://www.ibm.com/developerworks/cn/linux/l-pyunit/
2. python其他類似的工具
doctest: https://docs.python.org/2.7/library/doctest.html#module-doctest
unittest2: https://pypi.python.org/pypi/unittest2
nose: https://nose.readthedocs.org/en/latest/
3. 定義
unittest,有時也叫"PyUnit",是Python語言版本的單元測試框架。和JAVA語言的JUnit類似。
unittest 主要分為以下幾個模組:
test fixture:
指的是測試之前需要做的準備工作以及相關的清理工作。比如,新建暫時資料庫或代理資料庫,目錄,或者啟動伺服器程式。這裡主要就是指 setup 以及teardown 函式。
test case(TestCase):
test case是測試的最小單元。它檢查一組特定輸入的結果。unittest提供一個基類,TestCase,我們可以使用TestCase來新建新的測試用例。
test suite(TestSuite):
test suite是test case的集合,也可以是test suite的集合。它用來將所有一起執行的測試整合在一起。
test runner(TestRunner):
test runner負責執行測試並且提供測試結果。runner可能使用圖形介面、文字介面、或者返回一個特殊值來指定測試結果。
示例
1. 測試用例組成(TestCase)
所有的測試用例必須繼承自類 unittest.TestCase ,一般測試用例方法名以 test開頭。一個測試用例類內部可以定義一個或多個測試用例方法,比如下面就定義了3個方法。
import unittest class TestStringMethods(unittest.TestCase): def setUp(self): print("This is setup") def test_upper(self): print("This is test_upper") self.assertEqual('foo'.upper(), 'FOO') def test_isupper(self): print("This is test_isupper") self.assertTrue('FOO'.isupper()) self.assertFalse('Foo'.isupper()) def test_split(self): print("This is test_split") s = 'hello world' self.assertEqual(s.split(), ['hello', 'world']) # check that s.split fails when the separator is not a string with self.assertRaises(TypeError): s.split(2) def tearDown(self): print("This is tearDown") if __name__ == '__main__': unittest.main()
指令碼上右鍵,選擇 Run -> Run as Python unit-test ,就可以看到控制檯輸出如下:
Finding files... done. Importing test modules ... done. This is setup This is test_isupper This is tearDown This is setup This is test_split This is tearDown This is setup This is test_upper This is tearDown ---------------------------------------------------------------------- Ran 3 tests in 0.004s OK
可以看到,每個測試用例方法執行之前都會呼叫 setUp方法,每個測試用例方法執行結束都會呼叫 tearDown方法。
一般會把測試用例執行前的準備工作放到setUp方法裡面,比如WEB測試的登入操作。setUp執行時如果丟擲異常,後邊的test都不會被執行,該用例會被標記為error。
一般會把測試用例執行後的清理工作放到tearDown方法裡面,比如WEB測試的關閉瀏覽器操作。只要setUp執行成功,tearDown就會被執行。tearDown丟擲異常的話,測試結果會被標記為error。
2. 測試結果判定
unittest.TestCase 類包含各種判定測試結果的方法。
比如上面我們使用過的“self.assertEqual('foo'.upper(), 'FOO')”。
Method | Checks that | New in |
---|---|---|
assertEqual(a, b) |
a == b |
|
assertNotEqual(a, b) |
a != b |
|
assertTrue(x) |
bool(x) is True |
|
assertFalse(x) |
bool(x) is False |
|
assertIs(a, b) |
a is b |
3.1 |
assertIsNot(a, b) |
a is not b |
3.1 |
assertIsNone(x) |
x is None |
3.1 |
assertIsNotNone(x) |
x is not None |
3.1 |
assertIn(a, b) |
a in b |
3.1 |
assertNotIn(a, b) |
a not in b |
3.1 |
assertIsInstance(a, b) |
isinstance(a, b) |
3.2 |
assertNotIsInstance(a, b) |
not isinstance(a, b) |
3.2 |
所有的assert方法都含有一個預設引數“msg”,該引數預設值為“None”。如果給msg傳值,在assert判定fail的時候,系統就會列印出msg作為fail message。
“self.assertEqual('foo'.upper(), 'FOO1',"foo.upper is not FOO1!")”
如果assert方法判定特定條件不滿足,就會丟擲一個異常 AssertionError,unittest執行結果就會被判定為 fail。
3. 測試用例集(TestSuite)
多個TestCase可以組成一個TestSuite 。一般實際測試都有很多個TestCase,我們需要對其進行組合,這樣方便執行以及收集結果。
跑一個TestSuite ,和單獨跑TestSuite裡面的所有的TestCase ,結果是一樣的。
多個TestSuite 也可以組成一個更大的TestSuite。
import unittest class TestStringMethods(unittest.TestCase): # 類內容參見上面的程式碼 def suite(): suite = unittest.TestSuite() suite.addTest(TestStringMethods('test_upper')) suite.addTest(TestStringMethods('test_split')) return suite if __name__ == '__main__': print(suite())
執行結果如下:
<unittest.suite.TestSuite tests=[<__main__.TestStringMethods testMethod=test_upper>,
<__main__.TestStringMethods testMethod=test_split>]>
可以看到suite()返回的是一個 TestSuite例項,裡面只含有我們新增進去的測試用例方法。
TestSuite 主要包含4個方法:
addTest(test):新增test到TestSuite。這裡的test可以是一個TestCase,也可以是一個TestSuite。
addTests(tests):新增多個test到TestSuite。這裡的tests 可以是一個 TestCase列表,也可以是一個TestSuite列表。
run(result):跑TestSuite,並且收集測試結果到result。
countTestCases():返回該TestSuite說包含的所有的TestCase數目。
4. TestLoader
該類的目的就是為了更方便的整合所有testCase 為 TestSuite。
一般都不需要例項化一個TestLoader物件,可以直接通過 unittest.defaultTestLoader 來獲得一個 TestLoader 例項。
該類主要包含的都是loadTest方法,就是通過各種方式來收集TestCase 。 方法返回的是一個TestSuite 例項 。
loadTestsFromTestCase(testCaseClass):通過TestCase的類名載入用例。比如 TestStringMethods 。
loadTestsFromModule(module, pattern=None):通過模組名載入用例。
loadTestsFromName(name, module=None):通過名稱載入用例。該名稱一般是“xxx.xxxx.xxx.xxx”的形式,可以具體到TestCase類名(比如 TestStringMethods),也可以具體到類中的方法名(比如 test_split )。
loadTestsFromNames(names, module=None):通過多個名稱載入用例。類似 loadTestsFromName ,只不過這裡的引數是一個集合,比如一個列表。
discover(start_dir, pattern='test*.py', top_level_dir=None):通過路徑載入用例。 start_dir 一般是一個大的路徑。pattern一般是具體指令碼的匹配模式,預設是所有以test開頭的指令碼。
使用示例:
import unittest class TestStringMethods(unittest.TestCase): #類內容參見上面的程式碼 def suite(): suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestStringMethods) return suite if __name__ == '__main__': print(suite())
輸出結果如下:
<unittest.suite.TestSuite tests=[<__main__.TestStringMethods testMethod=test_isupper>,
<__main__.TestStringMethods testMethod=test_split>,
<__main__.TestStringMethods testMethod=test_upper>]>
可以看到返回的是一個TestSuite例項。
5. TestResult
該類用來收集測試結果。所有的測試結果都可以通過TestResult獲取。
import unittest class TestStringMethods(unittest.TestCase): #類內容參見上面的程式碼 def suite(): suite =unittest.defaultTestLoader.loadTestsFromTestCase(TestStringMethods) return suite if __name__ == '__main__': my_result=unittest.TestResult() my_suite=suite() my_suite.run(my_result) print("My Result:",my_result) print("Errors:",my_result.errors) print("Failures:",my_result.failures) print("Total Run case:",my_result.testsRun)
輸出結果:
My Result: <unittest.result.TestResult run=3 errors=0 failures=1> Errors: [] Failures: [(<__main__.TestStringMethods testMethod=test_upper>, 'Traceback (most recent call last):\n File "E:\\workspace\\Test\\src\\test\\test_unittest.py", line 10, in test_upper\n self.assertEqual(\'foo\'.upper(), \'FOO1\',"oooFail")\nAssertionError: \'FOO\' != \'FOO1\'\n- FOO\n+ FOO1\n? +\n : oooFail\n')]
Total Run case: 3
errors : 返回一個列表。如果測試Pass,則列表為空。不然就是所有的error集合。
failures : 返回一個列表。如果測試Pass,則列表為空。不然就是所有的failure集合。比如上面我故意更改 test_upper的assert判定條件,導致該用例fail。這裡就記錄了failure的情況。
testsRun:返回一個整數。值為所有的run的測試用例數目。比如這裡就是3。
TestResult 還含有一些方法,感覺主要作用就是定義自己的TestResult時候可以重寫這些方法。
startTest(test) :將要執行測試用例方法的時候呼叫。
stopTest(test) :測試用例方法執行完畢後呼叫。
startTestRun() :執行第一個用例前呼叫。
stopTestRun() :執行最後一個用例後呼叫。
addError(test, err) :測試用例返回error時呼叫。
addFailure(test, err) :測試用例返回fail時呼叫。
addSuccess(test) :測試用例返回pass時呼叫。
6. 命令列執行
示例:
python -m unittest test_module1 test_module2 python -m unittest test_module.TestClass python -m unittest test_module.TestClass.test_method python -m unittest tests/test_something.py
可以採用 “python -m unittest xxxx” 的方式來執行測試用例。 xxxx可以是模組、用例類、用例類中的方法、或者檔案路徑。
>python -m unittest test_unittest.py ... ---------------------------------------------------------------------- Ran 3 tests in 0.001s OK
如果想獲取更詳細的內容,可以加 -v 。
>python -m unittest -v test_unittest.py test_isupper (tet_excel.TestStringMethods) ... ok test_split (tet_excel.TestStringMethods) ... ok test_upper (tet_excel.TestStringMethods) ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.002s OK
也可以使用discover來自動載入測試用例。但是前提是指令碼名稱必須以 test開頭。
discover 命令還有其他的一些使用方法,比如自定義匹配模式等等。這裡不細說。
>cd project_directory >python -m unittest discover ... ---------------------------------------------------------------------- Ran 3 tests in 0.001s OK
7. 跳過執行
如果某個測試方法不想執行,給它加上 skip()裝飾器即可。
示例:
import unittest import sys from common import mylib class MyTestCase(unittest.TestCase): @unittest.skip("demonstrating skipping") def test_nothing(self): self.fail("shouldn't happen") @unittest.skipIf(mylib.__version__< 5, "not supported in this library version") def test_format(self): # Tests that work for only a certain version of the library. pass @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") def test_windows_support(self): # windows specific testing code pass if __name__ == '__main__': unittest.main(verbosity=2)
執行結果如下:
test_format (__main__.MyTestCase) ... skipped 'not supported in this library version' test_nothing (__main__.MyTestCase) ... skipped 'demonstrating skipping' test_windows_support (__main__.MyTestCase) ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.001s OK (skipped=2)
可以看到前面兩個用例都被跳過了。但是因為我的環境時window的,所以第3個用例沒有被跳過。
如果某個測試用例類不想執行,也可以跳過。
import unittest @unittest.skip("showing class skipping") class MySkippedTestCase(unittest.TestCase): def test_not_run(self): pass def test_not_run2(self): pass if __name__ == '__main__': unittest.main(verbosity=2)
執行結果如下:
test_not_run (__main__.MySkippedTestCase) ... skipped 'showing class skipping' test_not_run2 (__main__.MySkippedTestCase) ... skipped 'showing class skipping' ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK (skipped=2)
可以看到兩個測試用例方法都被跳過了。
8. TestProgram
關於命令列執行的一個測試類。沒看到官方文件有詳細的資料,暫時不研究。