¿Cómo se escribe una prueba unitaria que falla solo si una función no arroja una excepción esperada?
¿Cómo se escribe una prueba unitaria que falla solo si una función no arroja una excepción esperada?
Respuestas:
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)
myfunc
debe agregarlos como argumentos a la llamada afirmarRaises. Ver la respuesta de Daryl Spitzer.
self.assertRaises(TypeError, mymod.myfunc)
. Puede encontrar una lista completa de las Excepciones incorporadas aquí: docs.python.org/3/library/exceptions.html#bltin-exceptions
self.assertRaises(SomeCoolException, Constructor, arg1)
Desde Python 2.7 puede usar el administrador de contexto para obtener el objeto de excepción 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 , hay que envolver context.exception
en str
, de lo contrario obtendrá unaTypeError
self.assertTrue('This is broken' in str(context.exception))
context.exception
no da el mensaje; Es un tipo.
import unittest2
, necesita usar str()
, es decir self.assertTrue('This is broken' in str(context.exception))
.
El código en mi respuesta anterior se puede simplificar a:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
Y si una función toma argumentos, simplemente páselos en afirmarRaises como este:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction, arg1, arg2)
2.7.15
. Si afunction
in self.assertRaises(ExpectedException, afunction, arg1, arg2)
es el inicializador de clase, debe pasar self
como primer argumento, por ejemplo, self.assertRaises(ExpectedException, Class, self, arg1, arg2)
¿Cómo se prueba que una función de Python arroja una excepción?
¿Cómo se escribe una prueba que falla solo si una función no arroja una excepción esperada?
Use el self.assertRaises
método como administrador de contexto:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
El enfoque de mejores prácticas es bastante fácil de demostrar en un shell de Python.
La unittest
biblioteca
En Python 2.7 o 3:
import unittest
En Python 2.6, puede instalar un backport de la unittest
biblioteca de 2.7 , llamado unittest2 , y solo alias como unittest
:
import unittest2 as unittest
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 primera prueba se usa assertRaises
como administrador de contexto, lo que garantiza que el error se detecte y limpie 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, legible y fácil de usar que usar el administrador de contexto.
Para ejecutar las pruebas:
unittest.main(exit=False)
En Python 2.6, probablemente necesitará lo siguiente :
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Y su terminal debería generar 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 ay 1
un '1'
resultado en a TypeError
.
Para una salida más detallada, intente esto:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Su código debe seguir este patrón (esta es una prueba de estilo de módulo de prueba de unidad):
def test_afunction_throws_exception(self):
try:
afunction()
except ExpectedException:
pass
except Exception:
self.fail('unexpected exception raised')
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 produjo una excepción.
assertRaises
, obtendrá un ERROR en lugar de una FALLA.
de: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/
Primero, aquí está la función correspondiente (todavía 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 al intentar 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)
TypeError se activa y se genera una falla 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))
El resultado final:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Perfecto !
... y para mi tambien es perfecto !!
Thansk mucho Sr. Julien Lengrand-Lambert
Esta afirmación de prueba en realidad devuelve un falso positivo . Eso sucede porque la lambda dentro de 'afirmarRaises' es la unidad que genera el error de tipo y no la función probada.
self.assertRaises(TypeError, df.square_value(self.false_int))
llama al método y devuelve el resultado. Lo que quiere es pasar el método y cualquier argumento y dejar que la unidad lo llame:self.assertRaises(TypeError, df.square_value, self.false_int)
Puede crear el suyo propio contextmanager
para verificar si se produjo 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
así:
with raises(Exception):
print "Hola" # Calls assert False
with raises(Exception):
raise Exception # Calls assert True
Si está utilizando 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
unittest
módulo!
Uso 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 - Pruebe ejemplos interactivos de Python
Acabo de descubrir que la biblioteca Mock proporciona un método ClaimRaisesWithMessage () (en su subclase unittest.TestCase), que comprobará no solo que se genera la excepción esperada, sino también que se genera con el mensaje esperado:
from testcase import TestCase
import mymod
class MyTestCase(TestCase):
def test1(self):
self.assertRaisesWithMessage(SomeCoolException,
'expected message',
mymod.myfunc)
Hay muchas respuestas aquí. El código muestra cómo podemos crear una Excepción, cómo podemos usar esa excepción en nuestros métodos y, por último, cómo puede verificar en una prueba unitaria, las excepciones correctas que se generan.
import unittest
class DeviceException(Exception):
def __init__(self, msg, code):
self.msg = msg
self.code = code
def __str__(self):
return repr("Error {}: {}".format(self.code, self.msg))
class MyDevice(object):
def __init__(self):
self.name = 'DefaultName'
def setParameter(self, param, value):
if isinstance(value, str):
setattr(self, param , value)
else:
raise DeviceException('Incorrect type of argument passed. Name expects a string', 100001)
def getParameter(self, param):
return getattr(self, param)
class TestMyDevice(unittest.TestCase):
def setUp(self):
self.dev1 = MyDevice()
def tearDown(self):
del self.dev1
def test_name(self):
""" Test for valid input for name parameter """
self.dev1.setParameter('name', 'MyDevice')
name = self.dev1.getParameter('name')
self.assertEqual(name, 'MyDevice')
def test_invalid_name(self):
""" Test to check if error is raised if invalid type of input is provided """
self.assertRaises(DeviceException, self.dev1.setParameter, 'name', 1234)
def test_exception_message(self):
""" Test to check if correct exception message and code is raised when incorrect value is passed """
with self.assertRaises(DeviceException) as cm:
self.dev1.setParameter('name', 1234)
self.assertEqual(cm.exception.msg, 'Incorrect type of argument passed. Name expects a string', 'mismatch in expected error message')
self.assertEqual(cm.exception.code, 100001, 'mismatch in expected error code')
if __name__ == '__main__':
unittest.main()
Puede usar afirmarRaises 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)
Si bien todas las respuestas están perfectamente bien, estaba buscando una manera de probar si una función generaba una excepción sin depender de los marcos de prueba de la unidad y tener que escribir clases de prueba.
Terminé escribiendo lo siguiente:
def assert_error(e, x):
try:
e(x)
except:
return
raise AssertionError()
def failing_function(x):
raise ValueError()
def dummy_function(x):
return x
if __name__=="__main__":
assert_error(failing_function, 0)
assert_error(dummy_function, 0)
Y falla en la línea correcta:
Traceback (most recent call last):
File "assert_error.py", line 16, in <module>
assert_error(dummy_function, 0)
File "assert_error.py", line 6, in assert_error
raise AssertionError()
AssertionError