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


¿Cómo se escribe un unittest que falla solo si una función no lanza una excepción esperada?

Author: Aaron Hall, 2008-09-25

11 answers

Uso TestCase.assertRaises (o TestCase.failUnlessRaises) desde el módulo unittest, por ejemplo:

import mymod

class MyTestCase(unittest.TestCase):
    def test1(self):
        self.assertRaises(SomeCoolException, mymod.myfunc)
 451
Author: Moe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-05 15:59:38

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 , tienes que envolver context.exception en str, de lo contrario obtendrás un TypeError

self.assertTrue('This is broken' in str(context.exception))
 320
Author: Art,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-20 06:26:18

El código en mi respuesta anterior se puede simplificar a:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction)

Y si afunction toma argumentos, simplemente pásalos a assertRaises como este:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction, arg1, arg2)
 207
Author: Daryl Spitzer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-18 13:37:56

¿Cómo se prueba que una función Python arroja 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 gestor 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 biblioteca unittest

En Python 2.7 o 3:

import unittest

En Python 2.6, puede instalar un backport de la biblioteca unittest de 2.7, llamada unittest2 , y simplemente alias que como unittest:

import unittest2 as unittest

Pruebas de ejemplo

Ahora, pegue en su shell Python la siguiente prueba de seguridad de tipo 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')

Test one usa assertRaises como gestor de contexto, lo que asegura que el error se capte y limpie correctamente, mientras se registra.

También podríamos escribirlo sin el gestor de contexto, véase 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 args restantes y los args de palabras clave se pasarán a esa función.

Creo que es mucho más simple, legible y mantenible 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 debe arroje lo siguiente:

..
----------------------------------------------------------------------
Ran 2 tests in 0.007s

OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>

Y vemos que, como esperamos, intentar agregar un 1 y a '1' resultado en un TypeError.


Para una salida más detallada, prueba esto:

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
 95
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:43

Su código debe seguir este patrón (esta es una prueba de estilo de 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 assertRaises solo comprueba si se ha producido una excepción.

 30
Author: Daryl Spitzer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-05 16:08:52

De: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/

Primero, aquí está la función correspondiente (still 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 que se 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)

El TypeError se eleva actullay, y genera un error de prueba. El problema es que esto 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 mí es perfecto también!!

Thansk mucho Sr. Julien Lengrand-Lambert

 12
Author: macm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-05 17:04:37

Uso doctest [1] casi en 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, se evalúan y comprueban ambos casos de prueba.

[1] Documentación de Python: 23.2 doctest examples Prueba ejemplos interactivos de Python

 8
Author: pi.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-25 11:17:18

Eche un vistazo al método assertRaises del módulo unittest.

 7
Author: Greg Hewgill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-21 12:57:25

Acabo de descubrir que la biblioteca falsa proporciona un método assertRaisesWithMessage () (en su unittest.subclase TestCase), que comprobará no solo que la excepción esperada se eleva, sino también que se eleva con el mensaje esperado:

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)
 5
Author: Daryl Spitzer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-28 00:13:39

Usted puede construir su propio contextmanager para comprobar si la excepción fue planteada.

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á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
 5
Author: Pigueiras,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-18 10:09:17

Puede utilizar 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)
 1
Author: Bruno Carvalho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-21 00:12:46