天天看點

pytest-捕獲告警資訊

1.告警資訊的預設捕獲行為

pytest可以自動捕獲測試中産生的告警資訊,在測試結束後進行展示

import warnings
def api_v1():
    warnings.warn(UserWarning('請使用新版本的api'))
    return 1

def test_one():
    assert api_v1() == 1
           

傳回結果:

D:\pytest\exercise\chapter8>pytest -q test_show_warning.py
.                                                                                                                                                   [100%]
==================================================================== warnings summary ====================================================================
test_show_warning.py::test_one
  D:\pytest\exercise\chapter8\test_show_warning.py:7: UserWarning: 請使用新版本的api
    warnings.warn(UserWarning('請使用新版本的api'))

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

           

可以通過-W arg、指令行選項來自定義告警的捕獲:

arg參數的格式為:

action:message:catagory:module:lineno;
           
  • action:隻能在error、ignore、always(all)、default、module、once中取值,預設取值為default
  • category必須是warning的子類,預設取值為Warning類,表示所有的告警
  • module必須為字元串,表示特定子產品産生的告警資訊

一些常見的使用場景:

  • 忽略某一種類型的告警資訊,例如會略UserWarning類型的告警(-W ignore::UserWarning)
D:\pytest\exercise\chapter8>pytest -W ignore::UserWarning test_show_warning.py
================================================================= test session starts ==================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8
plugins: allure-pytest-2.8.36
collected 1 item

test_show_warning.py .                                                                                                                            [100%]

================================================================== 1 passed in 0.07s ============================================================
           
  • 将一種類型的告警轉換為異常來處理,例如:将UserWaarning告警轉換為異常處理(-W error::Userwarning)
D:\pytest\exercise\chapter8>pytest -W error::UserWarning test_show_warning.py
================================================================== test session starts ===================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8
plugins: allure-pytest-2.8.36
collected 1 item                                                                                                                                          

test_show_warning.py F                                                                                                                              [100%]

======================================================================== FAILURES ========================================================================
________________________________________________________________________ test_one ________________________________________________________________________

   def test_one():
>       assert api_v1() == 1

test_show_warning.py:11:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

   def api_v1():
>       warnings.warn(UserWarning('請使用新版本的api'))
E       UserWarning: 請使用新版本的api

test_show_warning.py:7: UserWarning
================================================================ short test summary info =================================================================
FAILED test_show_warning.py::test_one - UserWarning: 請使用新版本的api
=================================================================== 1 failed in 0.11s ====================================================================
           
  • 隻展示某一個子產品中産生的告警,例如:隻展示test_show_warnings子產品中産生的告警資訊,忽略其他的告警(-W ignore -W default:::test_show_warnings)
D:\pytest\exercise>pytest -W ignore -W default:::test_show_warnings chapter8/
================================================================= test session starts ==================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise
plugins: allure-pytest-2.8.36
collected 1 item                                                                                                                                        

chapter8\test_show_warning.py .                                                                                                                   [100%]

================================================================== 1 passed in 0.03s ===================================================================
           

這裡我們示範了多個-W選項的組合操作,優先級是從左到右依次遞增的;這裡如果将它們調換一下順序(即-W default:::test_show_warnings -W ignore),因為-W ignore最後生效,覆寫掉之前的操作,最終的結果就是我們一個告警資訊都沒有捕獲到;

  • 也可以通過在pytest.ini檔案中配置filterwarnings選項,來實作同樣的操作。
[pytest]
filterwarnings =
    ignore
    default:::test_show_warnings
           

2. @pytest.mark.filterwarnings

上述操作是通過指令行執行的,使用起來多有不便,如何能夠在用例、類、子產品級别上自定義告警的捕獲行為?可通過為測試項添加告警過濾器實作。

pytest.ini中定義的配置,禁止了除了test_show_warning子產品外,其他的所有告警捕獲行為。

現在新增用例, 禁止捕獲由它所觸發的使用者告警:

@pytest.mark.filterwarnings('ignore::UserWarning')
def test_two():
    assert api_v1() ==1
           

測試結果:

D:\pytest\exercise\chapter8>pytest -k "test_two"
================================================================= test session starts ==================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8, configfile: pytest.ini
plugins: allure-pytest-2.8.36
collected 2 items / 1 deselected / 1 selected                                                                                                           

test_show_warning.py .                                                                                                                            [100%]

=========================================================== 1 passed, 1 deselected in 0.05s ============================================================
           

測試test_one:

