八、pytest -- 捕獲告警信息

pytest 3.1版本新增特性python

1. 告警信息的默認捕獲行爲

pytest能夠自動捕獲測試中產生的告警信息,並在執行結束後進行展現;git

下面這個例子,咱們在測試中人爲的產生一條告警:github

# 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:linenoshell

  • action只能在"error", "ignore", "always(all)", "default", "module", "once"中取值,默認取值爲default
  • category必須是Warning的子類,默認取值爲Warning類,表示全部的告警;
  • module必須爲字符串,表示特定模塊產生的告警信息;

下面是一些常見的使用場景:api

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

    λ 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 Files\Projects\pytest-chinese-doc
    collected 1 item
    
    src\chapter-8\test_show_warnings.py .                                   [100%]
    
    ============================= 1 passed in 0.02s ==============================
  • 將某一種類型的告警轉換爲異常來處理;例如,將UserWarning告警轉換爲異常處理(-W error::UserWarning):ide

    λ 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 Files\Projects\pytest-chinese-doc
    collected 1 item
    
    src\chapter-8\test_show_warnings.py F                                   [100%]
    
    ================================== FAILURES ================================== 
    __________________________________ test_one __________________________________
    
        def test_one():
    >       assert api_v1() == 1
    
    src\chapter-8\test_show_warnings.py:31:
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    
        def api_v1():
    >       warnings.warn(UserWarning('請使用新版本的API。'))
    E       UserWarning: 請使用新版本的API。
    
    src\chapter-8\test_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 Files\Projects\pytest-chinese-doc
    collected 1 item
    
    src\chapter-8\test_show_warnings.py .                                   [100%]
    
    ============================== warnings summary ============================== 
    src/chapter-8/test_show_warnings.py::test_one
      D:\Personal Files\Projects\pytest-chinese-doc\src\chapter-8\test_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 Files\Projects\pytest-chinese-doc\src\chapter-8, inifile: pytest.ini
    collected 1 item
    
    src\chapter-8\test_show_warnings.py .                                   [100%]
    
    ============================== warnings summary ============================== 
    test_show_warnings.py::test_one
      D:\Personal Files\Projects\pytest-chinese-doc\src\chapter-8\test_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 Files\Projects\pytest-chinese-doc\src\chapter-8, inifile: pytest.ini
collected 2 items / 1 deselected / 1 selected

src\chapter-8\test_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 Files\Projects\pytest-chinese-doc\src\chapter-8, inifile: pytest.ini
collected 2 items / 1 deselected / 1 selected

src\chapter-8\test_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 Files\Projects\pytest-chinese-doc\src\chapter-8, inifile: pytest.ini
collected 2 items / 1 deselected / 1 selected

src\chapter-8\test_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 += "\nUse 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

相關文章
相關標籤/搜索