pytest 測(cè)試輸出和結(jié)果-使用skip和xfail處理無(wú)法成功的測(cè)試

2022-03-21 15:23 更新

您可以標(biāo)記不能在某些平臺(tái)上運(yùn)行的測(cè)試函數(shù),或者您預(yù)計(jì)會(huì)失敗的測(cè)試函數(shù),以便pytest可以相應(yīng)地處理它們,并顯示測(cè)試會(huì)話的摘要,同時(shí)保持測(cè)試套件為綠色。

?skip?意味著您希望您的測(cè)試只有在滿足某些條件時(shí)才會(huì)通過,否則pytest應(yīng)該完全跳過運(yùn)行測(cè)試。常見的例子是跳過非windows平臺(tái)上的僅限windows的測(cè)試,或者跳過依賴于當(dāng)前不可用的外部資源(例如數(shù)據(jù)庫(kù))的測(cè)試。

?xfail?意味著您預(yù)期測(cè)試由于某種原因會(huì)失敗。一個(gè)常見的例子是對(duì)尚未實(shí)現(xiàn)的特性或尚未修復(fù)的bug進(jìn)行測(cè)試。當(dāng)測(cè)試通過時(shí),盡管預(yù)期會(huì)失敗(標(biāo)記為?pytest.mark.xfail?),它是一個(gè)?xpass?,并將在測(cè)試總結(jié)中報(bào)告。

Pytest分別計(jì)數(shù)和列出?skip?和?xfail?測(cè)試。默認(rèn)情況下,不顯示有關(guān)?skip?/?xfailed?測(cè)試的詳細(xì)信息,以避免輸出混亂。你可以使用?-r?選項(xiàng)來(lái)查看測(cè)試進(jìn)度中顯示的?short?字母對(duì)應(yīng)的詳細(xì)信息:

pytest -rxXs  # show extra info on xfailed, xpassed, and skipped tests

通過運(yùn)行 ?pytest -h? 可以找到有關(guān) ?-r選項(xiàng)的更多詳細(xì)信息。

跳過測(cè)試功能

跳過一個(gè)測(cè)試函數(shù)的最簡(jiǎn)單的方法是用?skip?裝飾器來(lái)標(biāo)記它,它可以傳遞一個(gè)可選的原因:

@pytest.mark.skip(reason="no way of currently testing this")
def test_the_unknown():
    ...

或者,也可以通過調(diào)用 ?pytest.skip(reason)? 函數(shù)在測(cè)試執(zhí)行或設(shè)置期間強(qiáng)制跳過:

def test_function():
    if not valid_config():
        pytest.skip("unsupported configuration")

當(dāng)在導(dǎo)入期間無(wú)法評(píng)估跳過條件時(shí),命令式方法很有用。

也可以在模塊級(jí)別使用 ?pytest.skip(reason, allow_module_level=True)? 跳過整個(gè)模塊:

import sys
import pytest

if not sys.platform.startswith("win"):
    pytest.skip("skipping windows-only tests", allow_module_level=True)

skipif

如果您希望有條件地跳過某些內(nèi)容,則可以改用 ?skipif?。 以下是在 Python3.10 之前的解釋器上運(yùn)行時(shí)標(biāo)記要跳過的測(cè)試函數(shù)的示例:

import sys


@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
def test_function():
    ...

如果在收集過程中條件評(píng)估為 ?True?,則將跳過測(cè)試函數(shù),使用 ?-rs? 時(shí)會(huì)在摘要中顯示指定的原因。

您可以在模塊之間共享 ?skipif標(biāo)記。 考慮這個(gè)測(cè)試模塊:

# content of test_mymodule.py
import mymodule

minversion = pytest.mark.skipif(
    mymodule.__versioninfo__ < (1, 1), reason="at least mymodule-1.1 required"
)


@minversion
def test_function():
    ...

您可以導(dǎo)入標(biāo)記并在另一個(gè)測(cè)試模塊中重用它:

# test_myothermodule.py
from test_mymodule import minversion


@minversion
def test_anotherfunction():
    ...

對(duì)于較大的測(cè)試套件,最好使用一個(gè)文件來(lái)定義標(biāo)記,然后在整個(gè)測(cè)試套件中始終如一地應(yīng)用這些標(biāo)記。

