122 votes

Sortie des données d'un test unitaire en Python

Si j'écris des tests unitaires en Python (à l'aide du module unittest), est-il possible de sortir les données d'un test qui a échoué, afin de pouvoir les examiner pour aider à déduire ce qui a causé l'erreur ?

Je sais qu'il est possible de créer un message personnalisé, qui peut contenir certaines informations, mais il arrive que l'on ait affaire à des données plus complexes, qui ne peuvent pas être facilement représentées sous forme de chaîne.

Par exemple, supposons que vous ayez une classe Foo, et que vous testiez une méthode bar, en utilisant les données d'une liste appelée testdata :

class TestBar(unittest.TestCase):
    def runTest(self):
        for t1, t2 in testdata:
            f = Foo(t1)
            self.assertEqual(f.bar(t2), 2)

Si le test échoue, je pourrais vouloir sortir t1, t2 et/ou f, pour voir pourquoi ces données particulières ont entraîné un échec. Par sortie, je veux dire que les variables sont accessibles comme n'importe quelles autres variables, après que le test ait été exécuté.

79voto

S.Lott Points 207588

Nous utilisons le module de journalisation pour cela.

Par exemple :

import logging
class SomeTest( unittest.TestCase ):
    def testSomething( self ):
        log= logging.getLogger( "SomeTest.testSomething" )
        log.debug( "this= %r", self.this )
        log.debug( "that= %r", self.that )
        self.assertEqual( 3.14, pi )

if __name__ == "__main__":
    logging.basicConfig( stream=sys.stderr )
    logging.getLogger( "SomeTest.testSomething" ).setLevel( logging.DEBUG )
    unittest.main()

Cela nous permet d'activer le débogage pour des tests spécifiques dont nous savons qu'ils échouent et pour lesquels nous voulons des informations de débogage supplémentaires.

Ma méthode préférée, cependant, n'est pas de passer beaucoup de temps à déboguer, mais d'écrire des tests plus fins pour exposer le problème.

0 votes

Que se passe-t-il si j'appelle une méthode foo dans testSomething et qu'elle enregistre quelque chose. Comment puis-je voir le résultat sans passer le logger à foo ?

0 votes

@simao : Qu'est-ce que foo ? Une fonction distincte ? Une fonction de méthode de SomeTest ? Dans le premier cas, une fonction peut avoir son propre logger. Dans le second cas, la fonction de l'autre méthode peut avoir son propre logger. Savez-vous comment le logging Le paquet fonctionne ? Les enregistreurs multiples sont la norme.

9 votes

J'ai configuré l'enregistrement de la manière exacte que vous avez indiquée. Je suppose que cela fonctionne, mais où puis-je voir la sortie ? Il n'y a pas de sortie sur la console. J'ai essayé de le configurer avec la journalisation vers un fichier, mais cela ne produit pas de sortie non plus.

76voto

F.C. Points 3283

Dans Python 2.7, vous pouviez utiliser un paramètre supplémentaire, msg pour ajouter des informations au message d'erreur, comme ceci :

self.assertEqual(f.bar(t2), 2, msg='{0}, {1}'.format(t1, t2))

La documentation officielle est aquí .

1 votes

Fonctionne aussi en Python 3.

18 votes

La documentation y fait allusion mais cela vaut la peine de le mentionner explicitement : par défaut, si msg est utilisé, il remplacera le message d'erreur normal. Pour obtenir msg ajouté au message d'erreur normal, vous devez également définir le paramètre TestCase.longMessage à True

2 votes

C'est bien de savoir que l'on peut passer un message d'erreur personnalisé, mais j'étais intéressé par l'impression d'un message indépendamment de l'erreur.

35voto

Ned Batchelder Points 128913

Vous pouvez utiliser de simples instructions d'impression, ou tout autre moyen d'écrire sur la sortie standard. Vous pouvez également invoquer le débogueur Python n'importe où dans vos tests.

Si vous utilisez nez pour exécuter vos tests (ce que je recommande), il collectera la sortie standard pour chaque test et ne vous la montrera que si le test a échoué, afin que vous n'ayez pas à vivre avec une sortie encombrée lorsque les tests réussissent.

