Verwendung von skip und xfail zur Behandlung von Tests, die nicht erfolgreich sein können

Sie können Testfunktionen markieren, die auf bestimmten Plattformen nicht ausgeführt werden können oder von denen Sie erwarten, dass sie fehlschlagen, damit pytest sie entsprechend behandeln und eine Zusammenfassung der Testsitzung präsentieren kann, während die Testsuite grün bleibt.

Ein skip bedeutet, dass Sie erwarten, dass Ihr Test nur dann erfolgreich ist, wenn bestimmte Bedingungen erfüllt sind, andernfalls sollte pytest die Ausführung des Tests vollständig überspringen. Häufige Beispiele sind das Überspringen von nur unter Windows ausführbaren Tests auf Nicht-Windows-Plattformen oder das Überspringen von Tests, die von einer externen Ressource abhängen, die derzeit nicht verfügbar ist (z. B. eine Datenbank).

Ein xfail bedeutet, dass Sie erwarten, dass ein Test aus irgendeinem Grund fehlschlägt. Ein häufiges Beispiel ist ein Test für eine noch nicht implementierte Funktion oder einen noch nicht behobenen Fehler. Wenn ein Test trotz der Erwartung, dass er fehlschlägt (markiert mit pytest.mark.xfail), erfolgreich ist, ist dies ein xpass und wird in der Testzusammenfassung gemeldet.

pytest zählt und listet skip und xfail Tests separat auf. Detaillierte Informationen zu übersprungenen/xfailed Tests werden standardmäßig nicht angezeigt, um die Ausgabe übersichtlich zu halten. Sie können die Option -r verwenden, um Details zu den "kurzen" Buchstaben anzuzeigen, die im Testfortschritt angezeigt werden.

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

Weitere Details zur Option -r finden Sie, indem Sie pytest -h ausführen.

(Siehe Standardeinstellungen für Konfigurationsdateien)

Testfunktionen überspringen

Der einfachste Weg, eine Testfunktion zu überspringen, ist die Markierung mit dem skip-Decorator, dem ein optionaler reason übergeben werden kann.

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

Alternativ ist es auch möglich, während der Testausführung oder -einrichtung imperativ durch Aufruf der Funktion pytest.skip(reason) zu überspringen.

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

Die imperative Methode ist nützlich, wenn die Überspringungsbedingung nicht zur Importzeit ausgewertet werden kann.

Es ist auch möglich, das gesamte Modul mit pytest.skip(reason, allow_module_level=True) auf Modulebene zu überspringen.

import sys

import pytest

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

Referenz: pytest.mark.skip

skipif

Wenn Sie etwas bedingt überspringen möchten, können Sie stattdessen skipif verwenden. Hier ist ein Beispiel für die Markierung einer Testfunktion, die übersprungen werden soll, wenn sie auf einem Interpreter vor Python3.13 ausgeführt wird.

import sys


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

Wenn die Bedingung während der Sammlung als True ausgewertet wird, wird die Testfunktion übersprungen, und der angegebene Grund wird in der Zusammenfassung angezeigt, wenn Sie -rs verwenden.

Sie können skipif-Marker zwischen Modulen teilen. Betrachten Sie dieses Testmodul.

# 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(): ...

Sie können den Marker importieren und in einem anderen Testmodul wiederverwenden.

# test_myothermodule.py
from test_mymodule import minversion


@minversion
def test_anotherfunction(): ...

Für größere Testsuiten ist es normalerweise eine gute Idee, eine Datei zu haben, in der Sie die Marker definieren, die Sie dann durchgängig in Ihrer Testsuite anwenden.

Alternativ können Sie Bedingungszeichenfolgen anstelle von Booleans verwenden, aber diese können nicht einfach zwischen Modulen geteilt werden, sodass sie hauptsächlich aus Gründen der Abwärtskompatibilität unterstützt werden.

Referenz: pytest.mark.skipif

Alle Testfunktionen einer Klasse oder eines Moduls überspringen

Sie können den skipif-Marker (wie jeden anderen Marker) auf Klassen anwenden.

@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"

Wenn die Bedingung True ist, erzeugt dieser Marker für jede Testmethode dieser Klasse ein Skip-Ergebnis.

Wenn Sie alle Testfunktionen eines Moduls überspringen möchten, können Sie die globale Variable pytestmark verwenden.

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

Wenn mehrere skipif-Dekoratoren auf eine Testfunktion angewendet werden, wird diese übersprungen, wenn eine der Skip-Bedingungen zutrifft.

Dateien oder Verzeichnisse überspringen

Manchmal müssen Sie eine ganze Datei oder ein Verzeichnis überspringen, z. B. wenn die Tests auf Python-versionsspezifische Funktionen angewiesen sind oder Code enthalten, den Sie nicht von pytest ausführen lassen möchten. In diesem Fall müssen Sie die Dateien und Verzeichnisse von der Sammlung ausschließen. Weitere Informationen finden Sie unter Anpassen der Testsammlung.

