Ich verwende Python-Assert-Anweisungen, um dem tatsächlichen und erwarteten Verhalten zu entsprechen. Ich habe keine Kontrolle darüber, als ob es einen Fehlertest gibt, der abbricht. Ich möchte die Kontrolle über den Assertionsfehler übernehmen und definieren, ob ich den Testfall bei Assertion des Fehlers abbrechen möchte oder nicht.
Außerdem möchte ich etwas hinzufügen, wenn ein Assertionsfehler vorliegt, sollte der Testfall angehalten werden und der Benutzer kann jederzeit fortfahren.
Ich habe keine Ahnung, wie das geht
Codebeispiel, wir verwenden hier pytest
import pytest
def test_abc():
a = 10
assert a == 10, "some error message"
Below is my expectation
Wenn assert einen assertionError auslöst, sollte ich die Option haben, den Testfall anzuhalten und kann debuggen und später fortsetzen. Für Pause und Lebenslauf werde ich tkinter
Modul verwenden. Ich werde eine Assert-Funktion wie unten machen
import tkinter
import tkinter.messagebox
top = tkinter.Tk()
def _assertCustom(assert_statement, pause_on_fail = 0):
#assert_statement will be something like: assert a == 10, "Some error"
#pause_on_fail will be derived from global file where I can change it on runtime
if pause_on_fail == 1:
try:
eval(assert_statement)
except AssertionError as e:
tkinter.messagebox.showinfo(e)
eval (assert_statement)
#Above is to raise the assertion error again to fail the testcase
else:
eval (assert_statement)
In Zukunft muss ich jede Assert-Anweisung mit dieser Funktion als ändern
import pytest
def test_abc():
a = 10
# Suppose some code and below is the assert statement
_assertCustom("assert a == 10, 'error message'")
Dies ist zu viel Aufwand für mich, da ich an Tausenden von Stellen, an denen ich behauptet habe, Änderungen vornehmen muss. Gibt es eine einfache Möglichkeit, dies zu tun?pytest
Summary:
Ich benötige etwas, bei dem ich den Testfall bei einem Fehler anhalten und nach dem Debuggen fortsetzen kann. Ich weiß davon tkinter
und das ist der Grund, warum ich es benutzt habe. Alle anderen Ideen sind willkommen
Note
: Der obige Code ist noch nicht getestet. Es können auch kleine Syntaxfehler auftreten
Edit: Danke für die Antworten. Erweitern Sie diese Frage jetzt ein wenig weiter. Was ist, wenn ich das Verhalten von assert ändern möchte? Derzeit wird der Testfall beendet, wenn ein Assertionsfehler vorliegt. Was ist, wenn ich auswählen möchte, ob ich einen Testfall-Exit bei einem bestimmten Assert-Fehler benötige oder nicht? Ich möchte keine benutzerdefinierte Assert-Funktion wie oben erwähnt schreiben, da ich auf diese Weise die Anzahl der Stellen ändern muss
assert
eigenen Überprüfungsfunktionen, sondern schreiben Sie diese, die das tun, was Sie wollen.pytest
für Ihre Testfälle verwenden möchten . Es unterstützt die Verwendung von Assert- und Skipping-Tests sowie viele weitere Funktionen, die das Schreiben von Testsuiten erleichtern.assert cond, "msg"
in Ihrem Code mechanisch ersetzt_assertCustom("assert cond, 'msg'")
? Wahrscheinlichsed
könnte es ein Einzeiler tun.Antworten:
Sie verwenden
pytest
, wodurch Sie zahlreiche Optionen für die Interaktion mit fehlgeschlagenen Tests erhalten. Es gibt Ihnen Befehlszeilenoptionen und mehrere Hooks, um dies zu ermöglichen. Ich werde erklären, wie Sie die einzelnen Funktionen verwenden und wo Sie Anpassungen vornehmen können, die Ihren spezifischen Debugging-Anforderungen entsprechen.Ich werde auch auf exotischere Optionen eingehen, mit denen Sie bestimmte Behauptungen vollständig überspringen können, wenn Sie wirklich das Gefühl haben, dass Sie es müssen.
Ausnahmen behandeln, nicht behaupten
Beachten Sie, dass ein fehlgeschlagener Test den Pytest normalerweise nicht stoppt. Nur wenn Sie die explizite Anweisung aktiviert haben, nach einer bestimmten Anzahl von Fehlern zu beenden . Außerdem schlagen Tests fehl, weil eine Ausnahme ausgelöst wird.
assert
erhöht,AssertionError
aber das ist nicht die einzige Ausnahme, die dazu führt, dass ein Test fehlschlägt! Sie möchten steuern, wie Ausnahmen behandelt und nicht geändert werdenassert
.Eine fehlgeschlagene Zusicherung beendet jedoch den einzelnen Test. Das liegt daran
try...except
, dass Python, sobald eine Ausnahme außerhalb eines Blocks ausgelöst wird, den aktuellen Funktionsrahmen abwickelt und es kein Zurück mehr gibt.Ich glaube nicht, dass Sie das wollen, gemessen an Ihrer Beschreibung Ihrer
_assertCustom()
Versuche, die Behauptung erneut auszuführen, aber ich werde Ihre Optionen dennoch weiter unten erörtern.Post-Mortem-Debugging im Pytest mit pdb
Für die verschiedenen Optionen zur Behandlung von Fehlern in einem Debugger beginne ich mit dem
--pdb
Befehlszeilenschalter , der die Standard-Debugging-Eingabeaufforderung öffnet, wenn ein Test fehlschlägt (die Ausgabe wurde der Kürze halber entfernt):Wenn ein Test fehlschlägt, startet pytest mit diesem Schalter eine Post-Mortem- Debugging-Sitzung . Dies ist im Wesentlichen genau das, was Sie wollten; um den Code an der Stelle eines fehlgeschlagenen Tests zu stoppen und den Debugger zu öffnen, um den Status Ihres Tests zu überprüfen. Sie können mit den lokalen Variablen des Tests, den Globals sowie den Lokalen und Globals jedes Frames im Stapel interagieren.
Hier haben Sie mit pytest die volle Kontrolle darüber, ob Sie nach diesem Punkt
q
beenden möchten oder nicht: Wenn Sie den Befehl quit verwenden, beendet pytest den Lauf ebenfalls. Wenn Siec
for continue verwenden, wird die Steuerung an pytest zurückgegeben und der nächste Test ausgeführt.Verwenden eines alternativen Debuggers
Sie sind dafür nicht an den
pdb
Debugger gebunden . Sie können mit dem--pdbcls
Switch einen anderen Debugger einstellen . Jedepdb.Pdb()
kompatible Implementierung würde funktionieren, einschließlich der IPython-Debugger-Implementierung oder der meisten anderen Python-Debugger (für den Pudb-Debugger muss der-s
Switch verwendet werden, oder ein spezielles Plugin ). Der Switch benötigt ein Modul und eine Klasse, z. B. umpudb
Folgendes zu verwenden:Sie können diese Funktion benutzen , um Ihre eigenen Wrapper - Klasse zu schreiben , um
Pdb
das einfach sofort zurück , wenn der spezifische Fehler nicht etwas , das Sie interessiert sind ist.pytest
AnwendungenPdb()
genau wiepdb.post_mortem()
tut :Hier
t
ist ein Traceback-Objekt . Wennp.interaction(None, t)
zurückgegeben wird,pytest
wird mit dem nächsten Test fortgefahren , sofern nichtp.quitting
gesetztTrue
(an diesem Punkt wird der Pytest dann beendet).Hier ist eine Beispielimplementierung, die ausgibt, dass wir das Debuggen ablehnen, und sofort zurückgibt, sofern der Test nicht ausgelöst wurde
ValueError
, gespeichert alsdemo/custom_pdb.py
:Wenn ich dies mit der obigen Demo verwende, wird dies ausgegeben (der Kürze halber wieder entfernt):
Die obigen Introspektionen,
sys.last_type
um festzustellen, ob der Fehler "interessant" ist.Ich kann diese Option jedoch nur empfehlen, wenn Sie Ihren eigenen Debugger mit tkInter oder ähnlichem schreiben möchten. Beachten Sie, dass dies ein großes Unterfangen ist.
Filterfehler; Wählen Sie aus, wann der Debugger geöffnet werden soll
Die nächste Stufe sind die Pytest- Debugging- und Interaktions- Hooks . Dies sind Hook-Points für Verhaltensanpassungen, um zu ersetzen oder zu verbessern, wie Pytest normalerweise Dinge wie die Behandlung einer Ausnahme oder die Eingabe des Debuggers über
pdb.set_trace()
oderbreakpoint()
(Python 3.7 oder neuer) behandelt.Die interne Implementierung dieses Hooks ist auch für das Drucken des
>>> entering PDB >>>
obigen Banners verantwortlich. Wenn Sie diesen Hook verwenden, um zu verhindern, dass der Debugger ausgeführt wird, wird diese Ausgabe überhaupt nicht angezeigt. Sie können Ihren eigenen Hook haben und dann an den ursprünglichen Hook delegieren, wenn ein Testfehler "interessant" ist, und so Testfehler unabhängig vom verwendeten Debugger filtern ! Sie können auf die interne Implementierung zugreifen, indem Sie über den Namen darauf zugreifen . Das interne Hook-Plugin dafür heißtpdbinvoke
. Um zu verhindern, dass es ausgeführt wird, müssen Sie die Registrierung aufheben , aber eine Referenz speichern. Können wir es bei Bedarf direkt aufrufen?Hier ist eine Beispielimplementierung eines solchen Hooks; Sie können dies an einer beliebigen Stelle ablegen, von der Plugins geladen werden . Ich habe es eingefügt
demo/conftest.py
:Das obige Plugin verwendet das interne
TerminalReporter
Plugin , um Zeilen an das Terminal zu schreiben. Dies macht die Ausgabe sauberer, wenn das standardmäßige kompakte Teststatusformat verwendet wird, und ermöglicht es Ihnen, Dinge auf das Terminal zu schreiben, auch wenn die Ausgabeerfassung aktiviert ist.In diesem Beispiel wird das Plugin-Objekt mit dem
pytest_exception_interact
Hook über einen anderen Hook registriert. Stellen Siepytest_configure()
jedoch sicher, dass es spät genug ausgeführt wird (mithilfe@pytest.hookimpl(trylast=True)
), um die Registrierung des internenpdbinvoke
Plugins aufheben zu können. Wenn der Hook aufgerufen wird, testet das Beispiel dascall.exceptinfo
Objekt . Sie können auch den Knoten oder den Bericht überprüfen .Wenn der obige Beispielcode vorhanden ist
demo/conftest.py
, wird dertest_ham
Testfehler ignoriert. Nur dertest_spam
Testfehler, der ausgelöst wirdValueError
, führt zum Öffnen der Debug-Eingabeaufforderung:Um es noch einmal zu wiederholen, hat der obige Ansatz den zusätzlichen Vorteil, dass Sie dies mit jedem Debugger kombinieren können, der mit pytest funktioniert , einschließlich pudb oder dem IPython-Debugger:
Es hat auch viel mehr Kontext darüber, welcher Test ausgeführt wurde (über das
node
Argument) und direkten Zugriff auf die ausgelöste Ausnahme (über diecall.excinfo
ExceptionInfo
Instanz).Beachten Sie, dass bestimmte Pytest-Debugger-Plugins (wie
pytest-pudb
oderpytest-pycharm
) ihre eigenenpytest_exception_interact
Hooksp registrieren . Eine vollständigere Implementierung müsste alle Plugins im Plugin-Manager durchlaufen , um beliebige Plugins automatisch zu überschreibenconfig.pluginmanager.list_name_plugin
undhasattr()
jedes Plugin zu verwenden und zu testen.Fehler verschwinden lassen
Auf diese Weise haben Sie zwar die volle Kontrolle über das Debuggen fehlgeschlagener Tests, der Test bleibt jedoch weiterhin fehlgeschlagen, selbst wenn Sie den Debugger für einen bestimmten Test nicht geöffnet haben. Wenn Sie Fehler ganz beseitigen möchten, können Sie einen anderen Haken verwenden :
pytest_runtest_call()
.Wenn pytest Tests ausführt, wird der Test über den obigen Hook ausgeführt, von dem erwartet wird, dass er
None
eine Ausnahme zurückgibt oder auslöst . Daraus wird ein Bericht erstellt, optional ein Protokolleintrag erstellt, und wenn der Test fehlgeschlagen ist, wird der oben genanntepytest_exception_interact()
Hook aufgerufen. Alles, was Sie tun müssen, ist zu ändern, was das Ergebnis dieses Hakens erzeugt. Anstelle einer Ausnahme sollte es überhaupt nichts zurückgeben.Der beste Weg, dies zu tun, ist die Verwendung eines Hook-Wrappers . Hook Wrapper müssen nicht die eigentliche Arbeit erledigen, sondern haben die Möglichkeit zu ändern, was mit dem Ergebnis eines Hooks passiert. Alles was Sie tun müssen, ist die Zeile hinzuzufügen:
In Ihrer Hook-Wrapper-Implementierung erhalten Sie Zugriff auf das Hook-Ergebnis , einschließlich der Testausnahme über
outcome.excinfo
. Dieses Attribut wird auf ein Tupel von (Typ, Instanz, Traceback) gesetzt, wenn im Test eine Ausnahme ausgelöst wurde. Alternativ können Sie dieoutcome.get_result()
Standardbehandlung aufrufen und verwendentry...except
.Wie schaffen Sie einen fehlgeschlagenen Test? Sie haben 3 grundlegende Optionen:
pytest.xfail()
den Wrapper aufrufen .pytest.skip()
.outcome.force_result()
Methode entfernen . Setzen Sie das Ergebnis hier auf eine leere Liste (was bedeutet: Der registrierte Hook hat nur etwas produziertNone
), und die Ausnahme wird vollständig gelöscht.Was Sie verwenden, liegt bei Ihnen. Stellen Sie sicher, dass Sie das Ergebnis zuerst auf übersprungene und erwartete Fehlertests überprüfen, da Sie diese Fälle nicht so behandeln müssen, als ob der Test fehlgeschlagen wäre. Sie können auf die speziellen Ausnahmen zugreifen, die diese Optionen über
pytest.skip.Exception
und auslösenpytest.xfail.Exception
.Hier ist eine Beispielimplementierung, die fehlgeschlagene Tests, die nicht ausgelöst werden
ValueError
, als übersprungen markiert :Beim Einfügen wird
conftest.py
die Ausgabe:Ich habe die
-r a
Flagge verwendet, um klarer zu machen, dass sietest_ham
jetzt übersprungen wurde.Wenn Sie den
pytest.skip()
Anruf durch ersetzenpytest.xfail("[XFAIL] ignoring everything but ValueError")
, wird der Test als erwarteter Fehler markiert:und mit
outcome.force_result([])
markiert es als bestanden:Es liegt an Ihnen, welches Ihrer Meinung nach am besten zu Ihrem Anwendungsfall passt. For
skip()
undxfail()
ich haben das Standardnachrichtenformat (mit[NOTRUN]
oder vorangestellt[XFAIL]
) nachgeahmt, aber Sie können jedes andere gewünschte Nachrichtenformat verwenden.In allen drei Fällen öffnet pytest den Debugger nicht für Tests, deren Ergebnis Sie mit dieser Methode geändert haben.
Ändern einzelner Assert-Anweisungen
Wenn Sie
assert
Tests innerhalb eines Tests ändern möchten, bereiten Sie sich auf viel mehr Arbeit vor. Ja, dies ist technisch möglich, aber nur durch Umschreiben des Codes, den Python zur Kompilierungszeit ausführen wird .Wenn Sie verwenden
pytest
, wird dies tatsächlich bereits durchgeführt . Pytest schreibtassert
Anweisungen neu, um Ihnen mehr Kontext zu geben, wenn Ihre Behauptungen fehlschlagen . In diesem Blogbeitrag finden Sie einen guten Überblick über die aktuellen Aktivitäten sowie den_pytest/assertion/rewrite.py
Quellcode . Beachten Sie, dass dieses Modul mehr als 1.000 Zeilen lang ist und dass Sie verstehen müssen, wie die abstrakten Syntaxbäume von Python funktionieren. Wenn Sie das tun, Sie könnten das Modul monkeypatch eigene Modifikationen dort hinzufügen, einschließlich der Umgebungassert
mit einemtry...except AssertionError:
Handler.Allerdings können Sie nicht nur deaktivieren oder ignorieren behauptet selektiv, da nachfolgende Aussagen leicht auf Zustand verlassen können (spezifische Objektarrangements, Variablen gesetzt, etc.) , dass ein übersprungener assert Schutz gegen gemeint war. Wenn ein Assert nicht testet, dann
foo
hängtNone
ein späterer Assert davon abfoo.bar
, dass er existiert. Dann werden Sie einfach auf einenAttributeError
dort usw. stoßen. Halten Sie sich daran, die Ausnahme erneut auszulösen, wenn Sie diesen Weg gehen müssen.Ich werde hier nicht näher auf das Umschreiben eingehen
asserts
, da ich nicht der Meinung bin, dass es sich lohnt, dies zu verfolgen, da der Arbeitsaufwand nicht gegeben ist und das Post-Mortem-Debugging Ihnen Zugriff auf den Teststatus am Punkt der Behauptung Fehler sowieso .Beachten Sie, dass Sie in diesem Fall
eval()
wederassert
eine Anweisung verwenden müssen (was ohnehin nicht funktionieren würde, ist eine Anweisung, die Sieexec()
stattdessen verwenden müssten), noch die Assertion zweimal ausführen müssten (welche kann zu Problemen führen, wenn der in der Behauptung verwendete Ausdruck den Zustand ändert). Sie würden stattdessen denast.Assert
Knoten in einenast.Try
Knoten einbetten und einen Ausnahme-Handler anhängen, der einen leerenast.Raise
Knoten verwendet, um die abgefangene Ausnahme erneut auszulösen.Verwenden des Debuggers zum Überspringen von Assertionsanweisungen.
Mit dem Python-Debugger können Sie Anweisungen mit dem Befehl
j
/jump
überspringen . Wenn Sie wissen , vorne , dass eine bestimmte Behauptung wird fehlschlagen, können Sie dies in dem Bypass verwenden. Sie können Ihre Tests mit ausführen--trace
, wodurch der Debugger zu Beginn jedes Tests geöffnet wird , und dann ein ausgebenj <line after assert>
, um ihn zu überspringen, wenn der Debugger kurz vor dem Assert angehalten wird.Sie können dies sogar automatisieren. Mit den oben genannten Techniken können Sie ein benutzerdefiniertes Debugger-Plugin erstellen, das
pytest_testrun_call()
Hook, um dieAssertionError
Ausnahme abzufangenPdb
Unterklasse, die einen Haltepunkt in der Zeile vor dem Assert festlegt und automatisch einen Sprung zur Sekunde ausführt, wenn der Haltepunkt erreicht wird, gefolgt von einemc
Fortfahren.Anstatt darauf zu warten, dass eine Zusicherung fehlschlägt, können Sie das Festlegen von Haltepunkten für jeden
assert
in einem Test gefundenen Test automatisieren (mithilfe der Quellcode-Analyse können Sie trivialerweise Zeilennummern fürast.Assert
Knoten in einem AST des Tests extrahieren ) und den bestätigten Test ausführen Verwenden Sie Debugger-Skriptbefehle und verwenden Sie denjump
Befehl, um die Zusicherung selbst zu überspringen. Sie müssten einen Kompromiss eingehen; Führen Sie alle Tests unter einem Debugger aus (was langsam ist, da der Interpreter für jede Anweisung eine Trace-Funktion aufrufen muss) oder wenden Sie diese nur auf fehlgeschlagene Tests an und zahlen Sie den Preis für die erneute Ausführung dieser Tests von Grund auf.Ein solches Plugin wäre eine Menge Arbeit, ich werde hier kein Beispiel schreiben, teils weil es sowieso nicht in eine Antwort passt, teils weil ich nicht denke, dass es die Zeit wert ist . Ich würde einfach den Debugger öffnen und den Sprung manuell machen. Eine fehlgeschlagene Zusicherung weist auf einen Fehler im Test selbst oder im zu testenden Code hin. Sie können sich also genauso gut auf das Debuggen des Problems konzentrieren.
quelle
Mit pytest --pdb können Sie ohne Codeänderung genau das erreichen, was Sie wollen .
Mit Ihrem Beispiel:
Führen Sie mit --pdb aus:
Sobald ein Test fehlschlägt, können Sie ihn mit dem eingebauten Python-Debugger debuggen. Wenn Sie
continue
mit dem Debuggen fertig sind, können Sie mit den restlichen Tests fortfahren.quelle
Wenn Sie PyCharm verwenden, können Sie einen Ausnahme-Haltepunkt hinzufügen, um die Ausführung anzuhalten, wenn eine Bestätigung fehlschlägt. Wählen Sie Haltepunkte anzeigen (STRG-UMSCHALT-F8) und fügen Sie einen Ausnahmehandler für AssertionError hinzu. Beachten Sie, dass dies die Ausführung der Tests verlangsamen kann.
Andernfalls haben Sie einige Optionen , wenn es Ihnen nichts ausmacht, am Ende jedes fehlgeschlagenen Tests (kurz vor dem Fehler) anzuhalten, anstatt an dem Punkt, an dem die Behauptung fehlschlägt. Beachten Sie jedoch, dass zu diesem Zeitpunkt möglicherweise bereits verschiedene Bereinigungscodes ausgeführt wurden, z. B. das Schließen von Dateien, die im Test geöffnet wurden. Mögliche Optionen sind:
Mit der Option --pdb können Sie pytest anweisen, Sie bei Fehlern in den Debugger zu verschieben .
Sie können den folgenden Dekorator definieren und damit jede relevante Testfunktion dekorieren. (Neben der Nachricht protokolliert, können Sie auch einen Start pdb.post_mortem an dieser Stelle oder sogar eine interaktive code.interact mit den Einheimischen des Rahmens , wo die Ausnahme entstanden sind , wie beschrieben in dieser Antwort .)
quelle
pause_on_assert
, um aus einer Datei zu lesen, um zu entscheiden, ob Sie pausieren möchten oder nicht.Eine einfache Lösung, wenn Sie bereit sind, Visual Studio-Code zu verwenden, könnte darin bestehen, bedingte Haltepunkte zu verwenden .
Auf diese Weise können Sie Ihre Aussagen einrichten, zum Beispiel:
Fügen Sie dann einen bedingten Haltepunkt in Ihre Assert-Zeile ein, der nur dann unterbrochen wird, wenn Ihre Assertion fehlschlägt:
quelle