Obtenir les résultats unittest de Python dans une méthode tearDown ()
Est-il possible d'obtenir les résultats d'un test (c'est à dire si toutes les affirmations ont passé), dans un tearDown() la méthode? J'exécute des scripts de sélénium, et j'aimerais faire quelques reportages à partir de inside tearDown (), mais je ne sais pas si c'est possible.
10 réponses
avertissement: je n'ai aucun moyen de revérifier la théorie suivante pour le moment, étant à l'écart d'une dev box. Donc c'est peut-être un tir dans le noir.
peut-être pourriez-vous vérifier la valeur de retour de sys.exc_info()
à l'intérieur de votre méthode tearDown (), si elle retourne (None, None, None)
, vous savez que le cas de test a réussi. Sinon, vous pouvez utiliser le tuple retourné pour interroger l'objet exception.
voir sys.exc_info de la documentation.
une autre approche plus explicite est d'écrire un décorateur de méthode que vous pourriez mettre sur toutes vos méthodes de cas de test qui exigent cette manipulation spéciale. Ce décorateur peut intercepter des exceptions d'assertion et sur la base de cela modifier un État dans self
permettant à votre méthode de démontage pour apprendre ce qui se passe.
@assertion_tracker
def test_foo(self):
# some test logic
si vous regardez l'implémentation de unittest.TestCase.run
, vous pouvez voir que tous les résultats de test sont collectés dans l'objet résultat (typiquement une instance unittest.TestResult
) passé comme argument. Aucun statut de résultat n'est laissé dans l'objet unittest.TestCase
.
il n'y a donc pas grand chose que vous puissiez faire dans la méthode unittest.TestCase.tearDown
à moins que vous ne brisiez impitoyablement l'élégant découplage des cas d'essai et des résultats d'essai avec quelque chose comme ceci:
import unittest
class MyTest(unittest.TestCase):
currentResult = None # holds last result object passed to run method
def setUp(self):
pass
def tearDown(self):
ok = self.currentResult.wasSuccessful()
errors = self.currentResult.errors
failures = self.currentResult.failures
print ' All tests passed so far!' if ok else \
' %d errors and %d failures so far' % \
(len(errors), len(failures))
def run(self, result=None):
self.currentResult = result # remember result for use in tearDown
unittest.TestCase.run(self, result) # call superclass run method
def test_onePlusOneEqualsTwo(self):
self.assertTrue(1 + 1 == 2) # succeeds
def test_onePlusOneEqualsThree(self):
self.assertTrue(1 + 1 == 3) # fails
def test_onePlusNoneIsNone(self):
self.assertTrue(1 + None is None) # raises TypeError
if __name__ == '__main__':
unittest.main()
EDIT: Cela fonctionne pour Python 2.6-3.3, (modifié pour le nouveau Python bellow ).
Cette solution est pour Python versions 2.7 à 3.6 (la version la plus récente et le développement peu avant 3.7-alpha ), sans décorateurs ou autre modification dans n'importe quel code avant tearDown
. Tout fonctionne selon la classification intégrée des résultats. Les tests expectedFailure
ou "skipped tests" sont également reconnus correctement. Il évalue le résultat de l'examen, pas un résumé de tous les tests passé jusqu'à présent. Compatible aussi avec pytest .
import unittest
class MyTest(unittest.TestCase):
def tearDown(self):
if hasattr(self, '_outcome'): # Python 3.4+
result = self.defaultTestResult() # these 2 methods have no side effects
self._feedErrorsToResult(result, self._outcome.errors)
else: # Python 3.2 - 3.3 or 3.0 - 3.1 and 2.7
result = getattr(self, '_outcomeForDoCleanups', self._resultForDoCleanups)
error = self.list2reason(result.errors)
failure = self.list2reason(result.failures)
ok = not error and not failure
# demo: report short info immediately (not important)
if not ok:
typ, text = ('ERROR', error) if error else ('FAIL', failure)
msg = [x for x in text.split('\n')[1:] if not x.startswith(' ')][0]
print("\n%s: %s\n %s" % (typ, self.id(), msg))
def list2reason(self, exc_list):
if exc_list and exc_list[-1][0] is self:
return exc_list[-1][1]
# DEMO tests
def test_success(self):
self.assertEqual(1, 1)
def test_fail(self):
self.assertEqual(2, 1)
def test_error(self):
self.assertEqual(1 / 0, 1)
commentaires: une seule ou zéro exception (erreur ou échec) doit être déclarée, car on ne peut s'attendre à ce qu'il y en ait plus avant tearDown
. Le paquet unittest
prévoit qu'une deuxième exception peut être soulevée par démontage. Par conséquent, les listes errors
et failures
ne peuvent contenir ensemble qu'un ou zéro élément avant démontage. Lignes après "démo" les commentaires font état d'un court résultat.
Démo output: (Non important)
$ python3.5 -m unittest test
EF.
ERROR: test.MyTest.test_error
ZeroDivisionError: division by zero
FAIL: test.MyTest.test_fail
AssertionError: 2 != 1
==========================================================
... skipped usual output from unittest with tracebacks ...
...
Ran 3 tests in 0.002s
FAILED (failures=1, errors=1)
Comparaison avec d'autres solutions - (par rapport à commettre l'histoire de Python référentiel source):
-
Cette solution utilise un attribut privé d'instance de TestCase comme plusieurs d'autres solutions, mais j'ai vérifié attentivement toutes les propagations pertinentes dans le dépôt source de Python que trois noms alternatifs couvrent l'histoire du code depuis Python 2.7 à 3.6.2 sans aucune discontinuité. Il peut être un problème après quelques de nouveaux grands Version Python, mais elle pourrait être clairement reconnue, sautée et facilement corrigée plus tard pour un nouveau Python. Un avantage est que rien n'est modifié avant en cours d'exécution démontage, il ne devrait jamais briser le test et toutes les fonctionnalités de unittest est supporté, fonctionne avec pytest, pourrait fonctionner avec de nombreux paquets prolongeant, mais pas avec nosetest (pas de surprise car nosetest n'est pas compatible par exemple avec unittest.expectedFailure).
-
les solutions avec décorateurs sur les méthodes d'essai de l'utilisateur ou avec un failureException ( mgilson , Pavel Repin 2nd way, kenorb) sont robustes contre avenir Python versions, mais si tout devrait fonctionner complètement, ils poussent comme une boule de neige avec plus d'exceptions supportées et plus d'internes répliqués de unittest. Décoré de fonctions ont moins lisible retraçage (encore plus de niveaux ajoutés par un décorateur), ils sont plus complexes pour Débogage et il est peu rassurant si un autre décorateur plus important a un problème. (Grâce à mgilson la fonctionnalité de base est prête et connue les problèmes peuvent être résolus.)
-
La solution avec modifired
run
méthode et enclenchéresult
paramètre- ( scoffey ) devrait fonctionner
aussi pour la version 2.6 de Python. L'interprétation des résultats peut être améliorée
exigences de la question, mais rien ne peut fonctionner en Python 3.4+,
parce que
result
est mis à jour après l'appel de larme, jamais avant. - Mark G.: (testé avec Python 2.7, 3.2, 3.3, 3.4 et avec nosetest)
- ( scoffey ) devrait fonctionner
aussi pour la version 2.6 de Python. L'interprétation des résultats peut être améliorée
exigences de la question, mais rien ne peut fonctionner en Python 3.4+,
parce que
-
solution by
exc_info()
(Pavel Repin 2st way) NE FONCTIONNE qu'avec Python 2. -
D'autres solutions sont essentiellement similaires, mais moins complètes ou avec plus de inconvénient.
expliqué par le dépôt source Python
= Lib/unittest/case.py =
Python V 2.7-3.3
class TestCase(object):
...
def run(self, result=None):
...
self._outcomeForDoCleanups = result # Python 3.2, 3.3
# self._resultForDoCleanups = result # Python 2.7
# # Python 2.6 - no result saved
...
try:
testMethod()
except... # many times for different exception classes
result.add...(self, sys.exc_info()) # _addSkip, addError, addFailure
...
try:
self.tearDown()
...
Python v. 3.4-3.6
def run(self, result=None):
...
# outocome is a context manager to catch and collect different exceptions
self._outcome = outcome
...
with outcome...(self):
testMethod()
...
with outcome...(self):
self.tearDown()
...
self._feedErrorsToResult(result, outcome.errors)
Note (en lisant les messages de propagation): une des raisons pour lesquelles les résultats des tests sont tellement découplés des tests est fuites de mémoire prévention. Chaque information d'exception peut accéder aux cadres de l'état de processus échoué, y compris toutes les variables locales. Si une trame est assignée à une variable locale dans un code block qui pourrait aussi échouer, alors une référence de mémoire croisée pourrait être facilement créée. Ce n'est pas terrible, grâce à garbage collector, mais la mémoire peut fragmenté plus rapidement que si la mémoire serait libérée correctement. C'est la raison pour laquelle les informations d'exception et de retraçage sont converties très rapidement en chaînes et pourquoi les objets temporaires comme self._outcome
sont encapsulés et placés à None dans un bloc finally
afin d'éviter les fuites de mémoire.
Si vous utilisez Python2 vous pouvez utiliser la méthode _resultForDoCleanups
. Cette méthode retourne un TextTestResult
objet:
<unittest.runner.TextTestResult run=1 errors=0 failures=0>
Vous pouvez utiliser cet objet pour vérifier le résultat de vos tests:
def tearDown(self):
if self._resultForDoCleanups.failures:
...
elif self._resultForDoCleanups.errors:
...
else:
#Success
si vous utilisez Python3 vous pouvez utiliser _outcomeForDoCleanups
:
def tearDown(self):
if not self._outcomeForDoCleanups.success:
...
suite à la réponse d'amatellanes, si vous êtes sur Python3.4, vous ne pouvez pas utiliser _outcomeForDoCleanups
. Voici ce que j'ai réussi à pirater ensemble:
def _test_has_failed(self):
for method, error in self._outcome.errors:
if error:
return True
return False
yucky, mais ça semble marcher.
cela dépend du type de rapport que vous souhaitez produire.
dans le cas où vous souhaitez faire certaines actions sur l'échec (comme générer des captures d'écran ), au lieu d'utiliser tearDown()
, vous pouvez atteindre cet objectif en remplaçant failureException
.
par exemple:
@property
def failureException(self):
class MyFailureException(AssertionError):
def __init__(self_, *args, **kwargs):
screenshot_dir = 'reports/screenshots'
if not os.path.exists(screenshot_dir):
os.makedirs(screenshot_dir)
self.driver.save_screenshot('{0}/{1}.png'.format(screenshot_dir, self.id()))
return super(MyFailureException, self_).__init__(*args, **kwargs)
MyFailureException.__name__ = AssertionError.__name__
return MyFailureException
Voici une solution pour ceux d'entre nous qui sont mal à l'aise en utilisant des solutions qui s'appuient sur unittest
internes:
tout d'abord, nous créons un décorateur qui placera un drapeau sur l'instance TestCase
pour déterminer si oui ou non le cas d'essai a échoué ou réussi:
import unittest
import functools
def _tag_error(func):
"""Decorates a unittest test function to add failure information to the TestCase."""
@functools.wraps(func)
def decorator(self, *args, **kwargs):
"""Add failure information to `self` when `func` raises an exception."""
self.test_failed = False
try:
func(self, *args, **kwargs)
except unittest.SkipTest:
raise
except Exception: # pylint: disable=broad-except
self.test_failed = True
raise # re-raise the error with the original traceback.
return decorator
ce décorateur est en fait assez simple. Il s'appuie sur le fait que unittest
détecte des essais ratés via Exceptions . Aussi loin que Je sais que la seule exception spéciale qui doit être traitée est unittest.SkipTest
(ce qui n'indique pas un échec au test). Toutes les autres exceptions indiquent des échecs de test, donc nous les marquons comme tels quand ils se présentent à nous.
nous pouvons maintenant utiliser ce décorateur directement:
class MyTest(unittest.TestCase):
test_failed = False
def tearDown(self):
super(MyTest, self).tearDown()
print(self.test_failed)
@_tag_error
def test_something(self):
self.fail('Bummer')
ça va devenir vraiment ennuyeux d'écrire tout le temps ce décorateur. Est-il un moyen que nous pouvons simplifier? Oui il y en a! * Nous pouvons écrire une métaclasse pour gérer l'application de la décoratrice pour nous:
class _TestFailedMeta(type):
"""Metaclass to decorate test methods to append error information to the TestCase instance."""
def __new__(cls, name, bases, dct):
for name, prop in dct.items():
# assume that TestLoader.testMethodPrefix hasn't been messed with -- otherwise, we're hosed.
if name.startswith('test') and callable(prop):
dct[name] = _tag_error(prop)
return super(_TestFailedMeta, cls).__new__(cls, name, bases, dct)
maintenant nous appliquons ceci à notre base TestCase
sous-classe et nous sommes tous ensemble:
import six # For python2.x/3.x compatibility
class BaseTestCase(six.with_metaclass(_TestFailedMeta, unittest.TestCase)):
"""Base class for all our other tests.
We don't really need this, but it demonstrates that the
metaclass gets applied to all subclasses too.
"""
class MyTest(BaseTestCase):
def tearDown(self):
super(MyTest, self).tearDown()
print(self.test_failed)
def test_something(self):
self.fail('Bummer')
il y a probablement un certain nombre de CAs qui ne sont pas traités correctement. Par exemple, ne détecte pas correctement les échecs subtests ou les échecs attendus. Je serais intéressé par d'autres modes de défaillance de ce, donc si vous trouvez un cas que je ne gère pas correctement, faites-le moi savoir dans les commentaires et je vais regarder.
* S'il n'y avait pas un moyen plus simple, je n'aurais pas fait de _tag_error
une fonction privée; -)
Python 2.7.
vous pouvez également obtenir le résultat après unittest.main ():
t = unittest.main(exit=False)
print t.result
ou utiliser la suite:
suite.addTests(tests)
result = unittest.result.TestResult()
suite.run(result)
print result
nom du test courant peut être récupéré avec unittest.TestCase.id () method. Donc, à tearDown, vous pouvez vérifier vous-même.id ().
exemple montre comment:
- rechercher si les test a l'erreur ou l'échec dans les erreurs ou les défaillances de la liste
- test d'impression id avec la réussite ou l'ÉCHEC ou de l'EXCEPTION
exemple testé ici fonctionne avec le bel exemple de @scoffey.
def tearDown(self):
result = "PASS"
#### find and show result for current test
# I did not find any nicer/neater way of comparing self.id() with test id stored in errors or failures lists :-7
id = str(self.id()).split('.')[-1]
# id() e.g. tup[0]:<__main__.MyTest testMethod=test_onePlusNoneIsNone>
# str(tup[0]):"test_onePlusOneEqualsThree (__main__.MyTest)"
# str(self.id()) = __main__.MyTest.test_onePlusNoneIsNone
for tup in self.currentResult.failures:
if str(tup[0]).startswith(id):
print ' test %s failure:%s' % (self.id(), tup[1])
## DO TEST FAIL ACTION HERE
result = "FAIL"
for tup in self.currentResult.errors:
if str(tup[0]).startswith(id):
print ' test %s error:%s' % (self.id(), tup[1])
## DO TEST EXCEPTION ACTION HERE
result = "EXCEPTION"
print "Test:%s Result:%s" % (self.id(), result)
exemple de résultat:
python run_scripts/tut2.py 2>&1
E test __main__.MyTest.test_onePlusNoneIsNone error:Traceback (most recent call last):
File "run_scripts/tut2.py", line 80, in test_onePlusNoneIsNone
self.assertTrue(1 + None is None) # raises TypeError
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
Test:__main__.MyTest.test_onePlusNoneIsNone Result:EXCEPTION
F test __main__.MyTest.test_onePlusOneEqualsThree failure:Traceback (most recent call last):
File "run_scripts/tut2.py", line 77, in test_onePlusOneEqualsThree
self.assertTrue(1 + 1 == 3) # fails
AssertionError: False is not true
Test:__main__.MyTest.test_onePlusOneEqualsThree Result:FAIL
Test:__main__.MyTest.test_onePlusOneEqualsTwo Result:PASS
.
======================================================================
ERROR: test_onePlusNoneIsNone (__main__.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "run_scripts/tut2.py", line 80, in test_onePlusNoneIsNone
self.assertTrue(1 + None is None) # raises TypeError
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
======================================================================
FAIL: test_onePlusOneEqualsThree (__main__.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "run_scripts/tut2.py", line 77, in test_onePlusOneEqualsThree
self.assertTrue(1 + 1 == 3) # fails
AssertionError: False is not true
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1, errors=1)
inspiré par réponse de scoffey , j'ai décidé de prendre mercilessnes à un niveau supérieur, et ont trouvé ce qui suit.
il fonctionne à la fois en unittest vanilla, et aussi lorsqu'il est exécuté via nosetests, et fonctionne également dans les versions 2.7, 3.2, 3.3, et 3.4 de Python (je n'ai pas testé spécifiquement 3.0, 3.1, ou 3.5, car je ne les ai pas installés pour le moment, mais si je lis le code source correctement, il devrait fonctionner en 3.5 aussi bien):
#! /usr/bin/env python
from __future__ import unicode_literals
import logging
import os
import sys
import unittest
# Log file to see squawks during testing
formatter = logging.Formatter(fmt='%(levelname)-8s %(name)s: %(message)s')
log_file = os.path.splitext(os.path.abspath(__file__))[0] + '.log'
handler = logging.FileHandler(log_file)
handler.setFormatter(formatter)
logging.root.addHandler(handler)
logging.root.setLevel(logging.DEBUG)
log = logging.getLogger(__name__)
PY = tuple(sys.version_info)[:3]
class SmartTestCase(unittest.TestCase):
"""Knows its state (pass/fail/error) by the time its tearDown is called."""
def run(self, result):
# Store the result on the class so tearDown can behave appropriately
self.result = result.result if hasattr(result, 'result') else result
if PY >= (3, 4, 0):
self._feedErrorsToResultEarly = self._feedErrorsToResult
self._feedErrorsToResult = lambda *args, **kwargs: None # no-op
super(SmartTestCase, self).run(result)
@property
def errored(self):
if (3, 0, 0) <= PY < (3, 4, 0):
return bool(self._outcomeForDoCleanups.errors)
return self.id() in [case.id() for case, _ in self.result.errors]
@property
def failed(self):
if (3, 0, 0) <= PY < (3, 4, 0):
return bool(self._outcomeForDoCleanups.failures)
return self.id() in [case.id() for case, _ in self.result.failures]
@property
def passed(self):
return not (self.errored or self.failed)
def tearDown(self):
if PY >= (3, 4, 0):
self._feedErrorsToResultEarly(self.result, self._outcome.errors)
class TestClass(SmartTestCase):
def test_1(self):
self.assertTrue(True)
def test_2(self):
self.assertFalse(True)
def test_3(self):
self.assertFalse(False)
def test_4(self):
self.assertTrue(False)
def test_5(self):
self.assertHerp('Derp')
def tearDown(self):
super(TestClass, self).tearDown()
log.critical('---- RUNNING {} ... -----'.format(self.id()))
if self.errored:
log.critical('----- ERRORED -----')
elif self.failed:
log.critical('----- FAILED -----')
else:
log.critical('----- PASSED -----')
if __name__ == '__main__':
unittest.main()
avec unittest
:
$ ./test.py -v
test_1 (__main__.TestClass) ... ok
test_2 (__main__.TestClass) ... FAIL
test_3 (__main__.TestClass) ... ok
test_4 (__main__.TestClass) ... FAIL
test_5 (__main__.TestClass) ... ERROR
[…]
$ cat ./test.log
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- FAILED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- FAILED -----
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- ERRORED -----
avec nosetests
:
$ nosetests ./test.py -v
test_1 (test.TestClass) ... ok
test_2 (test.TestClass) ... FAIL
test_3 (test.TestClass) ... ok
test_4 (test.TestClass) ... FAIL
test_5 (test.TestClass) ... ERROR
$ cat ./test.log
CRITICAL test: ---- RUNNING test.TestClass.test_1 ... -----
CRITICAL test: ----- PASSED -----
CRITICAL test: ---- RUNNING test.TestClass.test_2 ... -----
CRITICAL test: ----- FAILED -----
CRITICAL test: ---- RUNNING test.TestClass.test_3 ... -----
CRITICAL test: ----- PASSED -----
CRITICAL test: ---- RUNNING test.TestClass.test_4 ... -----
CRITICAL test: ----- FAILED -----
CRITICAL test: ---- RUNNING test.TestClass.test_5 ... -----
CRITICAL test: ----- ERRORED -----
arrière-plan
I a commencé avec ceci:
class SmartTestCase(unittest.TestCase):
"""Knows its state (pass/fail/error) by the time its tearDown is called."""
def run(self, result):
# Store the result on the class so tearDown can behave appropriately
self.result = result.result if hasattr(result, 'result') else result
super(SmartTestCase, self).run(result)
@property
def errored(self):
return self.id() in [case.id() for case, _ in self.result.errors]
@property
def failed(self):
return self.id() in [case.id() for case, _ in self.result.failures]
@property
def passed(self):
return not (self.errored or self.failed)
cependant, cela ne fonctionne qu'en Python 2. Dans Python 3, jusqu'à 3.3 inclus, le flux de contrôle semble avoir changé un peu: Python Le paquet unittest de 3 traite les résultats après appelant la méthode de chaque test tearDown()
... ce comportement peut être confirmé si nous ajoutons simplement une ligne supplémentaire (ou six) à notre classe de test:
@@ -63,6 +63,12 @@
log.critical('----- FAILED -----')
else:
log.critical('----- PASSED -----')
+ log.warning(
+ 'ERRORS THUS FAR:\n'
+ + '\n'.join(tc.id() for tc, _ in self.result.errors))
+ log.warning(
+ 'FAILURES THUS FAR:\n'
+ + '\n'.join(tc.id() for tc, _ in self.result.failures))
if __name__ == '__main__':
puis il suffit de relancer les tests:
$ python3.3 ./test.py -v
test_1 (__main__.TestClass) ... ok
test_2 (__main__.TestClass) ... FAIL
test_3 (__main__.TestClass) ... ok
test_4 (__main__.TestClass) ... FAIL
test_5 (__main__.TestClass) ... ERROR
[…]
...et vous verrez que vous obtenez ce résultat:
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING __main__: ERRORS THUS FAR:
WARNING __main__: FAILURES THUS FAR:
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING __main__: ERRORS THUS FAR:
WARNING __main__: FAILURES THUS FAR:
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING __main__: ERRORS THUS FAR:
WARNING __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING __main__: ERRORS THUS FAR:
WARNING __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING __main__: ERRORS THUS FAR:
WARNING __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4
maintenant, comparez ce qui précède à la sortie de Python 2:
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_1 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING __main__: ERRORS THUS FAR:
WARNING __main__: FAILURES THUS FAR:
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_2 ... -----
CRITICAL __main__: ----- FAILED -----
WARNING __main__: ERRORS THUS FAR:
WARNING __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_3 ... -----
CRITICAL __main__: ----- PASSED -----
WARNING __main__: ERRORS THUS FAR:
WARNING __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_4 ... -----
CRITICAL __main__: ----- FAILED -----
WARNING __main__: ERRORS THUS FAR:
WARNING __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4
CRITICAL __main__: ---- RUNNING __main__.TestClass.test_5 ... -----
CRITICAL __main__: ----- ERRORED -----
WARNING __main__: ERRORS THUS FAR:
__main__.TestClass.test_5
WARNING __main__: FAILURES THUS FAR:
__main__.TestClass.test_2
__main__.TestClass.test_4
puisque Python 3 traite les erreurs /Échecs après le test est démoli, nous ne pouvons pas facilement inférer le résultat d'un test en utilisant result.errors
ou result.failures
dans chaque cas. (Je pense qu'il est probablement plus sensé sur le plan de l'architecture de traiter les résultats d'un test après le démonter, cependant, il ne faire le cas d'utilisation parfaitement valide de suivre une procédure de fin de test différente en fonction de l'état de réussite/échec d'un test un peu plus difficile à satisfaire...)
donc, au lieu de compter sur l'ensemble result
objet, nous pouvons faire référence à _outcomeForDoCleanups
comme autres ont déjà mentionné, qui contient l'objet de résultat pour le test en cours d'exécution, et a les nécessaires errors
et failrues
attributs, que nous pouvons utiliser pour inférer le statut d'un test par le temps tearDown()
a a été appelé:
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import logging
import os
+import sys
import unittest
@@ -16,6 +17,9 @@
log = logging.getLogger(__name__)
+PY = tuple(sys.version_info)[:3]
+
+
class SmartTestCase(unittest.TestCase):
"""Knows its state (pass/fail/error) by the time its tearDown is called."""
@@ -27,10 +31,14 @@
@property
def errored(self):
+ if PY >= (3, 0, 0):
+ return bool(self._outcomeForDoCleanups.errors)
return self.id() in [case.id() for case, _ in self.result.errors]
@property
def failed(self):
+ if PY >= (3, 0, 0):
+ return bool(self._outcomeForDoCleanups.failures)
return self.id() in [case.id() for case, _ in self.result.failures]
@property
ceci ajoute le support pour les premières versions de Python 3.
à partir de Python 3.4, cependant, cette variable membre privé n'existe plus , et à la place, une nouvelle méthode (bien que également privé) a été ajoutée: _feedErrorsToResult
.
cela signifie que pour les versions 3.4 ( et suivantes ), si le besoin est grand assez, on peut - très farfelu - forcez un moyen de faire en sorte que tout fonctionne à nouveau comme il l'a fait dans la version 2...
@@ -27,17 +27,20 @@
def run(self, result):
# Store the result on the class so tearDown can behave appropriately
self.result = result.result if hasattr(result, 'result') else result
+ if PY >= (3, 4, 0):
+ self._feedErrorsToResultEarly = self._feedErrorsToResult
+ self._feedErrorsToResult = lambda *args, **kwargs: None # no-op
super(SmartTestCase, self).run(result)
@property
def errored(self):
- if PY >= (3, 0, 0):
+ if (3, 0, 0) <= PY < (3, 4, 0):
return bool(self._outcomeForDoCleanups.errors)
return self.id() in [case.id() for case, _ in self.result.errors]
@property
def failed(self):
- if PY >= (3, 0, 0):
+ if (3, 0, 0) <= PY < (3, 4, 0):
return bool(self._outcomeForDoCleanups.failures)
return self.id() in [case.id() for case, _ in self.result.failures]
@@ -45,6 +48,10 @@
def passed(self):
return not (self.errored or self.failed)
+ def tearDown(self):
+ if PY >= (3, 4, 0):
+ self._feedErrorsToResultEarly(self.result, self._outcome.errors)
+
class TestClass(SmartTestCase):
@@ -64,6 +71,7 @@
self.assertHerp('Derp')
def tearDown(self):
+ super(TestClass, self).tearDown()
log.critical('---- RUNNING {} ... -----'.format(self.id()))
if self.errored:
log.critical('----- ERRORED -----')
... à condition, bien entendu, tous les consommateurs de cette classe se souviennent de super(…, self).tearDown()
dans leurs tearDown
méthodes ...
avertissement: purement éducatif, ne tentez pas cela à la maison, etc. etc. etc. Je ne suis pas particulièrement fier de cette solution, mais elle semble fonctionner assez bien pour le moment, et c'est la meilleure que j'ai pu pirater après avoir joué du violon pendant une heure ou deux un samedi après-midi...