或者,您可以使用條件字符串而不是布爾值,但它們不能在模塊之間輕松共享,因此主要出于向后兼容性的原因支持它們。

跳過類或模塊的所有測(cè)試功能

您可以在類上使用 ?skipif標(biāo)記(與任何其他標(biāo)記一樣):

@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
class TestPosixCalls:
    def test_function(self):
        "will not be setup or run under 'win32' platform"

如果條件為?True?,該標(biāo)記將為該類的每個(gè)測(cè)試方法產(chǎn)生一個(gè)跳過結(jié)果。

如果你想跳過一個(gè)模塊的所有測(cè)試函數(shù),你可以使用全局?pytestmark?:

# test_module.py
pytestmark = pytest.mark.skipif(...)

如果將多個(gè) ?skipif ?裝飾器應(yīng)用于測(cè)試函數(shù),則如果任何跳過條件為?true?,它將被跳過。

跳過文件或目錄

有時(shí)您可能需要跳過整個(gè)文件或目錄,例如,如果測(cè)試依賴于 Python 版本特定的功能或包含您不希望 pytest 運(yùn)行的代碼。 在這種情況下,您必須從集合中排除文件和目錄。

跳過缺少的導(dǎo)入依賴項(xiàng)

您可以通過在模塊級(jí)別、測(cè)試或測(cè)試設(shè)置函數(shù)中使用 ?pytest.importorskip? 來(lái)跳過缺少導(dǎo)入的測(cè)試。

docutils = pytest.importorskip("docutils")

如果此處無(wú)法導(dǎo)入 ?docutils?,這將導(dǎo)致測(cè)試的跳過結(jié)果。 您也可以根據(jù)庫(kù)的版本號(hào)跳過:

docutils = pytest.importorskip("docutils", minversion="0.3")

版本將從指定模塊的 ?__version__? 屬性中讀取。

概述

以下是有關(guān)如何在不同情況下跳過模塊中的測(cè)試的快速指南:

  • 無(wú)條件跳過模塊中的所有測(cè)試:

pytestmark = pytest.mark.skip("all tests still WIP")

  • 根據(jù)某些條件跳過模塊中的所有測(cè)試:

pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="tests for linux only")

  • 如果缺少某些導(dǎo)入,則跳過模塊中的所有測(cè)試:

pexpect = pytest.importorskip("pexpect")

XFail:將測(cè)試功能標(biāo)記為預(yù)期失敗

您可以使用 ?xfail標(biāo)記來(lái)指示您希望測(cè)試失敗:

@pytest.mark.xfail
def test_function():
    ...

此測(cè)試將運(yùn)行,但失敗時(shí)不會(huì)報(bào)告回溯。 相反,終端報(bào)告會(huì)將其列在“預(yù)期失敗”(?XFAIL?)或“意外通過”(?XPASS?)部分。

或者,您也可以在測(cè)試或其設(shè)置函數(shù)中強(qiáng)制將測(cè)試標(biāo)記為 ?XFAIL?:

def test_function():
    if not valid_config():
        pytest.xfail("failing configuration (but should work)")
def test_function2():
    import slow_module

    if slow_module.slow_function():
        pytest.xfail("slow_module taking too long")

這兩個(gè)例子說(shuō)明了您不希望在模塊級(jí)檢查條件的情況,也就是當(dāng)一個(gè)條件將被用于標(biāo)記時(shí)。

這將使?test_function XFAIL?。請(qǐng)注意,在調(diào)用?pytest.xfail()?之后不會(huì)執(zhí)行其他代碼,這與標(biāo)記不同。這是因?yàn)樗峭ㄟ^引發(fā)已知異常在內(nèi)部實(shí)現(xiàn)的。

condition參數(shù)

如果測(cè)試只在特定條件下失敗,您可以將該條件作為第一個(gè)參數(shù)傳遞:

@pytest.mark.xfail(sys.platform == "win32", reason="bug in a 3rd party library")
def test_function():
    ...

reason參數(shù)

你可以用?reason?參數(shù)指定預(yù)期失敗的動(dòng)機(jī):

