如何使用 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
有关 -r
选项的更多详细信息,可以通过运行 pytest -h
找到。
(请参阅 内置配置文件选项)
跳过测试函数¶
跳过测试函数最简单的方法是使用 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)
**参考**:pytest.mark.skip
skipif
¶
如果您希望有条件地跳过某些内容,可以使用 skipif
。这是一个标记测试函数在早于 Python 3.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(): ...
对于大型测试套件,通常最好有一个文件来定义标记,然后您可以在整个测试套件中一致地应用这些标记。
另外,您可以使用 条件字符串 而不是布尔值,但它们不容易在模块之间共享,因此支持它们主要是为了向后兼容性原因。
**参考**:pytest.mark.skipif
跳过类或模块的所有测试函数¶
您可以在类上使用 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()
之后不会执行其他代码。这是因为它在内部是通过引发一个已知异常来实现的。
**参考**:pytest.mark.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
参数¶
如果一个测试应被标记为预期失败并如此报告,但甚至不应被执行,请将 run
参数设置为 False
。
@pytest.mark.xfail(run=False)
def test_function(): ...
这对于预期失败的测试(导致解释器崩溃,需要稍后调查)特别有用。
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()
不产生任何效果。
示例¶
这是一个包含多种用法的简单测试文件。
from __future__ import annotations
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 使用 Skip/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