pytest 核心功能-在測(cè)試中編寫和報(bào)告斷言

2022-03-18 09:51 更新

使用assert語(yǔ)句進(jìn)行斷言

pytest 允許您使用標(biāo)準(zhǔn) Python 斷言來(lái)驗(yàn)證 Python 測(cè)試中的期望和值。 例如,您可以編寫以下內(nèi)容:

# content of test_assert1.py
def f():
    return 3


def test_function():
    assert f() == 4

斷言您的函數(shù)返回某個(gè)值。 如果此斷言失敗,您將看到函數(shù)調(diào)用的返回值:

$ pytest test_assert1.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_assert1.py F                                                    [100%]

================================= FAILURES =================================
______________________________ test_function _______________________________

    def test_function():
>       assert f() == 4
E       assert 3 == 4
E        +  where 3 = f()

test_assert1.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_assert1.py::test_function - assert 3 == 4
============================ 1 failed in 0.12s =============================

Pytest支持顯示最常用的子表達(dá)式的值,包括調(diào)用、屬性、比較以及二進(jìn)制和一元操作符。這允許您在不使用樣板代碼的情況下使用慣用的python構(gòu)造,同時(shí)不丟失內(nèi)省信息。

但是,如果您使用這樣的斷言指定消息:

assert a % 2 == 0, "value was odd, should be even"

然后,根本不會(huì)發(fā)生任何斷言內(nèi)省,消息將簡(jiǎn)單地顯示在回溯中。

關(guān)于預(yù)期異常的斷言

為了編寫有關(guān)引發(fā)異常的斷言,您可以使用 ?pytest.raises()? 作為上下文管理器,如下所示:

import pytest


def test_zero_division():
    with pytest.raises(ZeroDivisionError):
        1 / 0

如果你需要訪問(wèn)實(shí)際的異常信息,你可以使用:

def test_recursion_depth():
    with pytest.raises(RuntimeError) as excinfo:

        def f():
            f()

        f()
    assert "maximum recursion" in str(excinfo.value)

?excinfo?是一個(gè)?ExceptionInfo?實(shí)例,它包裝了實(shí)際引發(fā)的異常。interest的主要屬性是?.type?、?.value?和?.traceback?

您可以向上下文管理器傳遞一個(gè)?match?關(guān)鍵字參數(shù),以測(cè)試正則表達(dá)式是否匹配異常的字符串表示(類似于 ?unittest ?中的 ?TestCase.assertRaisesRegex? 方法):

import pytest


def myfunc():
    raise ValueError("Exception 123 raised")


def test_match():
    with pytest.raises(ValueError, match=r".* 123 .*"):
        myfunc()

?match ?方法的 ?regexp ?參數(shù)與 ?re.search? 函數(shù)匹配,因此在上面的示例中 ?match='123'? 也可以正常工作。

?pytest.raises()? 函數(shù)還有另一種形式,您可以在其中傳遞一個(gè)函數(shù),該函數(shù)將使用給定的 ?*args? 和 ?**kwargs? 執(zhí)行,并斷言引發(fā)了給定的異常:

pytest.raises(ExpectedException, func, *args, **kwargs)

如果出現(xiàn)無(wú)異常或錯(cuò)誤異常等故障,?reporter ?將為您提供有用的輸出。

請(qǐng)注意,也可以為 ?pytest.mark.xfail? 指定一個(gè)?raises?參數(shù),它以更具體的方式檢查測(cè)試是否失敗,而不僅僅是引發(fā)任何異常:

@pytest.mark.xfail(raises=IndexError)
def test_f():
    f()

使用 ?pytest.raises()? 對(duì)于您正在測(cè)試自己的代碼故意引發(fā)的異常的情況可能會(huì)更好,而使用帶有檢查功能的?@pytest.mark.xfail? 可能更適合記錄未修復(fù)的錯(cuò)誤(其中測(cè)試描述了應(yīng)該發(fā)生什么)或依賴項(xiàng)中的錯(cuò)誤。

關(guān)于預(yù)期警告的斷言

您可以使用 ?pytest.warns? 檢查代碼是否引發(fā)了特定警告。

利用上下文相關(guān)的比較

Pytest具有豐富的支持,可以在遇到比較時(shí)提供上下文敏感的信息。例如:

# content of test_assert2.py
def test_set_comparison():
    set1 = set("1308")
    set2 = set("8035")
    assert set1 == set2

如果你運(yùn)行這個(gè)模塊:

$ pytest test_assert2.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_assert2.py F                                                    [100%]

================================= FAILURES =================================
___________________________ test_set_comparison ____________________________

    def test_set_comparison():
        set1 = set("1308")
        set2 = set("8035")
>       assert set1 == set2
E       AssertionError: assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'}
E         Extra items in the left set:
E         '1'
E         Extra items in the right set:
E         '5'
E         Use -v to get more diff

