pytest 测试输出和结果-捕获警告

2022-03-21 14:55 更新

从 3.1 版开始,pytest 现在会在测试执行期间自动捕获警告并在会话结束时显示它们:

# content of test_show_warnings.py
import warnings


def api_v1():
    warnings.warn(UserWarning("api v1, should use functions from v2"))
    return 1


def test_one():
    assert api_v1() == 1

现在运行 pytest 会产生以下输出:

$ pytest test_show_warnings.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item

test_show_warnings.py .                                              [100%]

============================= warnings summary =============================
test_show_warnings.py::test_one
  /home/sweet/project/test_show_warnings.py:5: UserWarning: api v1, should use functions from v2
    warnings.warn(UserWarning("api v1, should use functions from v2"))

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================= 1 passed, 1 warning in 0.12s =======================

控制警告

与 Python 的警告过滤器和 ​-W​ 选项标志类似,pytest 提供了自己的 ​-W​ 标志来控制哪些警告被忽略、显示或变成错误。

此代码示例显示如何将任何 ​UserWarning类别的警告视为错误:

$ pytest -q test_show_warnings.py -W error::UserWarning
F                                                                    [100%]
================================= FAILURES =================================
_________________________________ test_one _________________________________

    def test_one():
>       assert api_v1() == 1

test_show_warnings.py:10:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    def api_v1():
>       warnings.warn(UserWarning("api v1, should use functions from v2"))
E       UserWarning: api v1, should use functions from v2

test_show_warnings.py:5: UserWarning
========================= short test summary info ==========================
FAILED test_show_warnings.py::test_one - UserWarning: api v1, should use ...
1 failed in 0.12s

可以使用 ​filterwarnings ini​ 选项在 ​pytest.ini​ 或 ​pyproject.toml​ 文件中设置相同的选项。 例如,下面的配置将忽略与正则表达式匹配的所有用户警告和特定弃用警告,但会将所有其他警告转换为错误。

# pytest.ini
[pytest]
filterwarnings =
    error
    ignore::UserWarning
    ignore:function ham\(\) is deprecated:DeprecationWarning
# pyproject.toml
[tool.pytest.ini_options]
filterwarnings = [
    "error",
    "ignore::UserWarning",
    # note the use of single quote below to denote "raw" strings in TOML
    'ignore:function ham\(\) is deprecated:DeprecationWarning',
]

当警告与列表中的多个选项匹配时,将执行最后一个匹配选项的操作。

@pytest.mark.filterwarnings

您可以使用 ​@pytest.mark.filterwarnings​ 将警告过滤器添加到特定的测试项目,从而使您可以更好地控制应在测试、类甚至模块级别捕获哪些警告:

import warnings


def api_v1():
    warnings.warn(UserWarning("api v1, should use functions from v2"))
    return 1


@pytest.mark.filterwarnings("ignore:api v1")
def test_one():
    assert api_v1() == 1

使用标记应用的过滤器优先于通过命令行传递或由 ​filterwarnings ini​ 选项配置的过滤器。

您可以通过使用 ​filterwarnings标记作为类装饰器将过滤器应用于类的所有测试,或者通过设置 ​pytestmark变量将过滤器应用于模块中的所有测试:

# turns all warnings into errors for this module
pytestmark = pytest.mark.filterwarnings("error")

禁用警告摘要

尽管不推荐,但您可以使用 ​--disable-warnings​ 命令行选项从测试运行输出中完全抑制警告摘要。

完全禁用警告捕获

此插件默认启用,但可以在您的 ​pytest.ini​ 文件中完全禁用:

[pytest]
addopts = -p no:warnings

或在命令行中传递 ​-p no:warnings​ 。 如果您的测试套件使用外部系统处理警告,这可能很有用。

DeprecationWarning和PendingDeprecationWarning

默认情况下,pytest 将按照 PEP 565 的建议显示来自用户代码和第三方库的 ​DeprecationWarning和 ​PendingDeprecationWarning警告。这有助于用户保持其代码的现代性,并在有效删除弃用的警告时避免损坏。

有时隐藏一些您无法控制的代码(例如第三方库)中发生的特定弃用警告很有用,在这种情况下,您可以使用警告过滤器选项(​ini或​marks​)来忽略这些警告。

例如:

[pytest]
filterwarnings =
    ignore:.*U.*mode is deprecated:DeprecationWarning

这将忽略消息开头与正则表达式​.*U.*mode is deprecated​匹配的所有 ​DeprecationWarning ​类型的警告。

如果在解释器级别配置警告,使用 ​PYTHONWARNINGS环境变量或 ​-W​ 命令行选项,pytest 默认不会配置任何过滤器。

