从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-6.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_show_warnings.py . [100%]
============================= warnings summary =============================
test_show_warnings.py::test_one
$REGENDOC_TMPDIR/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/warnings.html
======================= 1 passed, 1 warning in 0.12s =======================
传入 -W 标志可以将将要输出的警告转变为错误输出:
$ 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
我们也可以通过在 pytest.ini 或 pyproject.toml 配置文件中设置filterwarnings来控制警告的输出。例如,下面的配置将会忽略所有的用户警告和一些满足正则表达式特定的弃用的警告,其他的警告会被转化为错误:
# 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', ]
如果警告满足不止一个配置,最后一个匹配的配置会被采用。-W选项和ini配置文件中filterwarnings是使用Python自己的-W和warnings.simplefilte实现的,所以你可以在 Python 的官方文档中查看更多的例子和建议使用方法。
你可以使用 @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
使用mark来定义过滤器会比在命令行中或配置文件中设置过滤器拥有更高的优先级。
你可以使用filterwarnings标记修饰类给一个类中的所有的测试设置一个过滤器,或者设置一个pytestmark设置整个模块的标记:
# 在这个模块中把所有的警告转化为错误
pytestmark = pytest.mark.filterwarnings("error")
感谢 Florian Schulze 在pytest-warnings 插件中的参考实现。
虽然不推荐,但是你还是可以在命令行中使用 --disable-warnings 来禁用所有的在输出中的警告汇总。
这个插件是默认打开的,但是可以被完全关闭,我们需要在pytest.ini文件中设置:
[pytest]
addopts = -p no:warnings
或者在命令行中传递一个 -p no:warnings。如果你的测试使用一个外部系统来处理警告,这种方式是十分有用的。
在默认情况下,pytest会像PEP-0565建议的那样,用户代码或者第三方插件引发的 DeprecationWarning 和 PendingDeprecationWarning都会显示。这将帮助用户保持代码的更新,避免已弃用的警告被删除带来的问题。
一些情况下,我们需要将一些特定的弃用警告隐藏起来,这种需要的原因是一些代码我们并没有控制权(例如第三方库),在这种情况下你可能需要使用警告过滤器(ini 或者 标记)来忽略哪些警告。
例如:
[pytest]
filterwarnings =
ignore:.*U.*mode is deprecated:DeprecationWarning
这将使用正则表达式的方式,忽略警告信息的开头符合表达式 ".*U.*mode is deprecated,以忽略所有类型为 DeprecationWarning 的警告。
注意:如果在警告被配置在解释器层面,使用 PYTHONWARNINGS 环境变量或者-W命令行参数,pytest会默认不配置任何的警告过滤器。pytest没有遵从 PEP-0506 的意见重置所有的警告过滤器,因为这样可能影响哪些通过调用 warnings.simplefilter 来配置警告过滤器的测试。(issue #2430 中有一个这样的例子)
你可以使用 pytest.deprecated_call() 来检查一个函数是不是触发了一个 DeprecationWarning 或 PendingDeprecationWarning:
import pytest
def test_myfunction_deprecated():
with pytest.deprecated_call():
myfunction(17)
当 myfunction 使用17作为参数调用的时候,如果没有引发一个已弃用的警告,则测试就会失败。默认情况下DeprecationWarning 和 PendingDeprecationWarning 不会被 pytest.warns() 或者 recwarn 捕获,因为默认的pytest警告过滤器过滤了它们。如果你想在你的代码中记录它们,使用 warnings.simplefilter(‘always’):
import warnings
import pytest
def test_deprecation(recwarn):
warnings.simplefilter("always")
myfunction(17)
assert len(recwarn) == 1
assert recwarn.pop(DeprecationWarning)
recwarn 夹具会自动的在测试结束的时候重置警告过滤器,所以,全局的设置不会改变。
你可以使用方法:pytest.warns 来检查代码是否引发了一个特定的警告,与raises的使用类似:
import warnings
import pytest
def test_warning():
with pytest.warns(UserWarning):
warnings.warn("my warning", UserWarning)
如果特定的警告没有引发,测试就会失败。加上keyword参数会断言异常是不是与特定的文本或者正则表达式匹配:
>>> 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... was 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)
# 断言只有一个警告被引发
assert len(record) == 1
# 断言警告信息是不是匹配
assert record[0].message.args[0] == "another warning"
或者,你可以使用 recwarn 夹具来检查警告的详细信息(看下面):
注意:DeprecationWarning 和 PendingDeprecationWarning会被不同的对待,查看10.5
你可以使用函数pytest.warns 或 recwarn 夹具来记录警告的产生。为了记录警告,我们可以使用pytest.warns,传入一个None作为期待的警告类型即可:
with pytest.warns(None) 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 夹具会记录整个函数的警告:
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.warns 方法都返回了一个相同的接口:WarningsRecorder对象。你可以迭代这个对象,也可以使用len方法获取警告的个数,或者通过index来获取特定的记录警告。
完整API:WarningsRecorder
记录警告给我们提供了一个机会在没有警告发生的时候产生一个测试失败的信息,或者其他的情况。
def test():
with pytest.warns(Warning) as record:
f()
if not record:
pytest.fail("Expected a warning!")
当调用f的时候,如果没有警告被调用则not record为True。你可以调用 pytest.fail(),参数填写一个自定义的错误信息。
在一些情况下,pytest会产生它自己内部的井盖,像是一些不恰当的使用或者已经弃用的功能。例如,如果pytest遇到一个类,符合 python_classes 的配置但是定义了一个 init 构造函数,因为这个构造函数会组织类的实例化:
译者注:正常情况下,pytest只会收集以Test_ 或者 test_开头的类,但是我们可以在pytest.ini中通过设置python_classes来让pytest收集更多符合某种特定格式的类(注意这里必须是class)
# 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
$REGENDOC_TMPDIR/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/warnings.html
1 warning in 0.12s
这些警告与我们之前讲的其他类型的警告使用同一套过滤机制被内建的过滤器过滤。
请阅读我们的 向后兼容策略 学习我们如何进行弃用,并最终移除功能。
警告的全部列表在之后的reference documentation中列出。