Pytest權威教程04-斷言的編寫和報告


返回: Pytest權威教程

斷言的編寫和報告

使用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`選項。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM