Python unittest 模組教學 part 2
Last updated on Mar 20, 2023 in Python 程式設計 - 中階 by Amo Chen ‐ 4 min read
本文是 Python unittest 模組的教學:
本文繼續介紹 unittest 幾個重要的方法,諸如 tearDown, test discover 以及 function test case 等等。
測試也需要善後工作- tearDown
測試過程難免會需要一些 test fixture, 例如產生設定檔、連結資料庫、產生檔案等等,而這些 test fixture 也可能需要在測試完成後進行善後與清除,此時可以選擇實作 unittest 模組的 tearDown
方法。
tearDown
會在每一個測試方法執行完之後被呼叫( setUp
則相反),因此可以把測試的善後與清除的工作放在 tearDown
方法中(不過也有人將測試過程的錯誤訊息在此方法中一併顯示),詳情可以直接參照以下範例 1。
記得,unittest 模組每一次執行測試方法的 順序 是 setUp > test 開頭的測試方法 > tearDown
範例 1:
import unittest
def compare(v1, v2):
if v1 == v2:
return True
return False
class MyTest(unittest.TestCase):
def setUp(self):
print("setUp!")
self.greeting = "Hello"
self.test_string = "HellO"
self.test_byte_string = b"\x48\x65\x6c\x6c\x6f"
self.v1, self.v2 = (1, True)
def test_compare_string(self):
print("test_compare_string")
assert compare(self.greeting, self.test_string) is False
def test_compare_byte_string(self):
print("test_compare_byte_string")
assert compare(self.greeting, self.test_byte_string) is False
def test_compare_int_bool(self):
print("test_compare_int_bool")
assert compare(self.v1, self.v2) is True
def tearDown(self):
print("tearDown!")
if __name__ == '__main__':
unittest.main()
範例 1 執行結果:
setUp!
test_compare_byte_string
tearDown!
.setUp!
test_compare_int_bool
tearDown!
.setUp!
test_compare_string
tearDown!
.
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
從上述執行結果可以看到 setUp 與 tearDown 確實會在每個 test case 執行之前與之後執行。
Test Discover
範例 2 中提及利用 test suite 對測試進行分類管理,除了能將性質相似的 test case 放在同 1 個類別或檔案之外,也可以將這些測試程式碼以資料夾進行分類管理,再搭配 unittest 的 test discover 功能,就能夠執行指定的資料夾內的所有測試程式。
首先,假設我們建立了一個專案資料夾為 MyProject ,接著在這資料夾內建立了一個 tests 資料夾, tests 資料夾內有兩個 test case 以及一個 __init__.py
, 資料夾的結構如下所示:
MyProject/
tests/
Case1_tests.py
Case2_tests.py
__init__.py
接下來,我們能夠使用以下指令讓 Python 自動載入 test case 並且執行:
$ python -m unittest discover -t /path/to/MyProject -s ./tests/ -p '*_tests.py'
上述指令的意思代表在 MyProject 資料夾載入 tests 資料夾內所有檔案以 _tests.py
結尾的 test case 並執行測試。
這就是 unittest 的 test discover 功能!
舊有的 test case 回收再利用
有些時候可能會遇到專案已經有一些不是用 class 寫成的測試,例如:
def test_compare_string():
assert compare(greeting, test) is False
在這種情況下我們也不需要捨棄這些測式函式(當然,有時間的話,轉為類別會是 Python 官方比較推薦的方式), Python 針對這種情況也設計了 FunctionTestCase 類別,讓我們將這些既有的測試函式轉換為 TestCase 類別。
範例 2 利用 FunctionTestCase 類別將舊有的測試函式轉換為 TestCase 類別後進行測試。
範例 2:
import unittest
def compare(v1, v2):
if v1 == v2:
return True
return False
def setUp():
global v1, v2
global greeting
global test_string
global test_byte_string
greeting = "Hello"
test_string = u"HellO"
test_byte_string = b"\x48\x65\x6c\x6c\x6f"
v1, v2 = (1, True)
def test_compare_string():
assert compare(greeting, test_string) is False
def test_compare_byte_string():
assert compare(greeting, test_byte_string) is False
def test_compare_int_bool():
assert compare(v1, v2) is True
def test_compare_type():
assert compare(v1, v2) is True
def tearDown():
greeting = None
test_string = None
test_byte_string = None
v1, v2 = (None, None)
if __name__ == '__main__':
testcases = (
test_compare_string,
test_compare_byte_string,
test_compare_int_bool,
test_compare_type,
)
for test in testcases:
testcase = unittest.FunctionTestCase(test, setUp=setUp, tearDown=tearDown)
unittest.TextTestRunner(verbosity=2).run(testcase)
從上述範例中的 unittest.FunctionTestCase(test, setUp=setUp, tearDown=tearDown)
可以發現 FunctionTestCase
也支援 setUp 以及 tearDown 的用法。
範例 2 執行結果如下:
unittest.case.FunctionTestCase (test_compare_string) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
unittest.case.FunctionTestCase (test_compare_byte_string) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
unittest.case.FunctionTestCase (test_compare_int_bool) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
unittest.case.FunctionTestCase (test_compare_type) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
雖然範例 2 的用法看似方便,但事實上並不是一個被 Python 官方推薦的方法, 應盡量避免使用 FunctionTestCase
。
略過測試(Skipping tests) & 預期中的錯誤(Expected failures)
Python 3.1 之後,在 unittest 模組也增加了 skipping tests 跟 expected failures 的功能。
Skipping tests 可以針對測試方法的執行進行條件的設定,例如滿足特定條件就略過測試。
Expected failures 則可以使用在某些預期必定會發生錯誤的測試方法上,例如測試故意打錯帳號密碼,一定要能夠阻擋登入,要是通過就有鬼了。
範例 3 展示了 skipping tests 跟 expected failures 的方法:
import sys
import unittest
mylib_version = (1, 3)
class MyTestCase(unittest.TestCase):
@unittest.skip("demonstrating skipping")
def test_nothing(self):
self.fail("shouldn't happen")
@unittest.skipIf(mylib_version < (1, 3),
"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
@unittest.skipIf(mylib_version > (1, 2), "not supported in this library version")
class MySkippedClass(unittest.TestCase):
def test_nothing(self):
pass
class ExpectedFailureTestCase(unittest.TestCase):
@unittest.expectedFailure
def failure_test(self):
self.assertEqual(1, 0, "broken")
if __name__ == '__main__':
unittest.main()
範例 3 執行結果(s 代表 skipped):
s.ss
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK (skipped=3)
範例 3 的重點在於對於想要設定 skipping 或者 expected failure 的測試方法可以使用 Python 裝飾子(decorator)的方式進行設定,相當簡單易懂。
最後我們在範例 4 中展示如何利用 unittest.skip 客製化自己的 skipping 的裝飾子(decorator),
範例 4:
import sys
import unittest
class MyClass(object):
do_test = True
def skipUnlessHasattr(obj, attr):
if hasattr(obj, attr):
return lambda func: func
return unittest.skip("{!r} doesn't have {!r}".format(obj, attr))
@skipUnlessHasattr(MyClass, "test")
class MySkippedClass(unittest.TestCase):
def test_nothing(self):
pass
if __name__ == '__main__':
unittest.main()
範例 4 的 skipUnlessHasattr
函式利用回傳 unittest.skip
來達成客製化 Skipping 裝飾子的效果。
總結
以上就是本篇 unittest 模組的大致教學與使用範例,在學會使用 unittest 之後,就可以學著開始利用一些以 unittest 模組為基礎而開發的測試套件(例如 nose, pytest 等等),相信一定可以駕輕就熟。
References
http://docs.python.org/3/library/unittest.html