斷言的編寫和報告
使用assert語句進行斷言
pytest允許你使用標准的Pythonassert
斷言語句來驗證測試中的期望結果和實際結果。 例如,你可以編寫以下內容:
# test_assert1.py文件內容
def f():
return 3
def test_function():
assert f() == 4
來斷言你的函數返回一個特定的值。 如果此斷言失敗,你將看到函數調用的返回值:
$ pytest test_assert1.py
=========================== test session starts ============================
platform linux -- Python 3.x.y,pytest-3.x.y,py-1.x.y,pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR,inifile:
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:5: AssertionError
========================= 1 failed in 0.12 seconds =========================
Pytest
支持顯示常見的包括調用,屬性,比較以及二元和一元運算符子表達式的值 (參考: pytest執行Python測試失敗報告示例)。 你可以在不使用繁瑣的Python慣用構造樣板代碼的同時,不丟失斷言失敗的對比信息(內省信息)。
當然,你也可以像下面所示,指定斷言失敗的返回消息:
assert a % 2 == 0,"值為奇數,應為偶數"
這樣將不會斷言失敗對比信息(內省信息),而只簡單地在追溯信息中顯示你指定的失敗返回信息。
有關斷言內省的更多信息,請參閱高級斷言內省。
異常斷言
你可以像如下所示,使用pytest.raises
作為上下文管理器來進行異常斷言:
import pytest
def test_zero_division():
with pytest.raises(ZeroDivisionError):
1 / 0
如果需要訪問實際的異常信息,你可以使用:
def test_recursion_depth():
with pytest.raises(RuntimeError) as excinfo:
def f():
f()
f()
assert 'maximum recursion' in str(excinfo.value)
excinfo
是一個ExceptionInfo
實例,它是實際異常的裝飾器。 其主要屬性有.type
,.value
及.traceback
三種
版本3.0已修改
在上下文管理器中,你可以使用參數message
來指定自定義失敗信息:
>>> with raises(ZeroDivisionError,message="Expecting ZeroDivisionError"):
... pass
... Failed: Expecting ZeroDivisionError
如果你想編寫適用於Python 2.4的測試代碼,你還可以使用其他兩種方法來測試預期的異常:
pytest.raises(ExpectedException,func,*args,**kwargs)
pytest.raises(ExpectedException,"func(*args,**kwargs)")
兩者都可以對帶任意參數的函數,斷言是否出現了期望的異常:ExpectedException
。 即使沒有異常或出現了不同的異常,報告生成器也能輸出一些有用的斷言信息。
注意,也可以為pytest.mark.xfail
指定一個“raises”參數,當引發異常時標記用例失敗:
@pytest.mark.xfail(raises=IndexError)
def test_f():
f()
對於你在代碼中故意設置的異常,使用pytest.raises
斷言更加好用,而將@ pytest.mark.xfail
與check函數一起使用對於已知未修復或依賴中的bug會更好。
此外,上下文管理器表單接受match
關鍵字參數來測試正則表達式匹配中的異常(如unittest
中的TestCase.assertRaisesRegexp
方法):
import pytest
def myfunc():
raise ValueError("Exception 123 raised")
def test_match():
with pytest.raises(ValueError,match=r'.* 123 .*'):
myfunc()
match
變量后的正則表達式與使用re.search
函數來進行匹配一致。 因此在上面的例子中,match ='123'
不會引發異常。
警示斷言
2.8版本新增
你可以使用pytest.warns
檢查代碼是否引發了特定警告。
使用上下文對比
2.0版本新增
Pytest
可以在斷言的比較中提供豐富的上下文信息。 例如:
# test_assert2.py文件內容
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
assert set1 == set2
當你運行這個模塊后
$ pytest test_assert2.py
=========================== test session starts ============================
platform linux -- Python 3.x.y,pytest-3.x.y,py-1.x.y,pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR,inifile:
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 the full diff
test_assert2.py:5: AssertionError
========================= 1 failed in 0.12 seconds =========================
對大量用例進行了特定對比:
- 長字符串斷言:顯示上下文差異
- 長序列斷言:顯示第一個失敗的索引
- 字典斷言:顯示不同的key-value對
有關更多示例,請參閱 報告樣例。
自定義斷言對比信息
可以通過實現hook
方法pytest_assertrepr_compare
來在斷言結果中添加你自己的詳細說明信息。
**pytest_assertrepr_compare(config,op,left,right)*- [源碼]
返回失敗斷言表達式中的對比信息。
如果沒有自定義對比信息,則返回None,否則返回一列字符串。 字符串將由換行符連接,但字符串中的任何換行符都將被轉義。 請注意,除第一行外的所有行都將略微縮進,目的是將第一行作為摘要。
參數: config(pytest.config.Config* - pytest config 對象
例如,在conftest.py文件中添加以下鈎子(Hook)方法,可以為Foo
對象提供了附加對比信息:
# 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 ['Foo實例對比:',
' 值: %s != %s' % (left.val,right.val)]
現在,在測試模塊使用
# test_foocompare.py內容
class Foo(object):
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
運行這個測試模塊你可以看到conftest.py
文件中定義的自定義輸出:
$ pytest -q test_foocompare.py
F [100%]
================================= FAILURES =================================
_______________________________ test_compare _______________________________
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
> assert f1 == f2
E assert Foo實例對比:
E 值: 1 != 2
test_foocompare.py:11: AssertionError
1 failed in 0.12 seconds
高級斷言內省
2.1版本新函數
報告有關失敗斷言的詳細信息是通過在運行之前重寫assert語句來實現的。 重寫的斷言語句將內省信息放入斷言失敗消息中。 Pytest
只重寫測試收集過程直接發現的測試模塊中的assert斷言,因此在支持模塊(非測試模塊)中的斷言,不會被重寫。
你可以在導入模塊前通過調用register_assert_rewrite
手動啟用斷言重寫(比如可以在conftest.py
這樣使用)。
注意
Pytest
通過使用導入hook方法寫入新的pyc
文件來重寫測試模塊。 通常這種結構比較清晰。 但是,如果你混亂導入,導入的hook方法可能會受到干擾。
如果是這種情況,你有兩種選擇:
通過將字符串PYTEST_DONT_REWRITE
添加到其docstring來禁用特定模塊的重寫。
使用--assert = plain
禁用所有模塊的重寫。
此外,如果無法寫入新的.pyc
文件(如在只讀文件系統或zip文件中),重寫將無提示失敗。
有關進一步的信息,課參閱:本傑明彼得森寫的[pytest的新斷言改寫的幕后故事。
版本2.1新函數:添加斷言重寫作為備用內省技術。
版本2.1更改:引入--assert
選項。 棄用--no-assert
和--nomagic
。
版本3.0版更改:刪除--no-assert
和--nomagic選項。 刪除
--assert = reinterp`選項。