pytest官方文档 6.2 中文翻译版(第十章):警告捕捉

本文档介绍了pytest 6.2中关于警告捕捉的特性,包括使用@ pytest.mark.filterwarnings装饰器、禁用警告、完全禁用警告捕获、处理DeprecationWarning和PendingDeprecationWarning,以及如何断言和记录警告。此外,还讨论了pytest对内部警告的处理和自定义错误信息的设定。
摘要由CSDN通过智能技术生成

从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 的官方文档中查看更多的例子和建议使用方法。

10.1 @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

使用mark来定义过滤器会比在命令行中或配置文件中设置过滤器拥有更高的优先级。
你可以使用filterwarnings标记修饰类给一个类中的所有的测试设置一个过滤器,或者设置一个pytestmark设置整个模块的标记:

# 在这个模块中把所有的警告转化为错误
pytestmark = pytest.mark.filterwarnings("error")

感谢 Florian Schulze 在pytest-warnings 插件中的参考实现。

10.2 禁用警告汇总

虽然不推荐,但是你还是可以在命令行中使用 --disable-warnings 来禁用所有的在输出中的警告汇总。

10.3 完全禁用警告捕获

这个插件是默认打开的,但是可以被完全关闭,我们需要在pytest.ini文件中设置:

[pytest]
addopts = -p no:warnings

或者在命令行中传递一个 -p no:warnings。如果你的测试使用一个外部系统来处理警告,这种方式是十分有用的。

10.4 DeprecationWarning 和 PendingDeprecationWarning

在默认情况下,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 中有一个这样的例子)

10.5 测试代码引发了一个已弃用的警告

你可以使用 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 夹具会自动的在测试结束的时候重置警告过滤器,所以,全局的设置不会改变。

10.6 使用warns方法断言警告

你可以使用方法: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

10.7 记录警告

你可以使用函数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

10.8 自定义错误信息

记录警告给我们提供了一个机会在没有警告发生的时候产生一个测试失败的信息,或者其他的情况。

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

当调用f的时候,如果没有警告被调用则not record为True。你可以调用 pytest.fail(),参数填写一个自定义的错误信息。

10.9 内部的pytest警告

在一些情况下,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中列出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值