Warnungen erfassen

Ab Version 3.1 fängt pytest Warnungen während der Testausführung automatisch ab und zeigt sie am Ende der Sitzung an.

# content of test_show_warnings.py
import warnings


def api_v1():
    warnings.warn(UserWarning("api v1, should use functions from v2"))
    return 1


def test_one():
    assert api_v1() == 1

Wenn Sie pytest jetzt ausführen, erhalten Sie diese Ausgabe:

$ pytest test_show_warnings.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-9.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item

test_show_warnings.py .                                              [100%]

============================= warnings summary =============================
test_show_warnings.py::test_one
  /home/sweet/project/test_show_warnings.py:5: UserWarning: api v1, should use functions from v2
    warnings.warn(UserWarning("api v1, should use functions from v2"))

-- Docs: https://docs.pytest.de/en/stable/how-to/capture-warnings.html
======================= 1 passed, 1 warning in 0.12s =======================

Warnungen steuern

Ähnlich wie die Warnfilter und die -W Option von Python bietet pytest seine eigene -W-Option, um zu steuern, welche Warnungen ignoriert, angezeigt oder in Fehler umgewandelt werden. Weitere fortgeschrittene Anwendungsfälle finden Sie in der Dokumentation zum Warnfilter.

Dieses Codebeispiel zeigt, wie Sie jede Warnung der UserWarning-Kategorie als Fehler behandeln.

$ pytest -q test_show_warnings.py -W error::UserWarning
F                                                                    [100%]
================================= FAILURES =================================
_________________________________ test_one _________________________________

    def test_one():
>       assert api_v1() == 1
               ^^^^^^^^

test_show_warnings.py:10:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    def api_v1():
>       warnings.warn(UserWarning("api v1, should use functions from v2"))
E       UserWarning: api v1, should use functions from v2

test_show_warnings.py:5: UserWarning
========================= short test summary info ==========================
FAILED test_show_warnings.py::test_one - UserWarning: api v1, should use ...
1 failed in 0.12s

Dieselbe Option kann in der Konfigurationsdatei mit der Konfigurationsoption filterwarnings gesetzt werden. Beispielsweise ignoriert die folgende Konfiguration alle Benutzerwarnungen und bestimmte Deprecationswarnungen, die einem regulären Ausdruck entsprechen, wandelt aber alle anderen Warnungen in Fehler um.

[pytest]
filterwarnings = [
    "error",
    "ignore::UserWarning",
    # note the use of single quote below to denote "raw" strings in TOML
    'ignore:function ham\(\) is deprecated:DeprecationWarning',
]
[pytest]
filterwarnings =
    error
    ignore::UserWarning
    ignore:function ham\(\) is deprecated:DeprecationWarning

Wenn eine Warnung mit mehr als einer Option in der Liste übereinstimmt, wird die Aktion der letzten übereinstimmenden Option ausgeführt.

Hinweis

Die -W-Option und die Konfigurationsoption filterwarnings verwenden Warnfilter, die strukturell ähnlich sind, aber jede Konfigurationsoption interpretiert ihren Filter anders. Beispielsweise ist *message* in filterwarnings eine Zeichenkette, die einen regulären Ausdruck enthält, mit dem der Anfang der Warnmeldung übereinstimmen muss (unabhängig von Groß- und Kleinschreibung), während *message* in -W eine buchstäbliche Zeichenkette ist, die der Anfang der Warnmeldung enthalten muss (unabhängig von Groß- und Kleinschreibung), wobei Leerzeichen am Anfang oder Ende der Meldung ignoriert werden. Weitere Einzelheiten finden Sie in der Dokumentation zum Warnfilter.

@pytest.mark.filterwarnings

Sie können die Markierung @pytest.mark.filterwarnings verwenden, um Warnfilter zu spezifischen Testelementen hinzuzufügen. Dies ermöglicht eine feinere Steuerung, welche Warnungen auf Test-, Klassen- oder sogar Modulebene erfasst werden sollen.

import warnings


def api_v1():
    warnings.warn(UserWarning("api v1, should use functions from v2"))
    return 1


@pytest.mark.filterwarnings("ignore:api v1")
def test_one():
    assert api_v1() == 1

Sie können mehrere Filter mit separaten Dekoratoren angeben.

