pytest 測試輸出和結(jié)果-捕獲警告

2022-03-21 14:55 更新

從 3.1 版開始,pytest 現(xiàn)在會在測試執(zhí)行期間自動捕獲警告并在會話結(jié)束時顯示它們:

# 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

現(xiàn)在運行 pytest 會產(chǎn)生以下輸出:

$ pytest test_show_warnings.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item

test_show_warnings.py .                                              [100%]

============================= warnings summary =============================
test_show_warnings.py::test_one
  /home/sweet/project/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/how-to/capture-warnings.html
======================= 1 passed, 1 warning in 0.12s =======================

控制警告

與 Python 的警告過濾器和 ?-W? 選項標(biāo)志類似,pytest 提供了自己的 ?-W? 標(biāo)志來控制哪些警告被忽略、顯示或變成錯誤。

此代碼示例顯示如何將任何 ?UserWarning類別的警告視為錯誤:

$ 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

可以使用 ?filterwarnings ini? 選項在 ?pytest.ini? 或 ?pyproject.toml? 文件中設(shè)置相同的選項。 例如,下面的配置將忽略與正則表達(dá)式匹配的所有用戶警告和特定棄用警告,但會將所有其他警告轉(zhuǎn)換為錯誤。

# 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',
]

當(dāng)警告與列表中的多個選項匹配時,將執(zhí)行最后一個匹配選項的操作。

@pytest.mark.filterwarnings

您可以使用 ?@pytest.mark.filterwarnings? 將警告過濾器添加到特定的測試項目,從而使您可以更好地控制應(yīng)在測試、類甚至模塊級別捕獲哪些警告:

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

使用標(biāo)記應(yīng)用的過濾器優(yōu)先于通過命令行傳遞或由 ?filterwarnings ini? 選項配置的過濾器。

您可以通過使用 ?filterwarnings標(biāo)記作為類裝飾器將過濾器應(yīng)用于類的所有測試,或者通過設(shè)置 ?pytestmark變量將過濾器應(yīng)用于模塊中的所有測試:

# turns all warnings into errors for this module
pytestmark = pytest.mark.filterwarnings("error")

禁用警告摘要

盡管不推薦,但您可以使用 ?--disable-warnings? 命令行選項從測試運行輸出中完全抑制警告摘要。

完全禁用警告捕獲

此插件默認(rèn)啟用,但可以在您的 ?pytest.ini? 文件中完全禁用:

[pytest]
addopts = -p no:warnings

或在命令行中傳遞 ?-p no:warnings? 。 如果您的測試套件使用外部系統(tǒng)處理警告,這可能很有用。

DeprecationWarning和PendingDeprecationWarning

默認(rèn)情況下,pytest 將按照 PEP 565 的建議顯示來自用戶代碼和第三方庫的 ?DeprecationWarning和 ?PendingDeprecationWarning警告。這有助于用戶保持其代碼的現(xiàn)代性,并在有效刪除棄用的警告時避免損壞。

有時隱藏一些您無法控制的代碼(例如第三方庫)中發(fā)生的特定棄用警告很有用,在這種情況下,您可以使用警告過濾器選項(?ini或?marks?)來忽略這些警告。

例如:

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

這將忽略消息開頭與正則表達(dá)式?.*U.*mode is deprecated?匹配的所有 ?DeprecationWarning ?類型的警告。

如果在解釋器級別配置警告,使用 ?PYTHONWARNINGS環(huán)境變量或 ?-W? 命令行選項,pytest 默認(rèn)不會配置任何過濾器。

確保代碼觸發(fā)棄用警告

您還可以使用 ?pytest.deprecated_call()? 檢查某個函數(shù)調(diào)用是否觸發(fā)了 ?DeprecationWarning或 ?PendingDeprecationWarning?:

import pytest


def test_myfunction_deprecated():
    with pytest.deprecated_call():
        myfunction(17)

如果 ?myfunction在使用 17 參數(shù)調(diào)用時未發(fā)出棄用警告,則此測試將失敗。

使用 warns 函數(shù)斷言警告

您可以使用 ?pytest.warns()? 檢查代碼是否引發(fā)了特定警告,其工作方式與?raises?類似:

import warnings
import pytest


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

如果沒有提出相關(guān)的警告,則測試將失敗。關(guān)鍵字參數(shù)匹配,以斷言異常匹配文本或正則表達(dá)式:

>>> 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...were emitted...

您還可以在函數(shù)或代碼字符串上調(diào)用 ?pytest.warns()?:

pytest.warns(expected_warning, func, *args, **kwargs)
pytest.warns(expected_warning, "func(*args, **kwargs)")

該函數(shù)還返回所有引發(fā)警告的列表(作為 ?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"

或者,您可以使用?recwarn fixture?詳細(xì)檢查已發(fā)出的警告.

?recwarn fixture?自動確保在測試結(jié)束時重置警告過濾器,因此不會泄露全局狀態(tài)。

記錄警告

可以使用?pytest. warnings()?或?recwarn fixture?記錄發(fā)出的警告。

要使用?pytest. warnings()?記錄而不聲明任何關(guān)于警告的信息,請不要傳遞任何參數(shù)作為預(yù)期的警告類型,它將默認(rèn)為一個通用的?warning?:

with pytest.warns() 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 fixture?將整個功能的警告記錄為:

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. warnings()?都為記錄的警告返回相同的接口:一個?warnings recorder?實例。要查看記錄的警告,可以遍歷這個實例,對其調(diào)用?len?以獲得記錄的警告的數(shù)量,或者對其進(jìn)行索引以獲得特定的記錄的警告。

測試中警告的其他用例

以下是一些在測試中經(jīng)常出現(xiàn)的涉及警告的用例,以及如何處理它們的建議:

  • 要確保至少發(fā)出一個警告,請使用:

with pytest.warns():
    ...

  • 為確保不發(fā)出警告,請使用:

with warnings.catch_warnings():
    warnings.simplefilter("error")
    ...

  • 要抑制警告,請使用:

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    ...

自定義失敗消息

記錄警告提供了在未發(fā)出警告或滿足其他條件時生成自定義測試失敗消息的機(jī)會。

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

如果調(diào)用 ?f? 時沒有發(fā)出警告,則 ?not record? 將評估為True?。 然后,您可以使用自定義錯誤消息調(diào)用 ?pytest.fail()?。

Internal pytest warnings

pytest 在某些情況下可能會生成自己的警告,例如使用不當(dāng)或不推薦使用的功能。

例如,如果 ?pytest遇到與 ?python_classes匹配但還定義了 ?__init__? 構(gòu)造函數(shù)的類,它將發(fā)出警告,因為這會阻止該類被實例化:

# 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
  /home/sweet/project/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/how-to/capture-warnings.html
1 warning in 0.12s


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號