unittest unitarias tests pruebas not integracion examples equal assert_equal python unit-testing nose

unitarias - testing en python



Obteniendo los resultados de prueba unitarios de Python en un método tearDown() (10)

¿Es posible obtener los resultados de una prueba (es decir, si todas las aserciones han pasado) en un método tearDown ()? Estoy ejecutando scripts de Selenium, y me gustaría hacer algunos informes desde dentro de tearDown (), sin embargo, no sé si esto es posible.


Aquí hay una solución para aquellos de nosotros que nos sentimos incómodos al usar soluciones que dependen de las unittest internas de la unidad:

Primero, creamos un decorador que establecerá un indicador en la instancia de TestCase para determinar si el caso de prueba falló o pasó:

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

Este decorador es bastante simple. Se basa en el hecho de que unittest detecta las pruebas fallidas a través de Excepciones . Hasta donde yo sé, la única excepción especial que necesita ser manejada es unittest.SkipTest (que no indica una falla de prueba). Todas las demás excepciones indican fallas de prueba, por lo que las marcamos como tales cuando aparecen en burbujas para nosotros.

Ahora podemos usar este decorador directamente:

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'')

Va a ser realmente molesto escribir este decorador todo el tiempo. ¿Hay alguna manera de simplificar? ¡Sí hay! * Podemos escribir una metaclase para manejar la aplicación del decorador para nosotros:

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)

Ahora aplicamos esto a nuestra subclase base TestCase y estamos listos:

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'')

Es probable que haya una cantidad de casos que esto no maneja adecuadamente. Por ejemplo, no detecta correctamente las subtests fallidas o las fallas esperadas. Me interesarían otros modos de falla de esto, así que si encuentras un caso que no estoy manejando correctamente, házmelo saber en los comentarios y lo investigaré.

* Si no hubiera una manera más fácil, no habría hecho _tag_error una función privada ;-)


CAVEAT: No tengo forma de verificar la siguiente teoría en este momento, alejándome de una caja dev. Así que esto puede ser un tiro en la oscuridad.

Quizás pueda verificar el valor de retorno de sys.exc_info() dentro de su método tearDown (), si devuelve (None, None, None) , sabe que el caso de prueba fue exitoso. De lo contrario, podría usar la tupla devuelta para interrogar al objeto de excepción.

Consulte la documentación de sys.exc_info .

Otro enfoque más explícito es escribir un decorador de métodos que pueda aplicar a todos sus métodos de casos de prueba que requieren este manejo especial. Este decorador puede interceptar excepciones de afirmación y en función de eso modificar algún estado en self permite que su método tearDown aprenda qué sucede.

@assertion_tracker def test_foo(self): # some test logic


Depende del tipo de informe que le gustaría producir.

En caso de que desee realizar algunas acciones en caso de error (como la generación de capturas de pantalla ), en lugar de utilizar tearDown() , puede lograrlo anulando failureException .

Por ejemplo:

@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


El nombre de la prueba actual se puede recuperar con el método unittest.TestCase.id() . Entonces en tearDown puedes verificar self.id ().

El ejemplo muestra cómo:

  • encontrar si la prueba actual tiene error o falla en la lista de errores o fallas
  • imprimir identificación de prueba con PASS o FAIL o EXCEPTION

El ejemplo probado aquí funciona con el buen ejemplo 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)

ejemplo de resultado:

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)


Esta solución es para las versiones de Python 2.7 a 3.6 (la versión actual más alta y el desarrollo poco antes de 3.7-alpha ), sin decoradores u otras modificaciones en ningún código antes de tearDown . Todo funciona según la clasificación de resultados incorporada. También las pruebas omitidas o las fallas expectedFailure se reconocen correctamente. Evalúa el resultado de la prueba actual, no un resumen de todas las pruebas aprobadas hasta el momento. Compatible también con 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)

Comentarios: solo se deben informar una o cero excepciones (error o falla) porque no se puede esperar más antes de tearDown . El paquete unittest espera que tearDown pueda generar una segunda excepción. Por lo tanto, los errors y failures las listas pueden contener solo uno o cero elementos juntos antes de tearDown. Las líneas después del comentario "demo" informan un resultado breve.

Salida de demostración: (no importante)

$ 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)

Comparación con otras soluciones (con respecto al historial de commit del repositorio fuente de Python):

  • Esta solución usa un atributo privado de la instancia de TestCase como muchas otras soluciones, pero revisé cuidadosamente todas las confirmaciones relevantes en el repositorio de fuentes de Python de que tres nombres alternativos cubren el historial de códigos desde Python 2.7 a 3.6.2 sin ningún espacio. Puede ser un problema después de una nueva versión importante de Python, pero podría ser claramente reconocida, omitida y fácil de arreglar más adelante para una nueva versión de Python. Una ventaja es que no se modifica nada antes de ejecutar tearDown, nunca debe romperse la prueba y se admite toda la funcionalidad de unittest, funciona con pytest, podría funcionar con muchos paquetes extendidos, pero no con nosetest (no es una sorpresa porque nosetest no es compatible, por ejemplo, unittest.expectedFailure).

  • Las soluciones con decoradores en los métodos de prueba del usuario o con una mgilson personalizada ( mgilson , Pavel Repin 2nd way, kenorb) son robustas frente a futuras versiones de Python, pero si todo funcionara por completo, crecerían como una bola de nieve con excepciones más compatibles y más partes internas replicadas de unittest. Las funciones decoradas tienen trazas menos legibles (incluso más niveles agregados por un decorador), son más complicadas para la depuración y es desagradable si otro decorador más importante tiene un problema. (Gracias a mgilson, la funcionalidad básica está lista y los problemas conocidos pueden solucionarse).

  • La solución con método de run modificada y parámetro de result atrapado

    • ( scoffey ) debería funcionar también para Python 2.6. La interpretación de los resultados se puede mejorar a los requisitos de la pregunta, pero nada puede funcionar en Python 3.4+, porque el result se actualiza después de la llamada de tearDown, nunca antes.
    • Mark G .: (probado con Python 2.7, 3.2, 3.3, 3.4 y con nosetest)
  • La solución de exc_info() (Pavel Repin 2º modo) funciona solo con Python 2.

  • Otras soluciones son principalmente similares, pero menos completas o con más desventajas.

Explicado por el repositorio fuente de 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)

Nota (leyendo los mensajes de confirmación): una razón por la cual los resultados de las pruebas están tan desacoplados de las pruebas es la prevención de fugas de memoria . Toda información de excepción puede acceder a los marcos del estado de proceso fallido, incluidas todas las variables locales. Si se asigna un cuadro a una variable local en un bloque de código que también podría fallar, se podría crear fácilmente una referencia de memoria cruzada. No es terrible, gracias al recolector de basura, pero la memoria libre puede fragmentarse más rápidamente que si la memoria se liberara correctamente. Esta es una razón por la que la información de excepción y el rastreo se convierten muy pronto en cadenas y por qué los objetos temporales como self._outcome están encapsulados y se configuran en Ninguno en un bloque finally para evitar fugas de memoria.


Inspirado por la respuesta de scoffey , decidí llevar a Mercilessnes al siguiente nivel, y he descubierto lo siguiente.

Funciona tanto en vainilla unittest, y también cuando se ejecuta a través de nosetests, y también funciona en las versiones de Python 2.7, 3.2, 3.3 y 3.4 (no probé específicamente 3.0, 3.1 o 3.5, ya que no tengo estos instalados en el momento, pero si leo correctamente el código fuente , debería funcionar en 3.5 también):

#! /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()

Cuando se ejecuta con 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 -----

Cuando se ejecuta con 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 -----

Fondo

Empecé con esto:

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)

