📘 NOTE |
---|
1. 放置在 test / tests 目錄內 |
2. 模組名稱為 test_*.py |
☢️ WARNING |
---|
1. 測試目錄與被測試目錄皆要有 __init__.py |
2. 測試目錄路徑不可有不合法名稱 (比如有中括號 [] 的名稱,會被誤當成 .ini 的 section)。 否則會產生錯誤 OSError: Starting path not found 。 |
3. 測試目錄路徑若有任一目錄改過名稱,請務必清除測試目錄底下的__pycache__ 。否則會產生錯誤 OSError: Starting path not found 。 |
4. TestClass 的第一個引數都要是 self ,就如同在寫一般 class 一樣。 |
選項
--markers
: 查看所有已註冊的 markers--fixtures
: 查看所有已註冊的 fixtures-h
: 參數說明 (help)-v
: 詳細結果 (verbose)-q
: 簡潔結果 (quiet)-s
: 允許 stdout / stderr 印出 (會打亂 pytest 測試結果輸出格式)測試範圍
全部測試
針對某個子目錄進行測試
針對某個模組進行測試
針對某個模組中某個函式進行測試
針對某個模組中某個類別進行測試
針對某個模組中某個類別的某個方法進行測試
CLI 測試結果
.
/ PASSED
: 代表一個通過的 test cases
/ SKIPPED
: 代表一個跳過的 test casex
/ XFAILED
: 代表一個已預期失敗 (等待修復) 的 test caseF
/ FAILED
: 代表一個失敗的 test case[80%]
: 代表的是總完成進度 (百分比).vscode/settings.json
pytest.ini
📗 TIP |
---|
如果你使用 pyproject.toml ,只要將以下設定都置於 [tool.pytest.ini_options] 即可 |
conftest.py
說明
example
tests/conftest.py
tests/test_something.py
raises
🚨 CAUTION |
---|
每個 with 述句只能驗證一個錯誤 |
setup
/ teardown
fixture
decorator kwargs
scope
: 變數生命週期的作用域,預設為 "function"name
: 變數調用名稱,預設就是函式的名稱autouse
: 是否在每個 test case 中自動使用,預設為 Falseparams
: 參數化測試,應給定 list[dict[str, Any]]必要性
Q : 為何使用 fixture,而不使用 global variable 就好?
global variable 寫起來不是更簡單嗎?
A : 請看下例,如果 fixture
test_data
變成一般的 global variable,
就會造成test_data
通過第一個 test case 後,反而無法通過之後的 test case。
很明顯,test_data
需要在每個 test case 執行前重新 create 一遍。
fixture(scope="function")
因而派上用場。
參數 autouse
scope="function" 的 autouse 會讓模組內的 test case 自動使用 clear_tables。
如果有非常多的 test cases,就不須每個都套上 decorator。
monkeypatch
capsys
輸出流
錯誤流
caplog
tmp_path
📘 NOTE |
---|
在 Windows 中,這些暫時目錄或檔案會放在 %APPDATA%/Local/Temp/pytest-of-user 中 |
cache
mark.?
🚨 CAUTION |
---|
要先在 pytest.ini 註冊,方可使用自訂標記 |
標記自訂 marker
只選擇有 mark.database 的 test case 進行測試
mark.skip
/ mark.skipif
decorator kwargs
condition
: 條件為真時跳過該 test casereason
: 跳過測試的原因 (在 verbose 模式會印出來)example
mark.xfail
decorator kwargs
reason
: 跳過測試的原因 (在 verbose 模式會印出來)example
mark.parametrize
🚨 CAUTION |
---|
可搭配 fixture 使用,但它一定要裝飾在最靠近函式的地方 |
decorator kwargs
argnames
: 參數名稱argvalues
: 參數值 (可以有多組值)ids
: 每個 test case 的名稱example
ids
mark.usefixtures
基本上功能與 fixture 傳遞引數寫法無異。
但通常用在欲使用 fixture 達成 context management 的場合,
而非使用它的回傳值做用途的場合。
上下文順序 : 由左至右、由下至上
context management : fixture + yield
為什麼不用 setup
/ teardown
就好?
不像 setup / teardown 函數無法傳遞引數,
使用 fixture 做 context management 的好處是可以傳遞引數。
如下,傳遞的引數為 test_session
插件:覆蓋率測試
🚨 CAUTION |
---|
預設的覆蓋率計算方法是行數覆蓋 (line coverage) |
選項
--cov=<path>
: 只根據指定路徑,進行覆蓋率測試 (會產生一個 .coverage
檔)--cov-report=<type>:<path>
: 產生覆蓋率測試報告 (<type>
指定格式,如 html
/ xml
;<path>
指定報告輸出目錄)--cov-config=<path>
: 覆蓋率測試的 config (預設是 .coveragerc
)--cov-branch
: 將覆蓋率計算方法設為分支覆蓋 (branch coverage)快速打開覆蓋率測試 HTML 報告 (Windows)
.coveragerc
no_cover
無效化某個 test case 所造成的覆蓋
插件:提供 Stub、Mock、Spy 等功能,擴充自標準庫的 unittest.mock
mocker
mocekr.Mock
: 建立一個 mock 物件
return_value
: 被呼叫時的回傳值。spec
AttributeError
。AttributeError
。side_effect
mocekr.MagicMock
: 建立一個 mock 物件 (已事先設定好 magic method),此類別繼承自 Mock
mocker.patch
: 全域補丁一個函式或變數
mocker.patch.object
: 針對某物件補丁一個方法或屬性
mocker.patch.multiple
: 針對某物件補丁多個方法或屬性
mocker.patch.dict
: 特別針對字典補丁 (採用 update,常用於環境變數)
mocker.stop
: 停止一個 mock 物件的功能
mocker.stopall
: 停止所有 mock 物件的功能
mocker.spy
: spy 功能 (僅做監控不做替代,MUT 的原功能仍會執行)
mocker.stub
: stub 功能 (常用於頂替 callback)
mocker.mock_open
: 模擬 builtins.open
的行為 (讓我們在測試時,不須去讀寫檔案)
mock 物件
mocker.?
都會回傳一個 mock 物件method | description |
---|---|
mock.assert_called() |
確保 mock 物件至少被呼叫一次 |
mock.assert_called_once() |
確保 mock 物件只被呼叫一次 |
mock.assert_called_with(*args, **kwargs) |
確保 mock 物件最後一次呼叫的參數符合 |
mock.assert_called_once_with(*args, **kwargs) |
確保 mock 物件只被呼叫一次,且參數完全匹配 |
mock.assert_any_call(*args, **kwargs) |
確保 mock 物件至少有一次以該參數呼叫 |
mock.assert_has_calls([call(*args, **kwargs), call(*args, **kwargs), ...], any_order=False) |
確保 mock 物件有一組特定的呼叫序列,可選擇順序是否無關。記得 from unittest.mock import call ! |
mock.assert_not_called() |
確保 mock 物件從未被呼叫過 |
mock.assert_call_count(n) |
確保 mock 物件被呼叫 n 次 |
case 1 : MUT 使用到了外部依賴
🚨 CAUTION |
---|
當 SUT 採用 from A import B 使用外部依賴時,mocker.patch 的路徑應填上 "SUT_path.B" ,否則會導致 SUT 沒有使用到你設定好的 mock 物件! |
case 2 : MUT 呼叫了 SUT 內的另一個方法