pytest 测试输出和结果-捕获警告
从 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