test_assert2.py:4: AssertionError
========================= short test summary info ==========================
FAILED test_assert2.py::test_set_comparison - AssertionError: assert {'0'...
============================ 1 failed in 0.12s =============================

對(duì)一些情況進(jìn)行了特殊比較:

  • 比較長(zhǎng)字符串:顯示上下文差異
  • 比較長(zhǎng)序列:第一個(gè)失敗的索引
  • 比較字典:不同的條目

為失敗的斷言定義你自己的解釋

可以通過(guò)實(shí)現(xiàn)?pytest_assertrepr_compare?鉤子來(lái)添加您自己的詳細(xì)解釋。

pytest_assertrepr_compare(config, op, left, right)

返回失敗的斷言表達(dá)式中比較的解釋。

如果沒有自定義解釋,則返回?None?,否則返回一個(gè)字符串列表。字符串將由換行符連接,但字符串中的任何換行符將被轉(zhuǎn)義。請(qǐng)注意,除第一行外的所有內(nèi)容都稍微縮進(jìn),目的是將第一行作為摘要。

參數(shù):

  • ?config (pytest.Config)? -- pytest 配置對(duì)象
  • ?op (str)? –
  • ?left (object)? –
  • ?right (object)? –

返回類型:

Optional[List[str]]

例如,可以考慮在?conftest.py?文件中添加以下鉤子,它提供了對(duì)?Foo?對(duì)象的另一種解釋:

# content of conftest.py
from test_foocompare import Foo


def pytest_assertrepr_compare(op, left, right):
    if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
        return [
            "Comparing Foo instances:",
            "   vals: {} != {}".format(left.val, right.val),
        ]

現(xiàn)在,給定這個(gè)測(cè)試模塊:

# content of test_foocompare.py
class Foo:
    def __init__(self, val):
        self.val = val

    def __eq__(self, other):
        return self.val == other.val


def test_compare():
    f1 = Foo(1)
    f2 = Foo(2)
    assert f1 == f2

你可以運(yùn)行?test?模塊,并獲得在?conftest?文件中定義的自定義輸出:

$ pytest -q test_foocompare.py
F                                                                    [100%]
================================= FAILURES =================================
_______________________________ test_compare _______________________________

    def test_compare():
        f1 = Foo(1)
        f2 = Foo(2)
>       assert f1 == f2
E       assert Comparing Foo instances:
E            vals: 1 != 2

test_foocompare.py:12: AssertionError
========================= short test summary info ==========================
FAILED test_foocompare.py::test_compare - assert Comparing Foo instances:
1 failed in 0.12s

斷言內(nèi)省細(xì)節(jié)

通過(guò)在?assert?語(yǔ)句運(yùn)行之前重寫它們,可以報(bào)告關(guān)于失敗斷言的詳細(xì)信息。重寫的斷言語(yǔ)句將自省信息放入斷言失敗消息中。pytest只重寫由其測(cè)試收集過(guò)程直接發(fā)現(xiàn)的測(cè)試模塊,因此在不屬于測(cè)試模塊的支持模塊中的斷言不會(huì)被重寫。

您可以在導(dǎo)入模塊之前通過(guò)調(diào)用 ?register_assert_rewrite ?手動(dòng)為導(dǎo)入的模塊啟用斷言重寫(這樣做的好地方是在您的根目錄 ?conftest.py? 中)。

斷言重寫將文件緩存到硬盤上

pytest 會(huì)將重寫的模塊寫回磁盤進(jìn)行緩存。 您可以通過(guò)將其添加到 ?conftest.py? 文件的頂部來(lái)禁用此行為(例如,避免在經(jīng)常移動(dòng)文件的項(xiàng)目中留下陳舊的? .pyc? 文件):

import sys

sys.dont_write_bytecode = True

請(qǐng)注意,您仍然可以獲得斷言自省的好處,唯一的變化是 ?.pyc? 文件不會(huì)緩存在磁盤上。

此外,如果無(wú)法寫入新的 ?.pyc? 文件,即在只讀文件系統(tǒng)或 zip 文件中,重寫將靜默跳過(guò)緩存。

禁用斷言重寫

pytest 在導(dǎo)入時(shí)重寫測(cè)試模塊,方法是使用導(dǎo)入鉤子編寫新的 ?pyc? 文件。大多數(shù)情況下,這是透明的。如果您自己使用導(dǎo)入,導(dǎo)入鉤子可能會(huì)干擾。

如果是這種情況,你有兩個(gè)選擇:

  • 通過(guò)將字符串?PYTEST_DONT_REWRITE?添加到其文檔字符串中,禁用特定模塊的重寫。
  • 使用?assert=plain?禁用所有模塊的重寫。


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)