assert 與例外測試
pytest 用 Python 內建的 assert 敘述判斷測試是否通過,並在失敗時自動顯示比較雙方的實際值;預期程式「應該拋出例外」時則用 pytest.raises。本頁整理比對一般值、浮點數、例外與警告的寫法。
用 assert 比對結果
任何結果為真(truthy)的運算式都可以放在 assert 後面:
def test_assert_examples():
assert 1 + 1 == 2
assert "py" in "pytest"
assert [1, 2, 3] == [1, 2, 3]
assert {"a": 1}.get("b") is None
assert len([]) == 0
比對串列、字典、長字串失敗時,pytest 會列出差異之處,不用自己逐項比對:
E AssertionError: assert {'name': 'Amy', 'age': 30} == {'name': 'Amy', 'age': 31}
E Differing items:
E {'age': 30} != {'age': 31}
需要補充說明時,可以在逗號後加上訊息,失敗時會一併顯示:
一個測試驗證一件事
一個測試可以有多個 assert,但最好都在驗證同一個行為。第一個 assert 失敗後,後面的不會執行;若想分別知道每個條件的結果,就拆成多個測試或改用 參數化。
浮點數比對:pytest.approx
浮點數有精度誤差,0.1 + 0.2 == 0.3 在 Python 中是 False。用 pytest.approx 比對「足夠接近」:
import pytest
def test_float():
assert 0.1 + 0.2 == pytest.approx(0.3)
assert [0.1 + 0.2, 0.2 + 0.4] == pytest.approx([0.3, 0.6])
assert 10.05 == pytest.approx(10, abs=0.1) # 指定絕對誤差
預設容許的相對誤差是 1e-6,也能用 rel= 或 abs= 調整。
測試例外:pytest.raises
「輸入錯誤時要拋出 ValueError」本身就是一個需要測試的行為。把預期會出錯的程式碼放進 with pytest.raises(...) 區塊:
import pytest
from shop import apply_discount
def test_invalid_percent_raises():
with pytest.raises(ValueError):
apply_discount(100, 150)
區塊內若沒有拋出 ValueError,測試就失敗。
如何同時驗證例外訊息?
用 match= 參數比對錯誤訊息。match 是正規表示式(regular expression),訊息中有 .、( 等特殊字元時要用 re.escape() 轉義:
def test_invalid_percent_message():
with pytest.raises(ValueError, match="0 到 100"):
apply_discount(100, -5)
需要檢查例外物件的其他屬性時,用 as 取得 ExceptionInfo:
def test_exception_info():
with pytest.raises(ValueError) as excinfo:
apply_discount(100, 150)
assert "percent" in str(excinfo.value)
區塊只放會出錯的那一行
with pytest.raises 區塊裡如果放了好幾行,可能是「錯的那一行」先拋出相同類型的例外,測試仍然通過,卻沒有測到想測的東西。
測試警告:pytest.warns
程式用 warnings.warn() 發出棄用(deprecation)等警告時,用 pytest.warns 驗證:
import warnings
import pytest
def old_api():
warnings.warn("old_api 已棄用,請改用 new_api", DeprecationWarning)
return 1
def test_old_api_warns():
with pytest.warns(DeprecationWarning, match="已棄用"):
assert old_api() == 1
推薦影音
pytest: testing exceptions
pytest 核心開發者 Anthony Sottile 約 12 分鐘講解如何用 pytest 測試「程式應該拋出例外」的情況,對應本頁「測試例外」一節。