J'utilise des instructions d'assertion Python pour faire correspondre le comportement réel et attendu. Je n'ai pas de contrôle sur ces derniers comme s'il y avait un cas de test d'erreur abandonné. Je veux prendre le contrôle de l'erreur d'assertion et définir si je veux abandonner le testcase en cas d'échec de l'assertion ou non.
Aussi, je veux ajouter quelque chose comme s'il y a une erreur d'assertion, le cas de test doit être suspendu et l'utilisateur peut reprendre à tout moment.
Je ne sais pas comment faire ça
Exemple de code, nous utilisons pytest ici
import pytest
def test_abc():
a = 10
assert a == 10, "some error message"
Below is my expectation
Lorsque assert jette une assertionError, je devrais avoir la possibilité de mettre le testcase en pause et de pouvoir déboguer et reprendre plus tard. Pour faire une pause et reprendre, j'utiliserai le tkinter
module. Je ferai une fonction d'affirmation comme ci-dessous
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)
À l'avenir, je dois changer chaque déclaration d'assertion avec cette fonction comme
import pytest
def test_abc():
a = 10
# Suppose some code and below is the assert statement
_assertCustom("assert a == 10, 'error message'")
C'est trop d'effort pour moi car je dois faire des changements à des milliers d'endroits où j'ai utilisé assert. Existe-t-il un moyen simple de le faire danspytest
Summary:
J'ai besoin de quelque chose où je peux mettre le testcase en pause en cas d'échec, puis reprendre après le débogage. Je sais tkinter
et c'est la raison pour laquelle je l'ai utilisé. Toute autre idée sera la bienvenue
Note
: Le code ci-dessus n'est pas encore testé. Il peut également y avoir de petites erreurs de syntaxe
Edit: Merci pour les réponses. Étendre cette question un peu en avance maintenant. Et si je veux changer le comportement de l'assert. Actuellement, lorsqu'il y a une erreur d'assertion, le testcase se ferme. Que se passe-t-il si je veux choisir si j'ai besoin de la sortie de testcase en cas d'échec d'assertion particulier ou non. Je ne veux pas écrire de fonction d'assertion personnalisée comme mentionné ci-dessus car de cette façon je dois changer à plusieurs endroits
assert
mais écrivez vos propres fonctions de vérification qui font ce que vous voulez.pytest
pour vos cas de test. Il prend en charge l' utilisation assert et sauter des tests ainsi que de nombreuses autres fonctionnalités qui facilitent l' écriture des suites de test plus facile.assert cond, "msg"
de votre code_assertCustom("assert cond, 'msg'")
?sed
Un monoligne pourrait probablement le faire.Réponses:
Vous utilisez
pytest
, ce qui vous offre de nombreuses options pour interagir avec les tests qui échouent. Il vous donne des options de ligne de commande et plusieurs crochets pour rendre cela possible. Je vais vous expliquer comment utiliser chacun et où vous pouvez faire des personnalisations pour répondre à vos besoins de débogage spécifiques.J'entrerai également dans des options plus exotiques qui vous permettraient d'ignorer entièrement des assertions spécifiques, si vous vous sentez vraiment obligé.
Gérer les exceptions, pas affirmer
Notez qu'un test qui échoue n'arrête pas normalement pytest; uniquement si vous avez activé le explicitement lui dire de quitter après un certain nombre d'échecs . De plus, les tests échouent car une exception est déclenchée;
assert
augmenteAssertionError
mais ce n'est pas la seule exception qui fera échouer un test! Vous voulez contrôler la façon dont les exceptions sont gérées, pas les modifierassert
.Cependant, une assertion défaillante mettra fin au test individuel. En effet, une fois qu'une exception est levée en dehors d'un
try...except
bloc, Python déroule le cadre de fonction actuel, et il n'y a pas de retour en arrière.Je ne pense pas que c'est ce que vous voulez, à en juger par votre description de vos
_assertCustom()
tentatives pour relancer l'assertion, mais je vais néanmoins discuter de vos options plus bas.Débogage post-mortem dans pytest avec pdb
Pour les différentes options pour gérer les échecs dans un débogueur, je vais commencer par le
--pdb
commutateur de ligne de commande , qui ouvre l'invite de débogage standard en cas d'échec d'un test (sortie élidée pour plus de concision):Avec ce commutateur, lorsqu'un test échoue, pytest démarre une session de débogage post-mortem . C'est essentiellement exactement ce que vous vouliez; pour arrêter le code au moment où un test a échoué et ouvrez le débogueur pour consulter l'état de votre test. Vous pouvez interagir avec les variables locales du test, les globaux et les locaux et globaux de chaque trame de la pile.
Ici, pytest vous donne un contrôle total sur la sortie ou non après ce point: si vous utilisez la
q
commande quit, pytest quitte également la course, en utilisantc
for continue, le contrôle reviendra à pytest et le prochain test sera exécuté.Utilisation d'un débogueur alternatif
Vous n'êtes pas lié au
pdb
débogueur pour cela; vous pouvez définir un débogueur différent avec le--pdbcls
commutateur. Toute implémentationpdb.Pdb()
compatible fonctionnerait, y compris l' implémentation du débogueur IPython ou la plupart des autres débogueurs Python (le débogueur pudb nécessite que le-s
commutateur soit utilisé ou un plugin spécial ). Le commutateur prend un module et une classe, par exemple, pour utiliser,pudb
vous pouvez utiliser:Vous pouvez utiliser cette fonctionnalité pour écrire votre propre classe wrapper
Pdb
qui renvoie simplement immédiatement si l'échec spécifique n'est pas quelque chose qui vous intéresse.pytest
UtilisePdb()
exactement comme lepdb.post_mortem()
fait :Voici
t
un objet traceback . Lorsquep.interaction(None, t)
revient,pytest
continue avec le test suivant, sauf sip.quitting
est défini surTrue
(à quel point pytest puis se termine).Voici un exemple d'implémentation qui indique que nous refusons de déboguer et renvoie immédiatement, sauf si le test est déclenché
ValueError
, enregistré sousdemo/custom_pdb.py
:Lorsque j'utilise ceci avec la démo ci-dessus, c'est la sortie (encore une fois, élidée par souci de concision):
Les introspections ci-dessus
sys.last_type
pour déterminer si l'échec est «intéressant».Cependant, je ne peux pas vraiment recommander cette option sauf si vous voulez écrire votre propre débogueur en utilisant tkInter ou quelque chose de similaire. Notez que c'est une grande entreprise.
Échecs de filtrage; choisissez et choisissez quand ouvrir le débogueur
Le prochain niveau est le pytest débogage et interaction crochets ; ce sont des points d'ancrage pour les personnalisations de comportement, pour remplacer ou améliorer la façon dont pytest gère normalement des choses comme la gestion d'une exception ou l'entrée dans le débogueur via
pdb.set_trace()
oubreakpoint()
(Python 3.7 ou plus récent).L'implémentation interne de ce hook est également responsable de l'impression de la
>>> entering PDB >>>
bannière ci-dessus, donc l'utilisation de ce hook pour empêcher le débogueur de fonctionner signifie que vous ne verrez pas du tout cette sortie. Vous pouvez avoir votre propre hook puis déléguer au hook d'origine lorsqu'un échec de test est «intéressant», et donc filtrer les échecs de test indépendamment du débogueur que vous utilisez! Vous pouvez accéder à l'implémentation interne en y accédant par son nom ; le plugin de hook interne pour cela est nommépdbinvoke
. Pour l'empêcher de fonctionner, vous devez le désinscrire , mais enregistrer une référence, nous pouvons l'appeler directement si nécessaire.Voici un exemple d'implémentation d'un tel crochet; vous pouvez le placer dans n'importe quel emplacement depuis lequel les plugins sont chargés ; Je l'ai mis
demo/conftest.py
:Le plugin ci-dessus utilise le
TerminalReporter
plugin interne pour écrire des lignes sur le terminal; cela rend la sortie plus propre lors de l'utilisation du format d'état de test compact par défaut et vous permet d'écrire des choses sur le terminal même avec la capture de sortie activée.L'exemple enregistre l'objet plugin avec
pytest_exception_interact
hook via un autre hook,pytest_configure()
mais en s'assurant qu'il s'exécute suffisamment tard (en utilisant@pytest.hookimpl(trylast=True)
) pour pouvoir annuler l'enregistrement dupdbinvoke
plugin interne . Lorsque le crochet est appelé, l'exemple teste l'call.exceptinfo
objet ; vous pouvez également vérifier le nœud ou le rapport également.Avec l'exemple de code ci-dessus en place dans
demo/conftest.py
, l'test_ham
échec du test est ignoré, seul l'test_spam
échec du test, qui se déclencheValueError
, entraîne l'ouverture de l'invite de débogage:Pour réitérer, l'approche ci-dessus présente l'avantage supplémentaire que vous pouvez combiner cela avec n'importe quel débogueur qui fonctionne avec pytest , y compris pudb, ou le débogueur IPython:
Il a également beaucoup plus de contexte sur le test exécuté (via l'
node
argument) et l'accès direct à l'exception levée (via l'call.excinfo
ExceptionInfo
instance).Notez que des plugins de débogage pytest spécifiques (tels que
pytest-pudb
oupytest-pycharm
) enregistrent leur proprepytest_exception_interact
hooksp. Une implémentation plus complète devrait parcourir tous les plugins dans le gestionnaire de plugins pour remplacer automatiquement les plugins, en utilisantconfig.pluginmanager.list_name_plugin
ethasattr()
pour tester chaque plugin.Faire disparaître complètement les échecs
Bien que cela vous donne un contrôle total sur le débogage d'un test qui a échoué, cela laisse toujours le test comme échoué même si vous avez choisi de ne pas ouvrir le débogueur pour un test donné. Si vous voulez faire des échecs disparaissent complètement, vous pouvez utiliser faire un crochet différent:
pytest_runtest_call()
.Lorsque pytest exécute des tests, il exécute le test via le crochet ci-dessus, qui devrait renvoyer
None
ou déclencher une exception. À partir de cela, un rapport est créé, éventuellement une entrée de journal est créée et si le test a échoué, lepytest_exception_interact()
hook susmentionné est appelé. Donc, tout ce que vous devez faire est de changer le résultat que ce crochet produit; au lieu d'une exception, il ne devrait tout simplement pas retourner quoi que ce soit.La meilleure façon de le faire est d'utiliser un crochet . Les enveloppes de crochet n'ont pas à effectuer le travail réel, mais ont plutôt la possibilité de modifier ce qui arrive au résultat d'un crochet. Tout ce que vous avez à faire est d'ajouter la ligne:
dans votre implémentation de wrapper de hook et vous avez accès au résultat du hook , y compris l'exception de test via
outcome.excinfo
. Cet attribut est défini sur un tuple de (type, instance, traceback) si une exception a été déclenchée dans le test. Vous pouvez également appeleroutcome.get_result()
et utiliser latry...except
gestion standard .Alors, comment faites-vous un test réussi? Vous avez 3 options de base:
pytest.xfail()
le wrapper.pytest.skip()
.outcome.force_result()
méthode ; définissez le résultat sur une liste vide ici (ce qui signifie: le hook enregistré n'a produit queNone
), et l'exception est entièrement effacée.Ce que vous utilisez dépend de vous. Assurez-vous de vérifier d'abord le résultat des tests ignorés et des échecs attendus car vous n'avez pas besoin de gérer ces cas comme si le test avait échoué. Vous pouvez accéder aux exceptions spéciales que ces options déclenchent via
pytest.skip.Exception
etpytest.xfail.Exception
.Voici un exemple d'implémentation qui marque les tests ayant échoué qui ne se déclenchent pas
ValueError
, comme ignorés :Une fois mis dans
conftest.py
la sortie devient:J'ai utilisé le
-r a
drapeau pour clarifier ce qui atest_ham
été ignoré maintenant.Si vous remplacez l'
pytest.skip()
appel parpytest.xfail("[XFAIL] ignoring everything but ValueError")
, le test est marqué comme un échec attendu:et en utilisant le
outcome.force_result([])
marque comme passé:C'est à vous de choisir celui qui vous convient le mieux. Pour
skip()
etxfail()
j'ai imité le format de message standard (préfixé par[NOTRUN]
ou[XFAIL]
) mais vous êtes libre d'utiliser tout autre format de message que vous souhaitez.Dans les trois cas, pytest n'ouvrira pas le débogueur pour les tests dont vous avez modifié le résultat en utilisant cette méthode.
Modification des instructions d'assertion individuelles
Si vous souhaitez modifier les
assert
tests dans un test , vous vous préparez pour beaucoup plus de travail. Oui, c'est techniquement possible, mais seulement en réécrivant le code que Python va exécuter au moment de la compilation .Lorsque vous utilisez
pytest
, cela est déjà fait . Pytest réécrit lesassert
instructions pour vous donner plus de contexte lorsque vos assertions échouent ; voir cet article de blog pour un bon aperçu de ce qui se fait exactement, ainsi que le_pytest/assertion/rewrite.py
code source . Notez que ce module fait plus de 1k lignes et nécessite que vous compreniez comment fonctionnent les arbres de syntaxe abstraite de Python . Si vous le faites, vous pouvez monkeypatch ce module pour y ajouter vos propres modifications, y compris entourer leassert
avec untry...except AssertionError:
gestionnaire.Cependant , vous ne pouvez pas simplement désactiver ou ignorer les assertions de manière sélective, car les instructions ultérieures peuvent facilement dépendre de l'état (dispositions d'objets spécifiques, jeu de variables, etc.) contre lequel une assertion ignorée était censée se prémunir. Si une assertion teste qui
foo
ne l'est pasNone
, alors une assertion plus récente repose sur l'foo.bar
existence, alors vous rencontrerez simplement unAttributeError
là-bas, etc. Restez à relancer l'exception, si vous avez besoin de suivre cette voie.Je ne vais pas entrer dans les détails sur la réécriture
asserts
ici, car je ne pense pas que cela vaille la peine, étant donné la quantité de travail nécessaire, et avec le débogage post mortem vous donnant accès à l'état du test à la point d'échec de l'assertion de toute façon .Notez que si vous voulez faire cela, vous n'avez pas besoin d'utiliser
eval()
(ce qui ne fonctionnerait pas de toute façon,assert
est une instruction, vous devriez donc utiliser à laexec()
place), et vous n'auriez pas à exécuter l'assertion deux fois (qui peut entraîner des problèmes si l'expression utilisée dans l'état d'assertion est modifiée). Au lieu de cela, vous incorporez least.Assert
nœud à l'intérieur d'unast.Try
nœud et attachez un gestionnaire except qui utilise unast.Raise
nœud vide, relancez l'exception qui a été interceptée.Utilisation du débogueur pour ignorer les instructions d'assertion.
Le débogueur Python vous permet en fait d' ignorer les instructions à l'aide de la commande
j
/jump
. Si vous savez à l' avance qu'une affirmation spécifique va échouer, vous pouvez l' utiliser pour le contourner. Vous pouvez exécuter vos tests avec--trace
, ce qui ouvre le débogueur au début de chaque test , puis émettez unj <line after assert>
pour l'ignorer lorsque le débogueur est suspendu juste avant l'assertion.Vous pouvez même automatiser cela. En utilisant les techniques ci-dessus, vous pouvez créer un plugin de débogage personnalisé qui
pytest_testrun_call()
crochet pour attraper l'AssertionError
exceptionPdb
sous-classe qui définit un point d'arrêt sur la ligne avant l'assertion, et exécute automatiquement un saut à la seconde lorsque le point d'arrêt est atteint, suivi d'unec
poursuite.Ou, au lieu d'attendre l'échec d'une assertion, vous pouvez automatiser la définition de points d'arrêt pour chacun
assert
trouvé dans un test (à nouveau en utilisant l'analyse du code source, vous pouvez extraire trivialement les numéros de ligne desast.Assert
nœuds dans un AST du test), exécuter le test affirmé à l'aide de commandes de script du débogueur et utilisez lajump
commande pour ignorer l'assertion elle-même. Il faudrait faire un compromis; exécutez tous les tests sous un débogueur (ce qui est lent car l'interpréteur doit appeler une fonction de trace pour chaque instruction) ou n'appliquez cela qu'aux tests qui échouent et payez le prix de la réexécution de ces tests à partir de zéro.Un tel plugin serait beaucoup de travail à créer, je ne vais pas écrire un exemple ici, en partie parce qu'il ne rentrerait pas dans une réponse de toute façon, et en partie parce que je ne pense pas que cela en vaille la peine . Je viens d'ouvrir le débogueur et de faire le saut manuellement. Une assertion défaillante indique un bogue dans le test lui-même ou le code en cours de test, vous pouvez donc tout aussi bien vous concentrer sur le débogage du problème.
la source
Vous pouvez obtenir exactement ce que vous voulez sans aucune modification de code avec pytest --pdb .
Avec votre exemple:
Exécutez avec --pdb:
Dès qu'un test échoue, vous pouvez le déboguer avec le débogueur python intégré. Si vous avez terminé le débogage, vous pouvez le faire
continue
avec le reste des tests.la source
Si vous utilisez PyCharm, vous pouvez ajouter un point d'arrêt d'exception pour suspendre l'exécution en cas d'échec d'une assertion. Sélectionnez Afficher les points d'arrêt (CTRL-SHIFT-F8) et ajoutez un gestionnaire d'exceptions de montée pour AssertionError. Notez que cela peut ralentir l'exécution des tests.
Sinon, si cela ne vous dérange pas de faire une pause à la fin de chaque test qui échoue (juste avant qu'il ne fasse une erreur) plutôt qu'au moment où l'assertion échoue, vous avez quelques options. Notez cependant qu'à ce stade, plusieurs codes de nettoyage, tels que la fermeture des fichiers ouverts dans le test, peuvent déjà avoir été exécutés. Les options possibles sont:
Vous pouvez dire à pytest de vous déposer dans le débogueur en cas d'erreur en utilisant l' option --pdb .
Vous pouvez définir le décorateur suivant et décorer chaque fonction de test pertinente avec lui. ( En plus de se connecter un message, vous pouvez également démarrer un pdb.post_mortem à ce stade, ou même un outil interactif code.interact avec les habitants du cadre où l'exception est originaire, comme décrit dans cette réponse .)
la source
pause_on_assert
pour lire dans un fichier pour décider de suspendre ou non.Une solution simple, si vous souhaitez utiliser Visual Studio Code, pourrait être d'utiliser des points d'arrêt conditionnels .
Cela vous permettrait de configurer vos assertions, par exemple:
Ajoutez ensuite un point d'arrêt conditionnel dans votre ligne d'assertion qui ne se cassera qu'en cas d'échec de votre assertion:
la source