unittest --- 單元測試框架¶
(假如你已經熟悉相關基礎的測試概念,你可能會希望跳過以下段落,直接參考 assert 方法清單。)
unittest 原生的單元測試框架最初由 JUnit 開發,和其他程式語言相似有主要的單元測試框架。支援自動化測試,對測試分享安裝與關閉程式碼,集合所有匯總的測試,並且獨立各個測試報告框架。
unittest 用來作為實現支援一些重要的物件導向方法的概念:
- test fixture
一個 test fixture 代表執行一個或多個測試所需要的準備,以及其他相關清理操作,例如可以是建立臨時性的或是代理用 (proxy) 資料庫、目錄、或是啟動一個伺服器程序。
- test case(測試用例)
一個 test case 是一個獨立的單元測試。這是用來確認一個特定設定的輸入的特殊回饋。
unittest提供一個基礎類別,類別TestCase,可以用來建立一個新的測試條例。- test suite(測試套件)
test suite 是一個搜集測試條例,測試套件,或是兩者皆有。它需要一起被執行並用來匯總測試。
- test runner(測試執行器)
test runner 是一個編排測試執行與提供結果給使用者的一個元件。執行器可以使用圖形化介面,文字介面或是回傳一個特別值用來標示出執行測試的結果。
也參考
doctest模組另一個執行測試的模組,但使用不一樣的測試方法與規範。
- Simple Smalltalk Testing: With Patterns
Kent Beck 的原始論文討論使用
unittest這樣模式的測試框架。- pytest
第三方的單元測試框架,但在撰寫測試時使用更輕量的語法。例如:
assert func(10) == 42。- The Python Testing Tools Taxonomy
一份詳細的 Python 測試工具列表,包含 functional testing 框架和mock object 函式庫。
- Testing in Python Mailing List
一個專門興趣的群組用來討論 Python 中的測試方式與測試工具。
The script Tools/unittestgui/unittestgui.py in the Python source distribution is
a GUI tool for test discovery and execution. This is intended largely for ease of use
for those new to unit testing. For production environments it is
recommended that tests be driven by a continuous integration system such as
Buildbot, Jenkins,
GitHub Actions, or
AppVeyor.
簡單範例¶
unittest 模組提供一系列豐富的工具用來建構與執行測試。本節將展示這一系列工具中一部份,它們已能滿足大部份使用者需求。
這是一段簡短的腳本用來測試 3 個字串方法:
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())
def test_split(self):
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)
if __name__ == '__main__':
unittest.main()
測試用例 (test case) 可以透過繼承 unittest.TestCase 類別來建立。這裡定義了三個獨立的物件方法,名稱皆以 test 開頭。這樣的命名方式能告知 test runner 哪些物件方法為定義的測試。
每個測試的關鍵為呼叫 assertEqual() 來確認是否為期望的結果; assertTrue() 或是 assertFalse() 用來驗證一個條件式; assertRaises() 用來驗證是否觸發一個特定的 exception。使用這些物件方法來取代 assert 陳述句,將能使 test runner 收集所有的測試結果並產生一個報表。
The setUp() and tearDown() methods allow you
to define instructions that will be executed before and after each test method.
They are covered in more detail in the section Organizing test code.
最後將顯示一個簡單的方法去執行測試 unittest.main() 提供一個命令列介面測試腳本。當透過命令列執行,輸出結果將會像是:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
在測試時加入 -v 選項將指示 unittest.main() 提高 verbosity 層級,產生以下的輸出:
test_isupper (__main__.TestStringMethods.test_isupper) ... ok
test_split (__main__.TestStringMethods.test_split) ... ok
test_upper (__main__.TestStringMethods.test_upper) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
以上的例子顯示大多數使用 unittest 特徵足以滿足大多數日常測試的需求。接下來第一部分文件的剩餘部分將繼續探索完整特徵設定。
在 3.11 版的變更: The behavior of returning a value from a test method (other than the default
None value), is now deprecated.
命令列介面¶
單元測試模組可以透過命令列執行測試模組,物件甚至個別的測試方法:
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
這允許你使用 shell 檔案名稱補完功能 (filename completion) 來指定測試模組。給定的檔案路徑必須亦能被當作模組 import。此路徑轉換為模組名稱的方式為移除 '.py' 並將路徑分隔符 (path separator) 轉換成 '.'。 假如你的測試檔案無法被 import 成模組,你應該直接執行該測試檔案。
透過增加 -v 的旗標數,可以在你執行測試時得到更多細節(更高的 verbosity):
python -m unittest -v test_module
若執行時不代任何引數,將執行 Test Discovery(測試探索):
python -m unittest
列出所有命令列選項:
python -m unittest -h
在 3.2 版的變更: 在早期的版本可以個別執行測試方法和不需要模組或是類別。
在 3.14 版被加入: Output is colorized by default and can be controlled using environment variables.
命令列模式選項¶
unittest 支援以下命令列選項:
- -b, --buffer¶
Standard output 與 standard error stream 將在測試執行被緩衝 (buffer)。這些輸出在測試透過時被丟棄。若是測試錯誤或失則,這些輸出將會正常地被印出,並且被加入至錯誤訊息中。
- -c, --catch¶
Control-C 測試執行過程中等待正確的測試結果並回報目前為止所有的測試結果。第二個 Control-C 拋出一般例外
KeyboardInterrupt。參照 Signal Handling 針對函式提供的功能。
- -f, --failfast¶
在第一次錯誤或是失敗停止執行測試。
- -k¶
Only run test methods and classes that match the pattern or substring. This option may be used multiple times, in which case all test cases that match any of the given patterns are included.
Patterns that contain a wildcard character (
*) are matched against the test name usingfnmatch.fnmatchcase(); otherwise simple case-sensitive substring matching is used.Patterns are matched against the fully qualified test method name as imported by the test loader.