pytest 测试输出和结果-使用skip和xfail处理无法成功的测试

2022-03-21 15:23 更新

您可以标记不能在某些平台上运行的测试函数,或者您预计会失败的测试函数,以便pytest可以相应地处理它们,并显示测试会话的摘要,同时保持测试套件为绿色。

skip​意味着您希望您的测试只有在满足某些条件时才会通过,否则pytest应该完全跳过运行测试。常见的例子是跳过非windows平台上的仅限windows的测试,或者跳过依赖于当前不可用的外部资源(例如数据库)的测试。

xfail​意味着您预期测试由于某种原因会失败。一个常见的例子是对尚未实现的特性或尚未修复的bug进行测试。当测试通过时,尽管预期会失败(标记为​pytest.mark.xfail​),它是一个​xpass​,并将在测试总结中报告。

Pytest分别计数和列出​skip​和​xfail​测试。默认情况下,不显示有关​skip​/​xfailed​测试的详细信息,以避免输出混乱。你可以使用​-r​选项来查看测试进度中显示的​short​字母对应的详细信息:

pytest -rxXs  # show extra info on xfailed, xpassed, and skipped tests

通过运行 ​pytest -h​ 可以找到有关 ​-r选项的更多详细信息。

跳过测试功能

跳过一个测试函数的最简单的方法是用​skip​装饰器来标记它,它可以传递一个可选的原因:

@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 ​装饰器应用于测试函数,则如果任何跳过条件为​true​,它将被跳过。

跳过文件或目录

有时您可能需要跳过整个文件或目录,例如,如果测试依赖于 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():
    ...

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():
    ...

这对于导致解释器崩溃的失败测试特别有用,应该稍后进行调查。

strict参数

默认情况下,​XFAIL和 ​XPASS都不会使测试套件失败。 您可以通过将 ​strict keyword-only​ 参数设置为 ​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 ============================

使用参数化skip/xfail

使用参数化时,可以将skipxfail等标记应用于单个测试实例:

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


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号