Sin embargo, esto solo funciona en Python 2. En Python 3, hasta e incluyendo 3.3, el flujo de control parece haber cambiado un poco: el paquete de test de Python 3 procesa los resultados después de llamar al método tearDown() cada prueba ... este comportamiento puede confirmarse si simplemente agregamos una línea adicional (o seis) a nuestra clase de prueba:

@@ -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__'':

Luego simplemente vuelva a ejecutar las pruebas:

$ 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 […]

... y verá que obtiene esto como resultado:

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

Ahora, compare lo anterior con la salida 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

Dado que Python 3 procesa errores / fallas después de que la prueba se derriba, no podemos deducir fácilmente el resultado de una prueba usando result.errors o result.failures en todos los casos. (Creo que probablemente tenga más sentido arquitectónico procesar los resultados de una prueba después de demolerlo, sin embargo, hace que el caso de uso perfectamente válido de seguir un procedimiento de fin de prueba diferente en función del estado de aprobación / falla de una prueba sea un poco más difícil de encontrar ...)

Por lo tanto, en lugar de confiar en el objeto de result general, podemos hacer referencia a _outcomeForDoCleanups como already se already mencionado, que contiene el objeto de resultado para la prueba en ejecución y tiene los atributos necesarios de errors y failrues , que podemos usar para inferir una prueba estado en el momento en que se tearDown() :

@@ -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

Esto agrega soporte para las primeras versiones de Python 3.

A partir de Python 3.4, sin embargo, esta variable miembro privada ya no existe , y en su lugar, se agregó un nuevo método (aunque también privado): _feedErrorsToResult .

Esto significa que para las versiones 3.4 ( y posteriores ), si la necesidad es lo suficientemente grande, uno puede, de manera muy hackosa , abrirse camino para hacer que todo vuelva a funcionar como lo hizo en la versión 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 -----'')

... siempre que, por supuesto, todos los consumidores de esta clase recuerden super(…, self).tearDown() en sus respectivos métodos tearDown ...

Descargo de responsabilidad: puramente educativo, no intente esto en casa, etc. etc. etc. No estoy particularmente orgulloso de esta solución, pero parece funcionar bastante bien por el momento, y es lo mejor que pude hackear después jugueteando durante una hora o dos un sábado por la tarde ...


Python 2.7.

También puede obtener el resultado después de unittest.main ():

t = unittest.main(exit=False) print t.result

o use suite:

suite.addTests(tests) result = unittest.result.TestResult() suite.run(result) print result


Si echa un vistazo a la implementación de unittest.TestCase.run , puede ver que todos los resultados de la prueba se recopilan en el objeto de resultado (por lo general, una instancia de unittest.TestResult ) pasada como argumento. No se deja ningún estado de resultado en el objeto unittest.TestCase .

Por lo tanto, no hay mucho que pueda hacer en el método unittest.TestCase.tearDown menos que rompa sin piedad el desacoplamiento elegante de los casos de prueba y los resultados de las pruebas con algo como esto:

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()

EDITAR: Esto funciona para Python 2.6 - 3.3, (modificado para el nuevo Python bellow ).


Si está utilizando Python2, puede usar el método _resultForDoCleanups . Este método devuelve un objeto TextTestResult :

<unittest.runner.TextTestResult run=1 errors=0 failures=0>

Puede usar este objeto para verificar el resultado de sus pruebas:

def tearDown(self): if self._resultForDoCleanups.failures: ... elif self._resultForDoCleanups.errors: ... else: #Success

Si está utilizando Python3, puede usar _outcomeForDoCleanups :

def tearDown(self): if not self._outcomeForDoCleanups.success: ...


Siguiendo con la respuesta de amatellanes, si estás en Python3.4, no puedes usar _outcomeForDoCleanups . Esto es lo que logré hackear juntos:

def _test_has_failed(self): for method, error in self._outcome.errors: if error: return True return False

asqueroso, pero parece funcionar.