小门板儿

Menu

pytest–警告捕捉

警告捕捉

从3.1版本开始,pytest会在整个测试执行的过程中自动的捕捉警告:

def warn_api():
    warnings.warn(UserWarning("这里是warning信息"))
    return 1

def test_a():
    assert warn_api()==1

运行之后:

D:\pytest\learn01\a>pytest  test_warn.py
========================================================================================== test session starts ===========================================================================================
platform win32 -- Python 3.7.3, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\learn01, configfile: pytest.ini
collected 1 item                                                                       
test_warn.py .  [100%]
========================= warnings summary ==========================
a/test_warn.py::test_a
  D:\pytest\learn01\a\test_warn.py:5: UserWarning: 这里是warning信息
    warnings.warn(UserWarning("这里是warning信息"))
-- Docs: https://docs.pytest.org/en/stable/warnings.html
=================================== 1 passed, 1 warning in 0.10s ========================

传入 -W 标志可以将将要输出的警告转变为错误输出:

D:\pytest\learn01\a>pytest -q test_warn.py -W error::UserWarning
F  [100%]
================ FAILURES ========
____________________ test_a _________
    def test_a():
>       assert warn_api()==1
test_warn.py:9:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    def warn_api():
>       warnings.warn(UserWarning("这里是warning信息"))
E       UserWarning: 这里是warning信息

test_warn.py:5: UserWarning
============================ short test summary info =============================
FAILED test_warn.py::test_a - UserWarning: 这里是warning信息
1 failed in 0.12s

也可以通过在 pytest.ini 或 pyproject.toml 配置文件中设置filterwarnings来控制警告的输出。例如,下面的配置将会忽略所有的用户警告和一些满足正则表达式特定的弃用的警告,其他的警告会被转化为错误:

[pytest]
filterwarnings =
    error
    ignore::UserWarning

一、@pytest.mark.filterwarnings

可以使用 @pytest.mark.filterwarnings 给特定的项添加一个警告的过滤器,允许你对于那些测试,那些类甚至模块级别要过滤哪些警告提供了更好的支持:

import warnings
import pytest

def warn_api():
    warnings.warn(UserWarning("warn api,这里是warning信息"))
    return 1

@pytest.mark.filterwarnings("ignore:warn api")
def test_a():
    assert warn_api()==1

执行测试后,警告信息被过滤了

D:\pytest\learn01\a>pytest  test_warn.py
==================================================== test session starts =====================================================
platform win32 -- Python 3.7.3, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\learn01, configfile: pytest.ini
collected 1 item                                                                    
test_warn.py .  [100%]
=========================== 1 passed in 0.06s ================

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

pytestmark = pytest.mark.filterwarnings("error")

执行测试,这个模块中把所有的警告转化为错误

D:\pytest\learn01\a>pytest -q  test_warn.py

================= FAILURES ==================
_________________ test_a _____________________

    @pytest.mark.filterwarnings("ignore:warn api")
    def test_a():
>       assert warn_api()==1

test_warn.py:13:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def warn_api():
>       warnings.warn(UserWarning("warn api,这里是warning信息"))
E       UserWarning: warn api,这里是warning信息

test_warn.py:8: UserWarning
========= short test summary info ====================
FAILED test_warn.py::test_a - UserWarning: warn api,这里是warning信息
========= 1 failed in 0.10s ===============================

二、禁用警告汇总

可以在命令行中使用 --disable-warnings 来禁用所有的在输出中的警告汇总(不推荐)

D:\pytest\learn01\a>pytest   --disable-warnings  test_warn.py
========================================================================================== test session starts ===========================================================================================
platform win32 -- Python 3.7.3, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\learn01, configfile: pytest.ini
collected 1 item   
test_warn.py .   
========================= 1 passed, 1 warning in 0.03s ================

三、完全禁用警告捕获

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

[pytest]
addopts = -p no:warnings

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

四、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 中有一个这样的例子)

五、使用warns方法断言警告

你可以使用方法:pytest.warns 来检查代码是否引发了一个特定的警告,与raises的使用类似:

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

如果未发出相关警告,测试将失败。关键字参数 match 要断言异常与文本或regex匹配

>>> 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+

也可以在方法上或者代码块上使用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"

六、记录警告

可以使用函数pytest.warns 或 recwarn 夹具来记录警告的产生。为了记录警告,我们可以使用pytest.warns,传入一个None作为期待的警告类型即可

def test_warn1():
    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 夹具会记录整个函数的警告:pop(cls: Type[Warning] = <class 'Warning'>) → warnings.WarningMessage)弹出第一个记录的警告,如果不存在则引发异常。

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来获取特定的记录警告。

七、自定义错误信息

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

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

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

D:\pytest\learn01\a>pytest  -q test_warn1.py
================================= FAILURES ==================================
________________________________ test ____________________________________
    def test():
        with pytest.warns(Warning) as record:
            f()
            if not record:
>               pytest.fail("Expected a warning!")
E               Failed: Expected a warning!

test_warn1.py:55: Failed
--------------------- Captured stdout call ------------------
aaaaaaa
====================================== short test summary info ============
FAILED test_warn1.py::test - Failed: Expected a warning!
====================================== 1 failed in 0.13s ===========

八、内部的pytest警告

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

例如,如果Pytest遇到匹配的类,它将发出警告。 python_classes 但也定义了 __init__ 构造函数,因为这样可以防止类被实例化:

class Test:
    def __init__(self):
        pass

    def test_foo(self):
        assert 1 == 1
D:\pytest\learn01\a>pytest -q  test_warn1.py
======================================= warnings summary ========================================
test_warn1.py:57
  D:\pytest\learn01\a\test_warn1.py:57: PytestCollectionWarning: cannot collect test class 'Test' because it has a __init__ constructor (from: a/test_warn1.py)
    class Test:

-- Docs: https://docs.pytest.org/en/stable/warnings.html
1 warning in 0.05s

可以使用用于筛选其他类型警告的相同内置机制筛选这些警告

参考文献:https://www.osgeo.cn/pytest/warnings.html

— 于 共写了7245个字
— 标签:

评论已关闭。