D:\pytest\exercise\chapter8>pytest -k "test_one"
================================================================= test session starts ==================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8, configfile: pytest.ini
plugins: allure-pytest-2.8.36
collected 2 items / 1 deselected / 1 selected                                                                                                           

test_show_warning.py .                                                                                                                            [100%]

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

我們可以通過将@pytest.mark.filterwarnings應用于測試類為類中每一個測試用例添加過濾器。也可以通過設定pytestmark變量為整個測試子產品中所有的用例添加告警過濾器:

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

3. 取消告警資訊的展示

D:\pytest\exercise\chapter8>pytest -k "test_one" --disable-warnings
================================================================= test session starts ==================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8, configfile: pytest.ini
plugins: allure-pytest-2.8.36
collected 2 items / 1 deselected / 1 selected                                                                                                           

test_show_warning.py .                                                                                                                            [100%]

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

4. 不使用告警的捕獲功能

上小節中隻是不展示捕獲到的告警資訊,我們可以通過- p no::warnings指令禁止告警的捕獲行為。

D:\pytest\exercise\chapter8>pytest -k "test_one" -p no:warnings test_show_warning.py
================================================================= test session starts ==================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8, configfile: pytest.ini
plugins: allure-pytest-2.8.36
collected 2 items / 1 deselected / 1 selected                                                                                                           

test_show_warning.py .                                                                                                                            [100%]

=========================================================== 1 passed, 1 deselected in 0.02s ============================================================
           

5. DeprecationWarning和PendingDeprecationWarning告警

pytest會預設捕獲DeprecationWarning和PendingDeprecationWarning類型的告警。有時候,你并不需要這種行為,可以通過在pytest.ini添加配置;例如,忽略告警資訊比對".*U.*mode is deprecated"的DeprecationWarning告警:

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

5.1 pytest.deprecated_call方法

可以通過deprecated_call方法確定一段代碼觸發DeprecationWarning或者PendingDeprecationWarning告警。

import warnings
import pytest

def api_call_v1():
    warnings.warn("v1版本已經棄用,請使用v2版本的api")

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

傳回結果:

D:\pytest\exercise\chapter8>pytest test_deprecation.py
================================================================= test session starts ==================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8, configfile: pytest.ini
plugins: allure-pytest-2.8.36
collected 1 item                                                                                                                                        

test_deprecation.py F                                                                                                                             [100%]

======================================================================= FAILURES =======================================================================
___________________________________________________________________ test_deprecation ___________________________________________________________________

    def test_deprecation():
>       assert pytest.deprecated_call(api_call_v1()) == 200
E       assert WarningsChecker(record=True) == 200
E        +  where WarningsChecker(record=True) = <function deprecated_call at 0x0000018F59D06280>(None)
E        +    where <function deprecated_call at 0x0000018F59D06280> = pytest.deprecated_call
E        +    and   None = api_call_v1()

test_deprecation.py:12: AssertionError
=============================================================== short test summary info ================================================================
FAILED test_deprecation.py::test_deprecation - assert WarningsChecker(record=True) == 200
================================================================== 1 failed in 0.09s ===================================================================
           

deprecated_call也支援上下文管理器的寫法:

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

6. 編寫觸發期望告警的斷言

可以使用pytest.warns()作為上下文管理器,編寫一個觸發期望告警的斷言,和pytest.raises()的用法很接近。

deprecaed_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_deprecation3():
    with pytest.warns((DeprecationWarning,PendingDeprecationWarning),match=r'v1版本已廢棄'):
        assert api_call_v1() == 200
           

測試結果:

D:\pytest\exercise\chapter8>pytest -k test_deprecation3 test_deprecation.py
================================================================= test session starts ==================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8, configfile: pytest.ini
plugins: allure-pytest-2.8.36
collected 3 items / 2 deselected / 1 selected                                                                                                           

test_deprecation.py F                                                                                                                             [100%]

======================================================================= FAILURES =======================================================================
__________________________________________________________________ test_deprecation3 ___________________________________________________________________

    def test_deprecation3():
        with pytest.warns((DeprecationWarning,PendingDeprecationWarning),match=r'v1版本已廢棄'):
>           assert api_call_v1() == 200
E           assert None == 200
E            +  where None = api_call_v1()

test_deprecation.py:20: AssertionError
=============================================================== short test summary info ================================================================
FAILED test_deprecation.py::test_deprecation3 - assert None == 200
=========================================================== 1 failed, 2 deselected in 0.12s ============================================================
           
  • 我們也可以直接傳遞可低啊用對象,表達式傳回執行這個可調用對象的結果:
def test_deprecation4():
    assert pytest.warns((DeprecationWarning,PendingDeprecationWarning), api_call_v1,
                      match=r'和 pytest.raises() 方法一樣,這時 pytest 不再判斷告警資訊是否正确') == 200
           

測試結果:

D:\pytest\exercise\chapter8>pytest -k test_deprecation4 test_deprecation.py
================================================================== test session starts ===================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8, configfile: pytest.ini
plugins: allure-pytest-2.8.36
collected 4 items / 3 deselected / 1 selected                                                                                                             

test_deprecation.py F                                                                                                                               [100%]

======================================================================== FAILURES ========================================================================
___________________________________________________________________ test_deprecation4 ____________________________________________________________________

    def test_deprecation4():
>       assert pytest.warns((DeprecationWarning,PendingDeprecationWarning), api_call_v1,
                          match=r'和 pytest.raises() 方法一樣,這時 pytest 不再判斷告警資訊是否正确') == 200
E       Failed: DID NOT WARN. No warnings of type (<class 'DeprecationWarning'>, <class 'PendingDeprecationWarning'>) was emitted. The list of emitted warn
ings is: [UserWarning('v1版本已經棄用,請使用v2版本的api')].

test_deprecation.py:24: Failed
================================================================ short test summary info =================================================================
FAILED test_deprecation.py::test_deprecation4 - Failed: DID NOT WARN. No warnings of type (<class 'DeprecationWarning'>, <class 'PendingDeprecationWarni...

============================================================ 1 failed, 3 deselected in 0.10s =============================================================
           
  • pytest.warns()可以傳回一個清單,包含所有捕獲到的告警對象 (warnings.WarningMessage)
import re
def test_deprecation5():
    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])
           

傳回結果:

D:\pytest\exercise\chapter8>pytest -k test_deprecation5 test_deprecation.py
================================================================= test session starts ==================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8, configfile: pytest.ini
plugins: allure-pytest-2.8.36
collected 5 items / 4 deselected / 1 selected                                                                                                           

test_deprecation.py F                                                                                                                             [100%]

======================================================================= FAILURES =======================================================================
__________________________________________________________________ test_deprecation5 ___________________________________________________________________

    def test_deprecation5():
        with pytest.warns((DeprecationWarning,PendingDeprecationWarning)) as records:
>           assert api_call_v1() == 200
E           assert None == 200
E            +  where None = api_call_v1()

test_deprecation.py:31: AssertionError
=============================================================== short test summary info ================================================================
FAILED test_deprecation.py::test_deprecation5 - assert None == 200
=========================================================== 1 failed, 4 deselected in 0.11s ============================================================
           

實際上傳回的不是一個清單。隻是實作了__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.2 自定義失敗時的提示消息

def test_deprecation6():
    with pytest.warns(Warning) as records:
        rsp = api_call_v1()
        if not records:
            pytest.fail('期望apu_call_v1 觸發告警,實際上沒有')
        assert rsp == 200
           

測試結果:

D:\pytest\exercise\chapter8>pytest -k test_deprecation6 test_deprecation.py
================================================================= test session starts ==================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8, configfile: pytest.ini
plugins: allure-pytest-2.8.36
collected 6 items / 5 deselected / 1 selected                                                                                                           

test_deprecation.py F                                                                                                                             [100%]

======================================================================= FAILURES =======================================================================
__________________________________________________________________ test_deprecation6 ___________________________________________________________________

    def test_deprecation6():
        with pytest.warns(Warning) as records:
            rsp = api_call_v1()
            if not records:
                pytest.fail('期望apu_call_v1 觸發告警,實際上沒有')
>           assert rsp == 200
E           assert None == 200

test_deprecation.py:41: AssertionError
=============================================================== short test summary info ================================================================
FAILED test_deprecation.py::test_deprecation6 - assert None == 200
=========================================================== 1 failed, 5 deselected in 0.09s ============================================================
           

7. recwarn fixture

recwarn是一個用例級别的fixture,可以用于記錄用例産生的所有告警。

def test_reprecation7(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])
           

測試結果:

D:\pytest\exercise\chapter8>pytest -k test_deprecation7 test_deprecation.py
================================================================= test session starts ==================================================================
platform win32 -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\exercise\chapter8, configfile: pytest.ini
plugins: allure-pytest-2.8.36
collected 7 items / 7 deselected                                                                                                                        

================================================================ 7 deselected in 0.03s =================================================================
           

pytest自定義的告警類型

pytest自定義的告警類型:

pytest-捕獲告警資訊