Usando un administrador de contexto con Python assertRaises

La documentación de Python para unittest implica que el método assertRaises() se puede utilizar como administrador de contexto. El código a continuación muestra un ejemplo simple de la prueba de unidad de los documentos de Python. La llamada testsample() método testsample() funciona bien.

Ahora me gustaría acceder a la excepción cuando se genera, pero si la comento y, en su lugar, elimino el comentario del siguiente bloque en el que bash utilizar un administrador de contexto, obtengo un AttributeError: __exit__ cuando bash ejecutar el código. Esto sucede tanto para Python 2.7.2 como para 3.2.2. Podría detectar la excepción en un try...except bloquearlo y acceder a él de esa manera, pero la documentación de unittest parece indicar que el administrador de contexto también haría esto.

¿Hay algo más que estoy haciendo mal aquí?

 class TestSequenceFunctions(unittest.TestCase): def setUp(self): self.seq = [x for x in range(10)] def testshuffle(self): # make sure the shuffled sequence does not lose any elements random.shuffle(self.seq) self.seq.sort() self.assertEqual(self.seq, [x for x in range(10)]) def testchoice(self): element = random.choice(self.seq) self.assert_(element in self.seq) def testsample(self): self.assertRaises(ValueError, random.sample, self.seq, 20) # with self.assertRaises(ValueError, random.sample, self.seq, 20): # print("Inside cm") for element in random.sample(self.seq, 5): self.assert_(element in self.seq) if __name__ == '__main__': unittest.main() 

El código fuente de unittest no muestra un gancho de excepción para assertRaises :

 class _AssertRaisesContext(object): """A context manager used to implement TestCase.assertRaises* methods.""" def __init__(self, expected, test_case, expected_regexp=None): self.expected = expected self.failureException = test_case.failureException self.expected_regexp = expected_regexp def __enter__(self): return self def __exit__(self, exc_type, exc_value, tb): if exc_type is None: try: exc_name = self.expected.__name__ except AttributeError: exc_name = str(self.expected) raise self.failureException( "{0} not raised".format(exc_name)) if not issubclass(exc_type, self.expected): # let unexpected exceptions pass through return False self.exception = exc_value # store for later retrieval if self.expected_regexp is None: return True expected_regexp = self.expected_regexp if isinstance(expected_regexp, basestring): expected_regexp = re.compile(expected_regexp) if not expected_regexp.search(str(exc_value)): raise self.failureException('"%s" does not match "%s"' % (expected_regexp.pattern, str(exc_value))) return True 

Así que, como sospechaba, formar su propio bloque try / except es el camino a seguir si desea interceptar la excepción mientras sigue manteniendo la prueba assertRaises:

 def testsample(self): with self.assertRaises(ValueError): try: random.sample(self.seq, 20) except ValueError as e: # do some action with e self.assertEqual(e.args, ('sample larger than population',)) # now let the context manager do its work raise 

Parece que nadie ha sugerido aún:

 import unittest # For python < 2.7, do import unittest2 as unittest class Class(object): def should_raise(self): raise ValueError('expected arg') class test_Class(unittest.TestCase): def test_something(self): DUT = Class() with self.assertRaises(ValueError) as exception_context_manager: DUT.should_raise() exception = exception_context_manager.exception self.assertEqual(exception.args, ('expected arg', )) 

Usualmente uso e_cm como la abreviatura de exception_context_manager.

Según la documentación:

Si se llama con callableObj omitido o Ninguno, devolverá un objeto de contexto

Entonces ese código debería ser:

 with self.assertRaises(ValueError): random.sample(self.seq, 20) 

Teniendo esto en cuenta, hace seis años, me imagino que esto es algo que funciona ahora pero no funcionó en ese momento. Los documentos indican que esto apareció en 2.7 pero no en qué versión micro.

 import unittest class TestIntParser(unittest.TestCase): def test_failure(self): failure_message = 'invalid literal for int() with base 10' with self.assertRaises(ValueError) as cm: int('forty two') self.assertIn(failure_message, cm.exception.message) if __name__ == '__main__': unittest.main()