确保代码触发弃用警告

您还可以使用 ​pytest.deprecated_call()​ 检查某个函数调用是否触发了 ​DeprecationWarning或 ​PendingDeprecationWarning​:

import pytest


def test_myfunction_deprecated():
    with pytest.deprecated_call():
        myfunction(17)

如果 ​myfunction在使用 17 参数调用时未发出弃用警告,则此测试将失败。

使用 warns 函数断言警告

您可以使用 ​pytest.warns()​ 检查代码是否引发了特定警告,其工作方式与​raises​类似:

import warnings
import pytest


def test_warning():
    with pytest.warns(UserWarning):
        warnings.warn("my warning", UserWarning)

如果没有提出相关的警告,则测试将失败。关键字参数匹配,以断言异常匹配文本或正则表达式:

>>> with warns(UserWarning, match='must be 0 or None'):
...     warnings.warn("value must be 0 or None", UserWarning)

>>> with warns(UserWarning, match=r'must be \d+$'):...     warnings.warn("value must be 42", UserWarning) 

>>> with warns(UserWarning, match=r'must be \d+$'): ...     warnings.warn("this is not here", UserWarning) Traceback (most recent call last): ... Failed: DID NOT WARN. No warnings of type ...UserWarning...were emitted...

您还可以在函数或代码字符串上调用 ​pytest.warns()​:

pytest.warns(expected_warning, func, *args, **kwargs)
pytest.warns(expected_warning, "func(*args, **kwargs)")

该函数还返回所有引发警告的列表(作为 ​warnings.WarningMessage​ 对象),您可以查询其他信息:

with pytest.warns(RuntimeWarning) as record:
    warnings.warn("another warning", RuntimeWarning)

# check that only one warning was raised
assert len(record) == 1
# check that the message matches
assert record[0].message.args[0] == "another warning"

或者,您可以使用​recwarn fixture​详细检查已发出的警告.

recwarn fixture​自动确保在测试结束时重置警告过滤器,因此不会泄露全局状态。

记录警告

可以使用​pytest. warnings()​或​recwarn fixture​记录发出的警告。

要使用​pytest. warnings()​记录而不声明任何关于警告的信息,请不要传递任何参数作为预期的警告类型,它将默认为一个通用的​warning​:

with pytest.warns() as record:
    warnings.warn("user", UserWarning)
    warnings.warn("runtime", RuntimeWarning)

assert len(record) == 2
assert str(record[0].message) == "user"
assert str(record[1].message) == "runtime"

recwarn fixture​将整个功能的警告记录为:

import warnings


def test_hello(recwarn):
    warnings.warn("hello", UserWarning)
    assert len(recwarn) == 1
    w = recwarn.pop(UserWarning)
    assert issubclass(w.category, UserWarning)
    assert str(w.message) == "hello"
    assert w.filename
    assert w.lineno

recwarn​和​pytest. warnings()​都为记录的警告返回相同的接口:一个​warnings recorder​实例。要查看记录的警告,可以遍历这个实例,对其调用​len​以获得记录的警告的数量,或者对其进行索引以获得特定的记录的警告。

测试中警告的其他用例

以下是一些在测试中经常出现的涉及警告的用例,以及如何处理它们的建议:

  • 要确保至少发出一个警告,请使用:

with pytest.warns():
    ...

  • 为确保不发出警告,请使用:

with warnings.catch_warnings():
    warnings.simplefilter("error")
    ...

  • 要抑制警告,请使用:

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    ...

自定义失败消息

记录警告提供了在未发出警告或满足其他条件时生成自定义测试失败消息的机会。

def test():
    with pytest.warns(Warning) as record:
        f()
        if not record:
            pytest.fail("Expected a warning!")

如果调用 ​f​ 时没有发出警告,则 ​not record​ 将评估为True​。 然后,您可以使用自定义错误消息调用 ​pytest.fail()​。

Internal pytest warnings

pytest 在某些情况下可能会生成自己的警告,例如使用不当或不推荐使用的功能。

例如,如果 ​pytest遇到与 ​python_classes匹配但还定义了 ​__init__​ 构造函数的类,它将发出警告,因为这会阻止该类被实例化:

# content of test_pytest_warnings.py
class Test:
    def __init__(self):
        pass

    def test_foo(self):
        assert 1 == 1
$ pytest test_pytest_warnings.py -q

============================= warnings summary =============================
test_pytest_warnings.py:1
  /home/sweet/project/test_pytest_warnings.py:1: PytestCollectionWarning: cannot collect test class 'Test' because it has a __init__ constructor (from: test_pytest_warnings.py)
    class Test:

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
1 warning in 0.12s


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

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号