nez dispose également de commutateurs pour afficher automatiquement les variables mentionnées dans les assertions, ou pour invoquer le débogueur en cas d'échec des tests. Par exemple, -s ( --nocapture ) empêche la capture de la sortie standard.

0 votes

Malheureusement, nose ne semble pas collecter les journaux écrits sur stdout/err en utilisant le cadre de journalisation. J'ai le print y log.debug() l'un à côté de l'autre, et activez explicitement DEBUG l'enregistrement à la racine de la setUp() mais seulement la méthode print La sortie apparaît.

7 votes

nosetests -s affiche le contenu de stdout qu'il y ait une erreur ou non - quelque chose que je trouve utile.

0 votes

Je ne trouve pas les options permettant d'afficher automatiquement les variables dans la documentation de nose. Pouvez-vous m'indiquer un document qui les décrit ?

19voto

monkut Points 14549

Je ne pense pas que ce soit ce que vous recherchez. Il n'y a aucun moyen d'afficher les valeurs des variables qui n'échouent pas, mais cela peut vous aider à vous rapprocher de l'affichage des résultats comme vous le souhaitez.

Vous pouvez utiliser le Objet TestResult retourné par le TestRunner.run() pour l'analyse et le traitement des résultats. En particulier, TestResult.errors et TestResult.failures.

À propos de l'objet TestResults :

http://docs.python.org/library/unittest.html#id3

Et quelques codes pour vous guider dans la bonne direction :

>>> import random
>>> import unittest
>>>
>>> class TestSequenceFunctions(unittest.TestCase):
...     def setUp(self):
...         self.seq = range(5)
...     def testshuffle(self):
...         # make sure the shuffled sequence does not lose any elements
...         random.shuffle(self.seq)
...         self.seq.sort()
...         self.assertEqual(self.seq, range(10))
...     def testchoice(self):
...         element = random.choice(self.seq)
...         error_test = 1/0
...         self.assert_(element in self.seq)
...     def testsample(self):
...         self.assertRaises(ValueError, random.sample, self.seq, 20)
...         for element in random.sample(self.seq, 5):
...             self.assert_(element in self.seq)
...
>>> suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
>>> testResult = unittest.TextTestRunner(verbosity=2).run(suite)
testchoice (__main__.TestSequenceFunctions) ... ERROR
testsample (__main__.TestSequenceFunctions) ... ok
testshuffle (__main__.TestSequenceFunctions) ... FAIL

======================================================================
ERROR: testchoice (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<stdin>", line 11, in testchoice
ZeroDivisionError: integer division or modulo by zero

======================================================================
FAIL: testshuffle (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<stdin>", line 8, in testshuffle
AssertionError: [0, 1, 2, 3, 4] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

----------------------------------------------------------------------
Ran 3 tests in 0.031s

FAILED (failures=1, errors=1)
>>>
>>> testResult.errors
[(<__main__.TestSequenceFunctions testMethod=testchoice>, 'Traceback (most recent call last):\n  File "<stdin>"
, line 11, in testchoice\nZeroDivisionError: integer division or modulo by zero\n')]
>>>
>>> testResult.failures
[(<__main__.TestSequenceFunctions testMethod=testshuffle>, 'Traceback (most recent call last):\n  File "<stdin>
", line 8, in testshuffle\nAssertionError: [0, 1, 2, 3, 4] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n')]
>>>

5voto

Silverfish Points 494

Je pense que j'ai peut-être trop réfléchi à tout ça. Une méthode que j'ai trouvée et qui fait l'affaire consiste simplement à avoir une variable globale qui accumule les données de diagnostic.

Quelque chose comme ça :

log1 = dict()
class TestBar(unittest.TestCase):
    def runTest(self):
        for t1, t2 in testdata:
            f = Foo(t1)
            if f.bar(t2) != 2:
                log1("TestBar.runTest") = (f, t1, t2)
                self.fail("f.bar(t2) != 2")

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X