Überspringen bei fehlender Importabhängigkeit

Sie können Tests bei fehlendem Import mit pytest.importorskip auf Modulebene, innerhalb eines Tests oder einer Setup-Funktion überspringen.

docutils = pytest.importorskip("docutils")

Wenn docutils hier nicht importiert werden kann, führt dies zu einem Skip-Ergebnis des Tests. Sie können auch basierend auf der Versionsnummer einer Bibliothek überspringen.

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

Die Version wird aus dem Attribut __version__ des angegebenen Moduls gelesen.

Zusammenfassung

Hier ist eine kurze Anleitung, wie Tests in einem Modul in verschiedenen Situationen übersprungen werden können.

  1. Alle Tests in einem Modul bedingungslos überspringen

pytestmark = pytest.mark.skip("all tests still WIP")
  1. Alle Tests in einem Modul basierend auf einer Bedingung überspringen

pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="tests for linux only")
  1. Alle Tests in einem Modul überspringen, wenn ein Import fehlt

pexpect = pytest.importorskip("pexpect")

XFail: Testfunktionen als voraussichtlich fehlschlagend markieren

Sie können den xfail-Marker verwenden, um anzuzeigen, dass Sie einen Test als fehlschlagend erwarten.

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

Dieser Test wird ausgeführt, aber kein Traceback wird gemeldet, wenn er fehlschlägt. Stattdessen listet die Terminalausgabe ihn in den Abschnitten "erwartet zu fehlschlagen" (XFAIL) oder "unerwartet erfolgreich" (XPASS) auf.

Alternativ können Sie einen Test auch imperativ aus dem Test oder seiner Setup-Funktion als XFAIL markieren.

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")

Diese beiden Beispiele veranschaulichen Situationen, in denen Sie eine Bedingung nicht auf Modulebene prüfen möchten, was bei der Auswertung von Markierungen der Fall wäre.

Dies führt dazu, dass test_function XFAIL ist. Beachten Sie, dass nach dem Aufruf von pytest.xfail() kein weiterer Code ausgeführt wird, anders als beim Marker. Das liegt daran, dass er intern durch Auslösen einer bekannten Ausnahme implementiert wird.

Referenz: pytest.mark.xfail

Parameter condition

Wenn ein Test nur unter einer bestimmten Bedingung fehlschlagen soll, können Sie diese Bedingung als ersten Parameter übergeben.

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

Beachten Sie, dass Sie auch einen Grund angeben müssen (siehe Beschreibung des Parameters unter pytest.mark.xfail).

Parameter reason

Sie können den Grund für einen erwarteten Fehler mit dem Parameter reason angeben.

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

Parameter raises

Wenn Sie spezifischer angeben möchten, warum der Test fehlschlägt, können Sie eine einzelne Ausnahme oder ein Tupel von Ausnahmen im Argument raises angeben.

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

Dann wird der Test als regulärer Fehler gemeldet, wenn er mit einer Ausnahme fehlschlägt, die nicht in raises aufgeführt ist.

Parameter run

Wenn ein Test als xfail markiert und entsprechend gemeldet werden soll, aber nicht einmal ausgeführt werden soll, verwenden Sie den Parameter run als False.

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

Dies ist besonders nützlich für xfailende Tests, die den Interpreter abstürzen lassen und später untersucht werden sollten.

Parameter strict

Sowohl XFAIL als auch XPASS führen standardmäßig nicht zum Fehlschlagen der Testsuite. Sie können dies ändern, indem Sie den nur-keyword-Parameter strict auf True setzen.

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

Dies führt dazu, dass XPASS ("unerwartet erfolgreich") Ergebnisse dieses Tests die Testsuite zum Fehlschlagen bringen.

Sie können den Standardwert des Parameters strict über die strict_xfail-INI-Option ändern.

[pytest]
xfail_strict = true
[pytest]
strict_xfail = true

Xfail ignorieren

Durch Angabe auf der Kommandozeile

pytest --runxfail

können Sie das Ausführen und Melden eines mit xfail markierten Tests erzwingen, als ob er gar nicht markiert wäre. Dies bewirkt auch, dass pytest.xfail() keine Wirkung hat.

Beispiele

Hier ist eine einfache Testdatei mit verschiedenen Verwendungen.

from __future__ import annotations

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

Wenn Sie sie mit der Option "report-on-xfail" ausführen, erhalten Sie diese Ausgabe.

! 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 ============================

Skip/xfail mit parametrize

Es ist möglich, Marker wie skip und xfail auf einzelne Testinstanzen anzuwenden, wenn parametrize verwendet wird.

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