utilizar - ¿Cómo prueba que una función de Python lanza una excepción?
validar division por 0 en python (11)
¿Cómo se escribe una prueba de unidad que falla solo si una función no lanza una excepción esperada?
¿Cómo prueba que una función de Python lanza una excepción?
¿Cómo se escribe una prueba que falla solo si una función no lanza una excepción esperada?
Respuesta corta:
Utilice el método self.assertRaises
como administrador de contexto:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + ''1''
Demostración
El enfoque de las mejores prácticas es bastante fácil de demostrar en un shell de Python.
La biblioteca de unittest
En Python 2.7 o 3:
import unittest
En Python 2.6, puedes instalar un backport de la biblioteca de unittest
la unittest
2.7, llamada unittest2 , y solo un alias que como unittest
:
import unittest2 as unittest
Pruebas de ejemplo
Ahora, pegue en su shell de Python la siguiente prueba de seguridad de tipos de Python:
class MyTestCase(unittest.TestCase):
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + ''1''
def test_2_cannot_add_int_and_str(self):
import operator
self.assertRaises(TypeError, operator.add, 1, ''1'')
La prueba uno utiliza assertRaises
como administrador de contexto, lo que garantiza que el error se assertRaises
y assertRaises
correctamente mientras se registra.
También podríamos escribirlo sin el administrador de contexto, ver prueba dos. El primer argumento sería el tipo de error que espera generar, el segundo argumento, la función que está probando, y los argumentos restantes y los argumentos de palabras clave se pasarán a esa función.
Creo que es mucho más simple, fácil de leer y fácil de mantener que usar el administrador de contexto.
Corriendo las pruebas
Para ejecutar las pruebas:
unittest.main(exit=False)
En Python 2.6, probablemente necesitarás lo siguiente :
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Y su terminal debe dar salida a lo siguiente:
..
----------------------------------------------------------------------
Ran 2 tests in 0.007s
OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>
Y vemos que, como esperamos, al intentar agregar un 1
y un resultado de ''1''
en un TypeError
.
Para una salida más detallada, intente esto:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Acabo de descubrir que la biblioteca de Mock proporciona un método assertRaisesWithMessage () (en su subclase unittest.TestCase), que verificará no solo que se genere la excepción esperada, sino también que se genere con el mensaje esperado:
from testcase import TestCase
import mymod
class MyTestCase(TestCase):
def test1(self):
self.assertRaisesWithMessage(SomeCoolException,
''expected message'',
mymod.myfunc)
Desde Python 2.7 puede usar el administrador de contexto para obtener un objeto del objeto Exception real lanzado:
import unittest
def broken_function():
raise Exception(''This is broken'')
class MyTestCase(unittest.TestCase):
def test(self):
with self.assertRaises(Exception) as context:
broken_function()
self.assertTrue(''This is broken'' in context.exception)
if __name__ == ''__main__'':
unittest.main()
http://docs.python.org/dev/library/unittest.html#unittest.TestCase.assertRaises
En Python 3.5 , tienes que envolver context.exception
en str
, de lo contrario obtendrás un TypeError
self.assertTrue(''This is broken'' in str(context.exception))
Eche un vistazo al método assertRaises del módulo unittest
.
El código en mi respuesta anterior se puede simplificar para:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
Y si una función toma argumentos, simplemente transfiéralos a assertRaises de esta manera:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction, arg1, arg2)
Puede crear su propio contextmanager
de contextmanager
para verificar si se generó la excepción.
import contextlib
@contextlib.contextmanager
def raises(exception):
try:
yield
except exception as e:
assert True
else:
assert False
Y luego puedes usar raises
como esta:
with raises(Exception):
print "Hola" # Calls assert False
with raises(Exception):
raise Exception # Calls assert True
Si estás usando pytest
, esto ya está implementado. Puedes hacer pytest.raises(Exception)
:
Ejemplo:
def test_div_zero():
with pytest.raises(ZeroDivisionError):
1/0
Y el resultado:
pigueiras@pigueiras$ py.test
================= test session starts =================
platform linux2 -- Python 2.6.6 -- py-1.4.20 -- pytest-2.5.2 -- /usr/bin/python
collected 1 items
tests/test_div_zero.py:6: test_div_zero PASSED
Puedes usar assertRaises desde el módulo unittest
import unittest
class TestClass():
def raises_exception(self):
raise Exception("test")
class MyTestCase(unittest.TestCase):
def test_if_method_raises_correct_exception(self):
test_class = TestClass()
# note that you dont use () when passing the method to assertRaises
self.assertRaises(Exception, test_class.raises_exception)
Su código debe seguir este patrón (esta es una prueba de estilo del módulo unittest):
def test_afunction_throws_exception(self):
try:
afunction()
except ExpectedException:
pass
except Exception as e:
self.fail(''Unexpected exception raised:'', e)
else:
self.fail(''ExpectedException not raised'')
En Python <2.7, esta construcción es útil para verificar valores específicos en la excepción esperada. La función unittest assertRaises
solo verifica si se generó una excepción.
Utilizo doctest [1] en casi todas partes porque me gusta el hecho de documentar y probar mis funciones al mismo tiempo.
Echa un vistazo a este código:
def throw_up(something, gowrong=False):
"""
>>> throw_up(''Fish n Chips'')
Traceback (most recent call last):
...
Exception: Fish n Chips
>>> throw_up(''Fish n Chips'', gowrong=True)
''I feel fine!''
"""
if gowrong:
return "I feel fine!"
raise Exception(something)
if __name__ == ''__main__'':
import doctest
doctest.testmod()
Si coloca este ejemplo en un módulo y lo ejecuta desde la línea de comandos, ambos casos de prueba se evalúan y verifican.
[1] Documentación de Python: 23.2 doctest - Probar ejemplos interactivos de Python
de: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/
Primero, aquí está la función correspondiente (aún dum: p) en el archivo dum_function.py:
def square_value(a):
"""
Returns the square value of a.
"""
try:
out = a*a
except TypeError:
raise TypeError("Input should be a string:")
return out
Aquí está la prueba a realizar (solo se inserta esta prueba):
import dum_function as df # import function module
import unittest
class Test(unittest.TestCase):
"""
The class inherits from unittest
"""
def setUp(self):
"""
This method is called before each test
"""
self.false_int = "A"
def tearDown(self):
"""
This method is called after each test
"""
pass
#---
## TESTS
def test_square_value(self):
# assertRaises(excClass, callableObj) prototype
self.assertRaises(TypeError, df.square_value(self.false_int))
if __name__ == "__main__":
unittest.main()
¡Ahora estamos listos para probar nuestra función! Esto es lo que sucede cuando se intenta ejecutar la prueba:
======================================================================
ERROR: test_square_value (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_dum_function.py", line 22, in test_square_value
self.assertRaises(TypeError, df.square_value(self.false_int))
File "/home/jlengrand/Desktop/function.py", line 8, in square_value
raise TypeError("Input should be a string:")
TypeError: Input should be a string:
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
El TypeError se activa y genera un error de prueba. El problema es que este es exactamente el comportamiento que queríamos: s.
Para evitar este error, simplemente ejecute la función usando lambda en la llamada de prueba:
self.assertRaises(TypeError, lambda: df.square_value(self.false_int))
La salida final:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Perfecto !
... y para mi tambien es perfecto !!
Muchas gracias Sr. Julien Lengrand-Lambert
Utilice TestCase.assertRaises
(o TestCase.failUnlessRaises
) del módulo unittest, por ejemplo:
import mymod
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc)