¿Cómo se prueba que una función de Python arroja una excepción?

Respuestas:

679

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)
Moe
fuente
99
¿Hay alguna manera de hacer lo contrario de esto? ¿Como si fallara solo si la función arroja la excepción?
BUInvent
67
Tenga en cuenta que para pasar argumentos myfuncdebe agregarlos como argumentos a la llamada afirmarRaises. Ver la respuesta de Daryl Spitzer.
cbron
1
¿Hay alguna manera de permitir múltiples tipos de excepción?
Dinesh
1
Puede usar las excepciones incorporadas de Python para probar rápidamente la afirmación; @ usando la respuesta de Moe anteriormente, por ejemplo: self.assertRaises(TypeError, mymod.myfunc). Puede encontrar una lista completa de las Excepciones incorporadas aquí: docs.python.org/3/library/exceptions.html#bltin-exceptions
Raymond Wachaga
3
Lo mismo para probar constructores de clase:self.assertRaises(SomeCoolException, Constructor, arg1)
tschumann
477

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.exceptionen str, de lo contrario obtendrá unaTypeError

self.assertTrue('This is broken' in str(context.exception))
Arte
fuente
66
Estoy usando Python 2.7.10, y lo anterior no funciona; context.exceptionno da el mensaje; Es un tipo.
LateCoder
66
También en Python 2.7 (al menos en mi 2.7.6) usando import unittest2, necesita usar str(), es decir self.assertTrue('This is broken' in str(context.exception)).
Sohail Si
44
Dos cosas: 1. Puede usar afirmar en lugar de afirmar verdadero. Por ejemplo, self.assertIn ('Esto está roto', context.exception) 2. En mi caso, usando 2.7.10, context.exception parece ser una matriz de caracteres. Usar str no funciona. Terminé haciendo esto: '' .join (context.exception) Entonces, juntos: self.assertIn ('Esto está roto', '' .join (context.exception))
blockcipher
1
¿Es normal que su método obstruya la consola de prueba con el Traceback de la excepción? ¿Cómo evito que eso suceda?
MadPhysicist
1
más tarde encontré otra forma de obtener el mensaje como str de la excepción, es err = context.exception.message. Y luego puede usar también use self.assertEqual (err, 'Esto está roto') para hacer la prueba.
zhihong
326

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)
Daryl Spitzer
fuente
17
El segundo recorte sobre qué hacer cuando se pasa la discusión fue realmente útil.
Sabyasachi
Estoy usando 2.7.15. Si afunctionin self.assertRaises(ExpectedException, afunction, arg1, arg2)es el inicializador de clase, debe pasar selfcomo primer argumento, por ejemplo, self.assertRaises(ExpectedException, Class, self, arg1, arg2)
Minh Tran
128

¿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?

Respuesta corta:

Use el self.assertRaisesmétodo como administrador de contexto:

    def test_1_cannot_add_int_and_str(self):
        with self.assertRaises(TypeError):
            1 + '1'

Demostración

El enfoque de mejores prácticas es bastante fácil de demostrar en un shell de Python.

La unittestbiblioteca

En Python 2.7 o 3:

import unittest

En Python 2.6, puede instalar un backport de la unittestbiblioteca de 2.7 , llamado unittest2 , y solo alias 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 primera prueba se usa assertRaisescomo 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.

Ejecutando las pruebas

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 1un '1'resultado en a TypeError.


Para una salida más detallada, intente esto:

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Aaron Hall
fuente
56

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 assertRaisessolo verifica si se produjo una excepción.

Daryl Spitzer
fuente
3
y el método self.fail sólo toma un argumento
mdob
3
Esto parece demasiado complicado para probar si una función arroja una excepción. Dado que cualquier excepción que no sea esa excepción hará que la prueba falle y no arrojar una excepción fallará la prueba, parece que la única diferencia es que si obtiene una excepción diferente assertRaises, obtendrá un ERROR en lugar de una FALLA.
despliega el
23

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.

macm
fuente
10
Solo una nota, no necesitas la lambda. La línea 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)
Roman Kutlak
Muchas gracias. funciona perfectamente
Chandan Kumar
14

Puede crear el suyo propio contextmanagerpara 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 raisesasí:

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
Pigueiras
fuente
1
¡Gracias por publicar una respuesta que no requiere el unittestmódulo!
Sherwood Callaway
10

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

Pi.
fuente
44
Me encanta doctest, pero creo que complementa en lugar de reemplazar unittest.
TimothyAWiseman
2
¿Es menos probable que doctest juegue bien con la refactorización automática? Supongo que una herramienta de refactorización diseñada para python debería tener en cuenta las cadenas de documentos. ¿Alguien puede comentar de su experiencia?
kdbanman
6

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)
Daryl Spitzer
fuente
Desafortunadamente, ya no lo proporciona. Pero la respuesta anterior de @Art ( stackoverflow.com/a/3166985/1504046 ) da el mismo resultado
Rmatt
6

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()
Arindam Roychowdhury
fuente
3

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)
Bruno Carvalho
fuente
-5

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
RUser4512
fuente