zoukankan      html  css  js  c++  java
  • 8、pytest -- 捕获告警信息

    pytest 3.1版本新增特性

    1. 告警信息的默认捕获行为

    pytest可以自动捕获测试中产生的告警信息,并在执行结束后进行展示;

    下面这个例子,我们在测试中人为的产生一条告警:

    # src/chapter-8/test_show_warning.py
    
    import warnings
    
    
    def api_v1():
        warnings.warn(UserWarning('请使用新版本的API。'))
        return 1
    
    
    def test_one():
        assert api_v1() == 1
    

    我们也可以通过-W arg命令行选项来自定义告警的捕获行为:

    arg参数的格式为:action:message:category:module:lineno

    • action只能在"error", "ignore", "always(all)", "default", "module", "once"中取值,默认取值为default
    • category必须是Warning的子类,默认取值为Warning类,表示所有的告警;
    • module必须为字符串,表示特定模块产生的告警信息;

    下面是一些常见的使用场景:

    • 忽略某一种类型的告警信息;例如,忽略UserWarning类型的告警(-W ignore::UserWarning):

      λ pipenv run pytest -W ignore::UserWarning src/chapter-8/test_show_warnings.py
      ============================ test session starts ============================= 
      platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
      rootdir: D:Personal FilesProjectspytest-chinese-doc
      collected 1 item
      
      srcchapter-8	est_show_warnings.py .                                   [100%]
      
      ============================= 1 passed in 0.02s ==============================
      
    • 将某一种类型的告警转换为异常来处理;例如,将UserWarning告警转换为异常处理(-W error::UserWarning):

      λ pipenv run pytest -W error::UserWarning src/chapter-8/test_show_warnings.py
      
      ============================ test session starts ============================= 
      platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
      rootdir: D:Personal FilesProjectspytest-chinese-doc
      collected 1 item
      
      srcchapter-8	est_show_warnings.py F                                   [100%]
      
      ================================== FAILURES ================================== 
      __________________________________ test_one __________________________________
      
          def test_one():
      >       assert api_v1() == 1
      
      srcchapter-8	est_show_warnings.py:31:
      _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
      
          def api_v1():
      >       warnings.warn(UserWarning('请使用新版本的API。'))
      E       UserWarning: 请使用新版本的API。
      
      srcchapter-8	est_show_warnings.py:26: UserWarning
      ============================= 1 failed in 0.05s ==============================
      
    • 只展示某一个模块中产生的告警;例如,只展示test_show_warnings模块产生的告警,忽略其它所有的告警(-W ignore -W default:::test_show_warnings):

      λ pipenv run pytest -W ignore -W default:::test_show_warnings src/chapter-8/
      ============================ test session starts ============================= 
      platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
      rootdir: D:Personal FilesProjectspytest-chinese-doc
      collected 1 item
      
      srcchapter-8	est_show_warnings.py .                                   [100%]
      
      ============================== warnings summary ============================== 
      src/chapter-8/test_show_warnings.py::test_one
        D:Personal FilesProjectspytest-chinese-docsrcchapter-8	est_show_warnings.py:26: UserWarning: 请使用新版本的API。
          warnings.warn(UserWarning('请使用新版本的API。'))
      
      -- Docs: https://docs.pytest.org/en/latest/warnings.html
      ======================= 1 passed, 1 warnings in 0.03s ========================
      

      这里我们演示了多个-W选项的组合操作,优先级是从左到右依次递增的;这里如果将它们调换一下顺序(即-W default:::test_show_warnings -W ignore),因为-W ignore最后生效,覆盖掉之前的操作,最终的结果就是我们一个告警信息都没有捕获到;

    • 我们也可以通过在pytest.ini文件中配置filterwarnings项,来实现同样的效果;例如,上述的例子在pytest.ini的配置为:

      # src/chapter-8/pytest.ini
      
      [pytest]
      filterwarnings =
          ignore
          default:::test_show_warnings
      

      不带-W选项执行:

      λ pipenv run pytest src/chapter-8/
      ============================ test session starts ============================= 
      platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
      rootdir: D:Personal FilesProjectspytest-chinese-docsrcchapter-8, inifile: pytest.ini
      collected 1 item
      
      srcchapter-8	est_show_warnings.py .                                   [100%]
      
      ============================== warnings summary ============================== 
      test_show_warnings.py::test_one
        D:Personal FilesProjectspytest-chinese-docsrcchapter-8	est_show_warnings.py:26: UserWarning: 请使用新版本的API。
          warnings.warn(UserWarning('请使用新版本的API。'))
      
      -- Docs: https://docs.pytest.org/en/latest/warnings.html
      ======================= 1 passed, 1 warnings in 0.04s ========================
      

    -W其实是python本身自带的命令行选项,你可以通过访问官方文档以了解更多:https://docs.python.org/3.7/library/warnings.html#warning-filter

    2. @pytest.mark.filterwarnings

    上述操作我们是在命令行上实现的,如果想要在用例、类甚至是模块级别上自定义告警的捕获行为,上面的方法就不是很便利了;这里,我们可以通过为测试项添加告警过滤器来实现这种需求;

    还记得在上一章中pytest.ini中的配置吗?我们禁止了除test_show_warnings模块外,其它所有告警的捕获行为;现在,我们在这个模块中新加一个用例test_two,禁止捕获由它所触发的用户告警:

    # src/chapter-8/test_show_warning.py
    
    @pytest.mark.filterwarnings('ignore::UserWarning')
    def test_two():
        assert api_v1() == 1
    

    执行这个用例:

    λ pipenv run pytest -k "test_two" src/chapter-8/
    ============================ test session starts ============================= 
    platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
    rootdir: D:Personal FilesProjectspytest-chinese-docsrcchapter-8, inifile: pytest.ini
    collected 2 items / 1 deselected / 1 selected
    
    srcchapter-8	est_show_warnings.py .                                   [100%]
    
    ====================== 1 passed, 1 deselected in 0.03s =======================
    

    我们没有捕获任何告警信息,这说明通过@pytest.mark.filterwarnings添加的过滤器优先级要高于命令行或pytest.ini添加的过滤器;你也可以通过执行test_one用例来对比它们之间的不同;

    我们可以通过将@pytest.mark.filterwarnings应用于测试类来为这个类中所有的用例添加告警过滤器;

    也可以通过设置pytestmark变量为整个测试模块中所有的用例添加告警过滤器;例如,将模块中所有的告警转换为异常处理:

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

    3. 去使能告警信息的展示

    我们可以通过--disable-warnings命令行选项来禁止告警信息的展示;例如,我们在测试输出中不展示test_one用例所产生到的告警信息:

    λ pipenv run pytest -k "test_one" --disable-warnings src/chapter-8/
    ============================ test session starts ============================= 
    platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
    rootdir: D:Personal FilesProjectspytest-chinese-docsrcchapter-8, inifile: pytest.ini
    collected 2 items / 1 deselected / 1 selected
    
    srcchapter-8	est_show_warnings.py .                                   [100%]
    
    ================ 1 passed, 1 deselected, 1 warnings in 0.03s =================
    

    4. 去使能告警的捕获行为

    上一章我们只是不展示捕获到的告警信息,这里我们可以通过-p no:warnings命令行选项彻底禁止告警的捕获行为:

    λ pipenv run pytest -k "test_one" -p no:warnings src/chapter-8/
    ============================ test session starts ============================= 
    platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
    rootdir: D:Personal FilesProjectspytest-chinese-docsrcchapter-8, inifile: pytest.ini
    collected 2 items / 1 deselected / 1 selected
    
    srcchapter-8	est_show_warnings.py .                                   [100%]
    
    ====================== 1 passed, 1 deselected in 0.03s =======================
    

    如果你足够细心的话,你可以看到它们的区别:

    ================ 1 passed, 1 deselected, 1 warnings in 0.03s =================
    

    ====================== 1 passed, 1 deselected in 0.03s =======================
    

    5. DeprecationWarningPendingDeprecationWarning告警

    遵循PEP-0565的建议,pytest会默认捕获DeprecationWarningPendingDeprecationWarning类型的告警;

    有时候,你并不需要这种行为,可以通过在pytest.ini添加配置;例如,忽略告警信息匹配".*U.*mode is deprecated"DeprecationWarning告警:

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

    注意:

    如果已经在python解释器中配置了告警选项,那么pytest不会再添加任何默认的告警过滤器;这一点,可以在pytest的源码中得到证实:

    # _pytest/warnings.py
    
        if not sys.warnoptions:
               # if user is not explicitly configuring warning filters, show deprecation warnings by default (#2908)
               warnings.filterwarnings("always", category=DeprecationWarning)
               warnings.filterwarnings("always", category=PendingDeprecationWarning)
    

    pytest issue #2908https://github.com/pytest-dev/pytest/issues/2908

    5.1. pytest.deprecated_call方法

    我们可以通过deprecated_call方法确保一段代码触发了DeprecationWarningPendingDeprecationWarning告警:

    # src/chapter-8/test_deprecation.py
    
    import warnings
    import pytest
    
    
    def api_call_v1():
        warnings.warn('v1版本已废弃,请使用v2版本的api;', DeprecationWarning)
        return 200
    
    
    def test_deprecation():
        assert pytest.deprecated_call(api_call_v1) == 200
    

    同时,deprecated_call也支持上下文管理器的写法,所以上面的例子也可以写成:

    def test_deprecation():
        with pytest.deprecated_call():
            assert api_call_v1() == 200
    

    6. 编写触发期望告警的断言

    我们可以使用pytest.warns()作为上下文管理器,来编写一个触发期望告警的断言,它和pytest.raises()的用法很接近;

    在正式开始之前,我们来看一下上一节中deprecated_call方法的源码:

    # _pytest/warnings.py
    
    def deprecated_call(func=None, *args, **kwargs):
        __tracebackhide__ = True
        if func is not None:
            args = (func,) + args
        return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs)
    

    可以看到,deprecated_call也不过是pytest.warns()的封装,区别在于其指定了具体期望的告警类型;

    现在,我们来具体看一下pytest.warns()的用法(以上一节的例子说明):

    • 我们可以为其传递一个关键字参数match,判断捕获到的告警信息是否匹配既定的正则表达式:

      def test_deprecation():
          with pytest.warns((DeprecationWarning, PendingDeprecationWarning), match=r'v1版本已废弃'):
              assert api_call_v1() == 200
      
    • 我们也可以直接传递可调用对象,表达式返回执行这个可调用对象的结果:

      def test_deprecation():
          assert pytest.warns((DeprecationWarning, PendingDeprecationWarning), api_call_v1, match=r'和 pytest.raises() 方法一样,这时 pytest 不再判断告警信息是否正确') == 200
      
      

      注意:和pytest.raises()一样,此时match参数不再生效;

    • pytest.warns()可以返回一个列表,包含所有捕获到的告警对象(warnings.WarningMessage):

      import re
      
      def test_deprecation():
          with pytest.warns((DeprecationWarning, PendingDeprecationWarning)) as records:
              assert api_call_v1() == 200
          assert len(records) == 1
          assert re.search(r'v1版本已废弃', records[0].message.args[0])
      

      实际上,其返回的并不是一个列表,只是实现了__getitem__()__len__()方法的普通类,其内部本身有一个_list的私有属性用于存储所有的数据;

    学习这一章节最好的办法就是结合pytest.warns()的源码一起看,上面所有的用法和特性都可以体现在里面:

    # _pytest/recwarn.py
     
    def warns(
        expected_warning: Union["Type[Warning]", Tuple["Type[Warning]", ...]],
        *args: Any,
        match: Optional[Union[str, "Pattern"]] = None,
        **kwargs: Any
    ) -> Union["WarningsChecker", Any]:
    
        __tracebackhide__ = True
        if not args:
            if kwargs:
                msg = "Unexpected keyword arguments passed to pytest.warns: "
                msg += ", ".join(sorted(kwargs))
                msg += "
    Use context-manager form instead?"
                raise TypeError(msg)
            return WarningsChecker(expected_warning, match_expr=match)
        else:
            func = args[0]
            if not callable(func):
                raise TypeError(
                    "{!r} object (type: {}) must be callable".format(func, type(func))
                )
            with WarningsChecker(expected_warning):
                return func(*args[1:], **kwargs)
    

    6.1. 自定义失败时的提示消息

    当我们使用一段代码,期望其触发告警时,我们可以通过一下方法,自定义失败时的提示消息,增加其可读性:

    def test_deprecation():
        with pytest.warns(Warning) as records:
            rsp = api_call_v1()
            if not records:
                pytest.fail('期望 api_call_v1 触发一个告警,实际上没有;')
            assert rsp == 200
    

    如果api_call_v1没有触发任何告警,pytest就会显示pytest.fail中自定义的提示消息;

    7. recwarn fixture

    上一章的最后,我们通过接收pytest.warns()的返回值来记录捕获到的所有告警;在这一章,我们可以通过recwarn来实现同样的功能;

    recwarn是一个用例级别的fixture,它可以记录用例产生的所有的告警;

    同样,重写之前的例子来说明:

    import re
    
    def test_deprecation(recwarn):
        api_call_v1()
        assert len(recwarn) == 1
        w = recwarn.pop()  # 不指定告警类型的话,默认弹出最先捕获的告警
        assert issubclass(w.category, (DeprecationWarning, PendingDeprecationWarning))
        assert re.search(r'v1版本已废弃', w.message.args[0])
    

    recwarn和之前pytest.warns()返回值一样,都是一个WarningsRecorder的实例;

    8. pytest自定义的告警类型

    pytest本身封装了一些告警的类型,并作为公共接口以供用户使用;

    下面列举了一些常见的内部告警:

    告警 父类 描述
    PytestWarning UserWarning 所有告警的父类;
    PytestCollectionWarning PytestWarning 不能够收集某个模块中的用例;
    PytestConfigWarning PytestWarning 配置错误;
    PytestUnknownMarkWarning PytestWarning 使用了未知的标记;

    更多的内部告警可以查看:https://docs.pytest.org/en/5.1.3/warnings.html#pytest.PytestWarning

    GitHub仓库地址:https://github.com/luizyao/pytest-chinese-doc

  • 相关阅读:
    Codeforces Round #417 C. Sagheer and Nubian Market
    linux 终端抓包命令
    计算机网络体系结构分析
    排序算法-快速排序
    排序算法-堆排序
    排序算法-希尔排序
    排序算法-插入排序
    排序算法-冒泡排序
    排序算法-选择排序
    杂谈:终端小工具
  • 原文地址:https://www.cnblogs.com/luizyao/p/11771585.html
Copyright © 2011-2022 走看看