# Ignore "api v1" warnings, but fail on all other warnings
@pytest.mark.filterwarnings("ignore:api v1")
@pytest.mark.filterwarnings("error")
def test_one():
    assert api_v1() == 1

Wichtig

Bezüglich der Dekoratorreihenfolge und Filter-Präzedenz: Es ist wichtig zu bedenken, dass Dekoratoren in umgekehrter Reihenfolge ausgewertet werden. Daher müssen Sie die Warnfilter in umgekehrter Reihenfolge angeben im Vergleich zur traditionellen Verwendung von warnings.filterwarnings() und der -W Option. Das bedeutet in der Praxis, dass Filter aus früheren @pytest.mark.filterwarnings-Dekoratoren Vorrang vor Filtern aus späteren Dekoratoren haben, wie im obigen Beispiel gezeigt.

Filter, die mithilfe einer Markierung angewendet werden, haben Vorrang vor Filtern, die auf der Befehlszeile übergeben oder mit der Konfigurationsoption filterwarnings konfiguriert werden.

Sie können einen Filter auf alle Tests einer Klasse anwenden, indem Sie die Markierung filterwarnings als Klassendekorator verwenden, oder auf alle Tests in einem Modul, indem Sie die Variable pytestmark setzen.

# turns all warnings into errors for this module
pytestmark = pytest.mark.filterwarnings("error")

Hinweis

Wenn Sie mehrere Filter anwenden möchten (durch Zuweisung einer Liste von filterwarnings-Markierungen an pytestmark), müssen Sie den traditionellen Ansatz der warnings.filterwarnings()-Sortierung verwenden (spätere Filter haben Vorrang), was umgekehrt zum oben erwähnten Dekorator-Ansatz ist.

Die Anerkennung gebührt Florian Schulze für die Referenzimplementierung im Plugin pytest-warnings.

Zusammenfassung der Warnungen deaktivieren

Obwohl nicht empfohlen, können Sie die Befehlszeilenoption --disable-warnings verwenden, um die Warnungszusammenfassung vollständig aus der Testausgabe zu unterdrücken.

Warnungserfassung vollständig deaktivieren

Dieses Plugin ist standardmäßig aktiviert, kann aber in Ihrer Konfigurationsdatei vollständig deaktiviert werden mit

[pytest]
addopts = ["-p", "no:warnings"]
[pytest]
addopts = -p no:warnings

Oder indem Sie -p no:warnings auf der Befehlszeile übergeben. Dies kann nützlich sein, wenn Ihre Test-Suite Warnungen mit einem externen System behandelt.

DeprecationWarning und PendingDeprecationWarning

Standardmäßig zeigt pytest DeprecationWarning und PendingDeprecationWarning-Warnungen von Benutzercode und Drittanbieterbibliotheken an, wie von PEP 565 empfohlen. Dies hilft Benutzern, ihren Code aktuell zu halten und Fehler zu vermeiden, wenn veraltete Warnungen tatsächlich entfernt werden.

Wenn Benutzer jedoch in ihren Tests jegliche Art von Warnungen abfangen, sei es mit pytest.warns(), pytest.deprecated_call() oder mithilfe der recwarn-Fixture, werden überhaupt keine Warnungen angezeigt.

Manchmal ist es nützlich, bestimmte Deprecationswarnungen zu verstecken, die in Code auftreten, den Sie nicht kontrollieren können (z. B. Drittanbieterbibliotheken). In diesem Fall können Sie die Optionen für Warnfilter (Konfiguration oder Markierungen) verwenden, um diese Warnungen zu ignorieren.

Zum Beispiel

[pytest]
filterwarnings = [
    'ignore:.*U.*mode is deprecated:DeprecationWarning',
]
[pytest]
filterwarnings =
    ignore:.*U.*mode is deprecated:DeprecationWarning

Dadurch werden alle Warnungen vom Typ DeprecationWarning ignoriert, bei denen der Anfang der Meldung mit dem regulären Ausdruck ".*U.*mode is deprecated" übereinstimmt.

Weitere Beispiele finden Sie unter @pytest.mark.filterwarnings und Warnungen steuern.

Hinweis

Wenn Warnungen auf Interpreter-Ebene konfiguriert sind, entweder über die Umgebungsvariable PYTHONWARNINGS oder die Befehlszeilenoption -W, konfiguriert pytest standardmäßig keine Filter.

Außerdem folgt pytest nicht dem Vorschlag von PEP 565, alle Warnfilter zurückzusetzen, da dies Test-Suites stören könnte, die ihre Warnfilter selbst konfigurieren, indem sie warnings.simplefilter() aufrufen (siehe #2430 für ein Beispiel dafür).

Sicherstellen, dass Code eine Deprecationswarnung auslöst

Sie können auch pytest.deprecated_call() verwenden, um zu überprüfen, ob ein bestimmter Funktionsaufruf eine DeprecationWarning oder PendingDeprecationWarning auslöst.

import pytest


def test_myfunction_deprecated():
    with pytest.deprecated_call():
        myfunction(17)

Dieser Test schlägt fehl, wenn myfunction bei einem Aufruf mit dem Argument 17 keine Deprecationswarnung ausgibt.

Warnungen mit der `warns`-Funktion assertieren

Sie können überprüfen, ob Code eine bestimmte Warnung auslöst, indem Sie pytest.warns() verwenden, das auf ähnliche Weise wie raises funktioniert (außer dass raises nicht alle Ausnahmen abfängt, sondern nur die expected_exception).

import warnings

import pytest


def test_warning():
    with pytest.warns(UserWarning):
        warnings.warn("my warning", UserWarning)

Der Test schlägt fehl, wenn die betreffende Warnung nicht ausgelöst wird. Verwenden Sie das Schlüsselwortargument match, um zu überprüfen, ob die Warnung mit einem Text oder einem regulären Ausdruck übereinstimmt. Um eine buchstäbliche Zeichenkette abzugleichen, die möglicherweise reguläre Ausdrucks-Metazeichen wie ( oder . enthält, kann das Muster zuerst mit re.escape maskiert werden.

Einige Beispiele

>>> with warns(UserWarning, match="must be 0 or None"):
...     warnings.warn("value must be 0 or None", UserWarning)
...

>>> with warns(UserWarning, match=r"must be \d+$"):
...     warnings.warn("value must be 42", UserWarning)
...

>>> with warns(UserWarning, match=r"must be \d+$"):
...     warnings.warn("this is not here", UserWarning)
...
Traceback (most recent call last):
  ...
Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...

>>> with warns(UserWarning, match=re.escape("issue with foo() func")):
...     warnings.warn("issue with foo() func")
...

Sie können pytest.warns() auch auf eine Funktion oder eine Code-Zeichenkette anwenden.

pytest.warns(expected_warning, func, *args, **kwargs)
pytest.warns(expected_warning, "func(*args, **kwargs)")

Die Funktion gibt auch eine Liste aller ausgelösten Warnungen (als Objekte vom Typ warnings.WarningMessage) zurück, die Sie für zusätzliche Informationen abfragen können.

with pytest.warns(RuntimeWarning) as record:
    warnings.warn("another warning", RuntimeWarning)

# check that only one warning was raised
assert len(record) == 1
# check that the message matches
assert record[0].message.args[0] == "another warning"

Alternativ können Sie ausgelöste Warnungen im Detail mit der recwarn-Fixture untersuchen (siehe unten).

Die recwarn-Fixture stellt sicher, dass der Warnfilter am Ende des Tests automatisch zurückgesetzt wird, sodass kein globaler Zustand durchsickert.

Warnungen aufzeichnen

Sie können ausgelöste Warnungen entweder mit dem Kontextmanager pytest.warns() oder mit der recwarn-Fixture aufzeichnen.

Um mit pytest.warns() aufzuzeichnen, ohne etwas über die Warnungen zu assertieren, übergeben Sie keine Argumente für den erwarteten Warnungstyp, und es wird standardmäßig auf eine generische `Warning` gesetzt.

with pytest.warns() as record:
    warnings.warn("user", UserWarning)
    warnings.warn("runtime", RuntimeWarning)

assert len(record) == 2
assert str(record[0].message) == "user"
assert str(record[1].message) == "runtime"

Die recwarn-Fixture zeichnet Warnungen für die gesamte Funktion auf.

import warnings


def test_hello(recwarn):
    warnings.warn("hello", UserWarning)
    assert len(recwarn) == 1
    w = recwarn.pop(UserWarning)
    assert issubclass(w.category, UserWarning)
    assert str(w.message) == "hello"
    assert w.filename
    assert w.lineno

Sowohl die recwarn-Fixture als auch der Kontextmanager pytest.warns() geben dieselbe Schnittstelle für aufgezeichnete Warnungen zurück: eine Instanz von WarningsRecorder. Um die aufgezeichneten Warnungen anzuzeigen, können Sie diese Instanz durchlaufen, len darauf aufrufen, um die Anzahl der aufgezeichneten Warnungen zu erhalten, oder sie indizieren, um eine bestimmte aufgezeichnete Warnung zu erhalten.

Zusätzliche Anwendungsfälle für Warnungen in Tests

Hier sind einige Anwendungsfälle, die Warnungen in Tests betreffen und oft vorkommen, sowie Vorschläge, wie damit umzugehen ist.

  • Um sicherzustellen, dass **mindestens eine** der angegebenen Warnungen ausgegeben wird, verwenden Sie

def test_warning():
    with pytest.warns((RuntimeWarning, UserWarning)):
        ...
  • Um sicherzustellen, dass **nur** bestimmte Warnungen ausgegeben werden, verwenden Sie

def test_warning(recwarn):
    ...
    assert len(recwarn) == 1
    user_warning = recwarn.pop(UserWarning)
    assert issubclass(user_warning.category, UserWarning)
  • Um sicherzustellen, dass **keine** Warnungen ausgegeben werden, verwenden Sie

def test_warning():
    with warnings.catch_warnings():
        warnings.simplefilter("error")
        ...
  • Um Warnungen zu unterdrücken, verwenden Sie

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    ...

Benutzerdefinierte Fehlermeldungen

Das Aufzeichnen von Warnungen bietet die Möglichkeit, benutzerdefinierte Fehlermeldungen für Tests zu erstellen, wenn keine Warnungen ausgegeben werden oder andere Bedingungen erfüllt sind.

def test():
    with pytest.warns(Warning) as record:
        f()
        if not record:
            pytest.fail("Expected a warning!")

Wenn beim Aufruf von f keine Warnungen ausgegeben werden, wird not record zu True ausgewertet. Sie können dann pytest.fail() mit einer benutzerdefinierten Fehlermeldung aufrufen.

Interne pytest-Warnungen

pytest kann in einigen Situationen eigene Warnungen generieren, z. B. bei unsachgemäßer Verwendung oder veralteten Funktionen.

pytest gibt beispielsweise eine Warnung aus, wenn es eine Klasse findet, die mit python_classes übereinstimmt, aber auch einen __init__-Konstruktor definiert. Dies verhindert, dass die Klasse instanziiert werden kann.

# content of test_pytest_warnings.py
class Test:
    def __init__(self):
        pass

    def test_foo(self):
        assert 1 == 1
$ pytest test_pytest_warnings.py -q

============================= warnings summary =============================
test_pytest_warnings.py:1
  /home/sweet/project/test_pytest_warnings.py:1: PytestCollectionWarning: cannot collect test class 'Test' because it has a __init__ constructor (from: test_pytest_warnings.py)
    class Test:

-- Docs: https://docs.pytest.de/en/stable/how-to/capture-warnings.html
1 warning in 0.12s

Diese Warnungen können mit denselben integrierten Mechanismen gefiltert werden, die auch zum Filtern anderer Arten von Warnungen verwendet werden.

Bitte lesen Sie unsere Rückwärtskompatibilitätsrichtlinie, um zu erfahren, wie wir mit der Veraltung und schließlichen Entfernung von Funktionen umgehen.

Die vollständige Liste der Warnungen finden Sie in der Referenzdokumentation.

Ressourcenwarnungen

Zusätzliche Informationen zur Quelle einer ResourceWarning können erfasst werden, wenn pytest sie erfasst und das tracemalloc-Modul aktiviert ist.

Eine einfache Möglichkeit, tracemalloc beim Ausführen von Tests zu aktivieren, ist die Einstellung der Umgebungsvariable PYTHONTRACEMALLOC auf eine ausreichend große Anzahl von Frames (z. B. 20, aber diese Zahl ist anwendungsabhängig).

Weitere Informationen finden Sie im Abschnitt Python Development Mode in der Python-Dokumentation.