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.