对于在某些平台上不能运行、或预期会失败的测试函数,可以添加标记,让 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)
参考:pytest.mark.skip。
skipif
需要按条件跳过时,使用 skipif。下面的测试在低于 Python 3.13 的解释器上运行时会被跳过:
import sys
@pytest.mark.skipif(sys.version_info < (3, 13), reason="requires python3.13 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 时,该类的每个测试方法都会产生 skip 结果。要跳过模块内的全部测试函数,可使用全局变量 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")
这两个例子适用于不希望在模块级检查条件的情形;使用标记时,条件原本会在模块级求值。
调用后,测试会成为 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,同时完全不执行测试,将 run 设为 False:
@pytest.mark.xfail(run=False)
def test_function(): ...
对会使解释器崩溃、需要以后再调查的测试,这个选项尤其有用。
strict 参数
默认情况下,XFAIL 和 XPASS 都不会让测试套件失败。将仅限关键字参数 strict 设为 True 可以改变这一点:
@pytest.mark.xfail(strict=True)
def test_function(): ...
这样,该测试的 XPASS(意外通过)结果会使测试套件失败。
可以使用 strict_xfail 配置项改变 strict 的默认值;xfail_strict 是其兼容别名。原文给出以下两种配置。
TOML:
[pytest]
xfail_strict = true
INI:
[pytest]
strict_xfail = 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
用报告 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
使用参数化时,可以为单个测试实例添加 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
原文:pytest:How to use skip and xfail to deal with tests that cannot succeed。作者:Holger Krekel 与 pytest-dev 团队。本版本翻译正文并调整版式;代码与原文输出保持原文。采用 MIT 许可。
The MIT License (MIT)
Copyright (c) 2004 Holger Krekel and others
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.











暂无评论内容