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)引發(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ò)誤。
您可以使用 ?pytest.warns
? 檢查代碼是否引發(fā)了特定警告。
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)行了特殊比較:
可以通過(guò)實(shí)現(xiàn)?pytest_assertrepr_compare
?鉤子來(lái)添加您自己的詳細(xì)解釋。
返回失敗的斷言表達(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
通過(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è)選擇:
PYTEST_DONT_REWRITE
?添加到其文檔字符串中,禁用特定模塊的重寫。assert=plain
?禁用所有模塊的重寫。
更多建議: