Overview
Basic example
隨著專案的不斷擴大,單元測試在保證開發效率、可維護性和軟體質量等方面的地位越發舉足輕重,是一本萬利的舉措。Python 常用 unittest module 編寫單元測試,它包含四個概念:
- test fixture:初始化和清理測試環境,比如建立臨時的資料庫,檔案和目錄等,其中 setUp() 和 setDown() 是最常用的方法
- test case:單元測試用例,TestCase 是編寫單元測試用例最常用的類
- test suite:單元測試用例的集合,TestSuite 是最常用的類
- test runner:執行單元測試
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import unittest class TestStringMethods(unittest.TestCase): def test_upper(self): self.assertEqual('foo'.upper(), 'FOO') def test_isupper(self): self.assertTrue('FOO'.isupper()) self.assertFalse('Foo'.isupper()) if __name__ == '__main__': unittest.main() |
執行結果如下:
1 2 3 4 5 6 7 |
$ python testdemo.py .. ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK |
Add fixture
setUp() 和 setDown() 允許執行每個測試用例前分別初始化和清理測試環境,用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import unittest class TestStringMethods(unittest.TestCase): def setUp(self): # Do something to initiate the test environment here. pass def tearDown(self): # Do something to clear the test environment here. pass def test_upper(self): self.assertEqual('foo'.upper(), 'FOO') def test_isupper(self): self.assertTrue('FOO'.isupper()) self.assertFalse('Foo'.isupper()) if __name__ == '__main__': unittest.main() |
Ignore some testcases
有時希望某些用例不被執行,unittest.skip() 提供了忽略某個測試用例的功能,用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import unittest class TestStringMethods(unittest.TestCase): def test_upper(self): self.assertEqual('foo'.upper(), 'FOO') @unittest.skip('skip is upper.') def test_isupper(self): self.assertTrue('FOO'.isupper()) self.assertFalse('Foo'.isupper()) if __name__ == '__main__': unittest.main() |
執行結果如下:
1 2 3 4 5 6 7 8 9 |
$ python testdemo.py test_isupper (testdemo.TestStringMethods) ... skipped 'skip is upper.' test_upper (testdemo.TestStringMethods) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK (skipped=1) |
Run your tests
Command Line Interface
unittest 提供了豐富的命令列入口,可以根據需要執行某些特定的用例。有了命令列的支援,上述例子的最後兩行程式碼就顯得冗餘,應當被移除:
1 2 3 4 5 6 |
... # 刪除以下程式碼 if __name__ == '__main__': unittest.main() |
執行 testdemo.py 檔案所有的測試用例:
1 2 |
$ python -m unittest testdemo |
執行 testdemo.py 檔案的 TestStringMethods 類的所有測試用例:
1 2 |
$ python -m unittest test_demo.TestStringMethods |
執行 testdemo.py 檔案 TestStringMethods 類的 test_upper:
1 2 |
$ python -m unittest test_demo.TestStringMethods.test_upper |
Test Discovery
unittest 提供了自動匹配發現並執行測試用例的功能,隨著專案程式碼結構越發龐大,勢必有多個測試檔案,自動匹配發現並測試用例的功能在此就顯得非常有用,只要滿足 load_tests protocol 的測試用例都會被 unittest 發現並執行,測試用例檔案的預設匹配規則為 test*.py。通過一條命令即可執行所有的測試用例,如此就很容易被 tox 等測試工具所整合。使用如下:
1 2 |
python -m unittest discover |
引數如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
-v, --verbose Verbose output -s, --start-directory directory Directory to start discovery (. default) -p, --pattern pattern Pattern to match test files (test*.py default) -t, --top-level-directory directory Top level directory of project (defaults to start directory) |
假設現在要被測試的程式碼目錄如下:
1 2 3 4 5 |
$ tree demo demo ├── testdemo.py └── testdemo1.py |
1 2 3 4 5 6 7 8 9 10 11 |
$ python -m unittest discover -s demo -v test_isupper (testdemo.TestStringMethods) ... ok test_upper (testdemo.TestStringMethods) ... ok test_is_not_prime (testdemo1.TestPrimerMethods) ... ok test_is_prime (testdemo1.TestPrimerMethods) ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK |
A Collection of Assertion
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
+--------------------------------+-------------------------------------------------------+-------+ | 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 | 2.7 | | assertIsNot(a, b) | a is not b | 2.7 | | assertIsNone(x) | x is None | 2.7 | | assertIsNotNone(x) | x is not None | 2.7 | | assertIn(a, b) | a in b | 2.7 | | assertNotIn(a, b) | a not in b | 2.7 | | assertIsInstance(a, b) | isinstance(a, b) | 2.7 | | assertNotIsInstance(a, b) | not isinstance(a, b) | 2.7 | | assertAlmostEqual(a, b) | round(a-b, 7) == 0 | | | assertNotAlmostEqual(a, b) | round(a-b, 7) != 0 | | | assertGreater(a, b) | a > b | 2.7 | | assertGreaterEqual(a, b) | a >= b | 2.7 | | assertLess(a, b) | a < b | 2.7 | | assertLessEqual(a, b) | a <= b | 2.7 | | assertRegexpMatches(s, r) | r.search(s) | 2.7 | | assertNotRegexpMatches(s, r) | not r.search(s) | 2.7 | | assertItemsEqual(a, b) | sorted(a) == sorted(b) and works with unhashable objs | 2.7 | | assertDictContainsSubset(a, b) | all the key/value pairs in a exist in b | 2.7 | | assertMultiLineEqual(a, b) | strings | 2.7 | | assertSequenceEqual(a, b) | sequences | 2.7 | | assertListEqual(a, b) | lists | 2.7 | | assertTupleEqual(a, b) | tuples | 2.7 | | assertSetEqual(a, b) | sets or frozensets | 2.7 | | assertDictEqual(a, b) | dicts | 2.7 | +--------------------------------+-------------------------------------------------------+-------+ |
Testtools
testtools is a set of extensions to the Python standard library’s unit testing framework.
testtools 是一個功能類似 unittest 的庫,它集合眾家標準測試庫之所長,和 unittest 相比,功能更為強大,使用更為簡單,OpenStack 廣泛的利用它編寫單元測試。由於在用法上 testtools 和 unittest 類似,所以本節簡要介紹 testtols 的特點:
- Better assertion methods: 支援 assertIn, assertIs, assertIsInstance 等 assertion
- More debugging info: 更為詳細的 debug 資訊
- Extend unittest, but stay compatible and re-usable: 相容 unittest
- Cross-Python compatibility: 支援多種 Python 版本 2.7, 3.3, 3.4, 3.5
更為詳細的說明和用法請見 testtools: tasteful testing for Python。