@pytest.mark.xfail(reason="known parser issue")
def test_function():
    ...

raises參數(shù)

如果您想更具體地了解測(cè)試失敗的原因,您可以在 ?raises參數(shù)中指定單個(gè)異?;虍惓TM:

@pytest.mark.xfail(raises=RuntimeError)
def test_function():
    ...

然后,如果測(cè)試失敗并出現(xiàn) ?raises中未提及的異常,則該測(cè)試將被報(bào)告為常規(guī)失敗。

run參數(shù)

如果測(cè)試應(yīng)標(biāo)記為 ?xfail并報(bào)告為 ?xfail?,但甚至不應(yīng)執(zhí)行,請(qǐng)將 ?run ?參數(shù)設(shè)置為 ?False?:

@pytest.mark.xfail(run=False)
def test_function():
    ...

這對(duì)于導(dǎo)致解釋器崩潰的失敗測(cè)試特別有用,應(yīng)該稍后進(jìn)行調(diào)查。

strict參數(shù)

默認(rèn)情況下,?XFAIL和 ?XPASS都不會(huì)使測(cè)試套件失敗。 您可以通過將 ?strict keyword-only? 參數(shù)設(shè)置為 ?True來(lái)更改此設(shè)置:

@pytest.mark.xfail(strict=True)
def test_function():
    ...

這將使該測(cè)試的 ?XPASS?(“意外通過”)結(jié)果無(wú)法通過測(cè)試套件。

您可以使用 ?xfail_strict ini? 選項(xiàng)更改 ?strict參數(shù)的默認(rèn)值:

[pytest]
xfail_strict=true

忽略xfail

通過在命令行上指定:

pytest --runxfail

您可以強(qiáng)制運(yùn)行和報(bào)告帶有 ?xfail標(biāo)記的測(cè)試,就好像它根本沒有標(biāo)記一樣。 這也會(huì)導(dǎo)致 ?pytest.xfail()? 不起作用。

例如,這是一個(gè)具有多種用途的簡(jiǎn)單測(cè)試文件:

import pytest

xfail = pytest.mark.xfail


@xfail
def test_hello():
    assert 0


@xfail(run=False)
def test_hello2():
    assert 0


@xfail("hasattr(os, 'sep')")
def test_hello3():
    assert 0


@xfail(reason="bug 110")
def test_hello4():
    assert 0


@xfail('pytest.__version__[0] != "17"')
def test_hello5():
    assert 0


def test_hello6():
    pytest.xfail("reason")


@xfail(raises=IndexError)
def test_hello7():
    x = []
    x[1] = 1

使用 ?report-on-xfail? 選項(xiàng)運(yùn)行它會(huì)給出以下輸出:

! pytest -rx xfail_demo.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/example
collected 7 items

xfail_demo.py xxxxxxx                                                [100%]

========================= short test summary info ==========================
XFAIL xfail_demo.py::test_hello
XFAIL xfail_demo.py::test_hello2
  reason: [NOTRUN]
XFAIL xfail_demo.py::test_hello3
  condition: hasattr(os, 'sep')
XFAIL xfail_demo.py::test_hello4
  bug 110
XFAIL xfail_demo.py::test_hello5
  condition: pytest.__version__[0] != "17"
XFAIL xfail_demo.py::test_hello6
  reason: reason
XFAIL xfail_demo.py::test_hello7
============================ 7 xfailed in 0.12s ============================

使用參數(shù)化skip/xfail

使用參數(shù)化時(shí),可以將skipxfail等標(biāo)記應(yīng)用于單個(gè)測(cè)試實(shí)例:

import sys
import pytest


@pytest.mark.parametrize(
    ("n", "expected"),
    [
        (1, 2),
        pytest.param(1, 0, marks=pytest.mark.xfail),
        pytest.param(1, 3, marks=pytest.mark.xfail(reason="some bug")),
        (2, 3),
        (3, 4),
        (4, 5),
        pytest.param(
            10, 11, marks=pytest.mark.skipif(sys.version_info >= (3, 0), reason="py2k")
        ),
    ],
)
def test_increment(n, expected):
    assert n + 1 == expected


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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)