unit-testing ejemplos - ¿Cómo prueba que una función de Python lanza una excepción?




traceback utilizar (10)

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)

¿Cómo se escribe una prueba de unidad que falla solo si una función no lanza una excepción esperada?


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)

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


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


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)

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

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


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))

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.


Usted podría hacer dos decoradores independientes que hacen lo que usted quiere, como se ilustra a continuación directamente. Observe el uso de *args, **kwargsen la declaración de la wrapped()función que admite la función decorada con múltiples argumentos (que no es realmente necesario para la say()función de ejemplo , pero se incluye para generalidad)

Por razones similares, el functools.wrapsdecorador se usa para cambiar los meta atributos de la función envuelta para que sean los del decorado. Esto hace que los mensajes de error y la documentación de la función incorporada ( func.__doc__) sean los de la función decorada en lugar de wrapped()'s.

from functools import wraps

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<b>" + fn(*args, **kwargs) + "</b>"
    return wrapped

def makeitalic(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<i>" + fn(*args, **kwargs) + "</i>"
    return wrapped

@makebold
@makeitalic
def say():
    return 'Hello'

print(say())  # -> <b><i>Hello</i></b>

Refinamientos

Como puede ver, hay muchos códigos duplicados en estos dos decoradores. Dada esta similitud, sería mejor que usted creara una genérica que en realidad era una fábrica de decoradores; en otras palabras, un decorador que crea otros decoradores. De esa manera, habría menos repetición de código y permitiría seguir el principio DRY .

def html_deco(tag):
    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return '<%s>' % tag + fn(*args, **kwargs) + '</%s>' % tag
        return wrapped
    return decorator

@html_deco('b')
@html_deco('i')
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

Para hacer que el código sea más legible, puede asignar un nombre más descriptivo a los decoradores generados en fábrica:

makebold = html_deco('b')
makeitalic = html_deco('i')

@makebold
@makeitalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

O incluso combinarlos de esta manera:

makebolditalic = lambda fn: makebold(makeitalic(fn))

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

Eficiencia

Si bien los ejemplos anteriores funcionan correctamente, el código generado implica una gran cantidad de gastos generales en forma de llamadas de funciones extrañas cuando se aplican varios decoradores a la vez. Esto puede no importar, dependiendo del uso exacto (que podría estar vinculado a E / S, por ejemplo).

Si la velocidad de la función decorada es importante, la sobrecarga se puede mantener en una sola función de función adicional escribiendo una función de fábrica de decorador ligeramente diferente que implementa la adición de todas las etiquetas a la vez, para que pueda generar un código que evite las funciones adicionales llamadas mediante el uso de decoradores separados para cada etiqueta.

Esto requiere más código en el decorador en sí, pero solo se ejecuta cuando se aplica a definiciones de funciones, no más tarde cuando se llaman ellas mismas. Esto también se aplica cuando se crean nombres más legibles utilizando lambdafunciones como se ilustró anteriormente. Muestra:

def multi_html_deco(*tags):
    start_tags, end_tags = [], []
    for tag in tags:
        start_tags.append('<%s>' % tag)
        end_tags.append('</%s>' % tag)
    start_tags = ''.join(start_tags)
    end_tags = ''.join(reversed(end_tags))

    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return start_tags + fn(*args, **kwargs) + end_tags
        return wrapped
    return decorator

makebolditalic = multi_html_deco('b', 'i')

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>






python unit-testing exception exception-handling