如何使用 skip 和 xfail 處理無法成功的測試¶
你可以標記無法在特定平台上執行或你預期會失敗的測試函式,讓 pytest 能夠適當地處理它們,並提供測試階段的摘要,同時保持測試套件綠色。
skip 表示你預期你的測試只有在滿足某些條件時才會通過,否則 pytest 應該完全跳過執行測試。常見範例是在非 Windows 平台上跳過僅限 Windows 的測試,或跳過依賴於目前不可用的外部資源(例如資料庫)的測試。
xfail 表示你預期測試會因為某些原因失敗。常見範例是針對尚未實作的功能或尚未修正的錯誤進行測試。當測試通過,儘管預期會失敗(標記為 pytest.mark.xfail
),它就是xpass,並會在測試摘要中報告。
pytest
會分別計算並列出skip 和 xfail 測試。預設情況下,不會顯示跳過/失敗測試的詳細資訊,以避免輸出雜亂。你可以使用 -r
選項查看測試進度中顯示的「簡短」字母對應的詳細資訊
pytest -rxXs # show extra info on xfailed, xpassed, and skipped tests
可以在執行 pytest -h
時找到 -r
選項的更多詳細資訊。
(請參閱 內建組態檔選項)
跳過測試函式¶
跳過測試函式最簡單的方法是用 skip
裝飾器標記它,可以傳遞一個選用的 reason
@pytest.mark.skip(reason="no way of currently testing this")
def test_the_unknown(): ...
或者,也可以在測試執行或設定期間透過呼叫 pytest.skip(reason)
函式強制跳過
def test_function():
if not valid_config():
pytest.skip("unsupported configuration")
強制方法在無法在匯入期間評估跳過條件時很有用。
也可以在模組層級使用 pytest.skip(reason, allow_module_level=True)
跳過整個模組
import sys
import pytest
if not sys.platform.startswith("win"):
pytest.skip("skipping windows-only tests", allow_module_level=True)
skipif
¶
如果您希望有條件地跳過某些內容,則可以使用 skipif
代替。以下是一個範例,說明當在早於 Python3.10 的直譯器上執行時,如何標記測試函數以跳過
import sys
@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
def test_function(): ...
如果條件在收集期間評估為 True
,則會跳過測試函數,並在使用 -rs
時在摘要中顯示指定的理由。
您可以在模組之間共用 skipif
標記。考慮這個測試模組
# content of test_mymodule.py
import mymodule
minversion = pytest.mark.skipif(
mymodule.__versioninfo__ < (1, 1), reason="at least mymodule-1.1 required"
)
@minversion
def test_function(): ...
您可以匯入標記並在另一個測試模組中重複使用
# test_myothermodule.py
from test_mymodule import minversion
@minversion
def test_anotherfunction(): ...
對於較大的測試套件,通常建議有一個檔案來定義標記,然後在整個測試套件中一致地套用這些標記。
或者,您可以使用 條件字串 代替布林值,但它們無法輕鬆地在模組之間共用,因此主要基於向後相容性的理由提供支援。
跳過類別或模組的所有測試函數¶
您可以在類別上使用 skipif
標記(如同任何其他標記)
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
class TestPosixCalls:
def test_function(self):
"will not be setup or run under 'win32' platform"
如果條件為 True
,此標記將為該類別的每個測試方法產生跳過結果。
如果您要跳過模組的所有測試函數,可以使用 pytestmark
全域變數
# test_module.py
pytestmark = pytest.mark.skipif(...)
如果將多個 skipif
裝飾器套用到測試函數,則只要任何跳過條件為真,就會跳過該函數。
跳過檔案或目錄¶
有時您可能需要跳過整個檔案或目錄,例如,如果測試依賴於 Python 版本特定功能,或包含您不希望 pytest 執行的程式碼。這種情況下,您必須從收集中排除檔案和目錄。請參閱 自訂測試收集 以取得更多資訊。
跳過遺失的匯入相依性¶
您可以使用 pytest.importorskip 在模組層級、測試中或測試設定函式中跳過遺失的匯入相依性。
docutils = pytest.importorskip("docutils")
如果無法在此處匯入 docutils
,這將導致測試跳過結果。您也可以根據函式庫的版本號碼跳過
docutils = pytest.importorskip("docutils", minversion="0.3")
版本將從指定的模組 __version__
屬性中讀取。
摘要¶
以下是在不同情況下跳過模組中測試的快速指南
無條件跳過模組中的所有測試
pytestmark = pytest.mark.skip("all tests still WIP")
根據某些條件跳過模組中的所有測試
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="tests for linux only")
如果缺少某些匯入,則跳過模組中的所有測試
pexpect = pytest.importorskip("pexpect")
XFail:將測試函式標記為預期會失敗¶
您可以使用 xfail
標記來表示您預期測試會失敗
@pytest.mark.xfail
def test_function(): ...
此測試將執行,但當它失敗時不會報告追蹤。相反地,終端機報告會將它列在「預期會失敗」(XFAIL
) 或「意外通過」(XPASS
) 區段中。
或者,您也可以從測試或其設定函式中強制性地將測試標記為 XFAIL
def test_function():
if not valid_config():
pytest.xfail("failing configuration (but should work)")
def test_function2():
import slow_module
if slow_module.slow_function():
pytest.xfail("slow_module taking too long")
這兩個範例說明了您不希望在模組層級檢查條件的情況,也就是條件會針對標記評估的情況。
這會讓 test_function
XFAIL
。請注意,在 pytest.xfail()
呼叫之後不會執行其他程式碼,這與標記不同。這是因為它是透過引發已知例外狀況在內部實作的。
condition
參數¶
如果預期測試僅在特定條件下失敗,您可以將該條件傳遞為第一個參數
@pytest.mark.xfail(sys.platform == "win32", reason="bug in a 3rd party library")
def test_function(): ...
請注意,您也必須傳遞原因(請參閱 pytest.mark.xfail 中的參數說明)。
reason
參數¶
您可以使用 reason
參數指定預期失敗的動機
@pytest.mark.xfail(reason="known parser issue")
def test_function(): ...
raises
參數¶
如果您想要更具體地說明測試失敗的原因,您可以在 raises
參數中指定單一例外狀況或例外狀況組。
@pytest.mark.xfail(raises=RuntimeError)
def test_function(): ...
然後,如果測試失敗的原因是 raises
中未提及的例外狀況,則測試將報告為一般失敗。
run
參數¶
如果測試應該標記為 xfail 並報告為 xfail,但甚至不應該執行,請將 run
參數設為 False
@pytest.mark.xfail(run=False)
def test_function(): ...
這對於會讓直譯器當機且應該在稍後調查的 xfail 測試特別有用。
strict
參數¶
預設情況下,XFAIL
和 XPASS
都不會讓測試套件失敗。您可以透過將 strict
關鍵字限定參數設為 True
來變更這項設定
@pytest.mark.xfail(strict=True)
def test_function(): ...
這將使 XPASS
(“意外通過”)結果從此測試失敗測試套件。
您可以使用 xfail_strict
ini 選項變更 strict
參數的預設值
[pytest]
xfail_strict=true
忽略 xfail¶
透過在命令列中指定
pytest --runxfail
您可以強制執行和報告標記為 xfail
的測試,就像它根本沒有標記一樣。這也會導致 pytest.xfail()
沒有產生任何效果。
範例¶
以下是包含多種用法的簡單測試檔案
import pytest
xfail = pytest.mark.xfail
@xfail
def test_hello():
assert 0
@xfail(run=False)
def test_hello2():
assert 0
@xfail("hasattr(os, 'sep')")
def test_hello3():
assert 0
@xfail(reason="bug 110")
def test_hello4():
assert 0
@xfail('pytest.__version__[0] != "17"')
def test_hello5():
assert 0
def test_hello6():
pytest.xfail("reason")
@xfail(raises=IndexError)
def test_hello7():
x = []
x[1] = 1
使用 report-on-xfail 選項執行它會產生這個輸出
! pytest -rx xfail_demo.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/example
collected 7 items
xfail_demo.py xxxxxxx [100%]
========================= short test summary info ==========================
XFAIL xfail_demo.py::test_hello
XFAIL xfail_demo.py::test_hello2
reason: [NOTRUN]
XFAIL xfail_demo.py::test_hello3
condition: hasattr(os, 'sep')
XFAIL xfail_demo.py::test_hello4
bug 110
XFAIL xfail_demo.py::test_hello5
condition: pytest.__version__[0] != "17"
XFAIL xfail_demo.py::test_hello6
reason: reason
XFAIL xfail_demo.py::test_hello7
============================ 7 xfailed in 0.12s ============================
使用 parametrize 跳過/xfail¶
在使用 parametrize 時,可以將標記(例如 skip 和 xfail)套用至個別測試執行個體
import sys
import pytest
@pytest.mark.parametrize(
("n", "expected"),
[
(1, 2),
pytest.param(1, 0, marks=pytest.mark.xfail),
pytest.param(1, 3, marks=pytest.mark.xfail(reason="some bug")),
(2, 3),
(3, 4),
(4, 5),
pytest.param(
10, 11, marks=pytest.mark.skipif(sys.version_info >= (3, 0), reason="py2k")
),
],
)
def test_increment(n, expected):
assert n + 1 == expected