Excepción de aumento simulado de Python

Salman Mehmood 21 junio 2023
  1. Lanzar una excepción al usar la biblioteca de pruebas unitarias unittest en Python
  2. Generar manualmente una excepción de una función
Excepción de aumento simulado de Python

El objetivo principal de este artículo es demostrar cómo lanzar una excepción cuando se utiliza la biblioteca de pruebas unitarias unittest.

Lanzar una excepción al usar la biblioteca de pruebas unitarias unittest en Python

Para verificar que nuestro código sea estable y represente la mayoría de los casos de uso en el alcance del proyecto, debemos probarlo dándole entradas variadas. Una de las librerías presentes en Python, unittest, ofrece dicha funcionalidad, permitiendo escribir y realizar diferentes casos de prueba.

Durante las pruebas, es necesario poder manejar los casos en los que la entrada es inesperada. Podemos manejar tales casos lanzando una excepción que detalla qué está mal con la entrada dada.

Considere el siguiente código:

class A:
    def __init__(self):
        pass

    def computeData(self):
        return 0


class B:
    def method_to_test():
        obj = A()
        try:
            print(obj.computeData())
        except Exception as e:
            print("Exception at method_to_test: " + str(e))

Digamos que la función que queremos probar es method_to_test. Inicializa el objeto de otra clase A y luego llama a uno de sus métodos llamado computeData en un bloque try-except para detectar cualquier excepción.

Si queremos probar el comportamiento de computeData cuando se lanza una excepción específica, tenemos que hacerlo usando el módulo unittest.

Generar excepción una vez

Teniendo en cuenta el escenario mencionado anteriormente, también puede darse el caso de que queramos tener más control sobre cuándo lanzar una excepción.

Puede haber muchas razones por las que puede surgir tal necesidad. Dependiendo de la implementación de su código, tener más control sobre el comportamiento puede ser muy útil.

Afirmar excepción

Mientras escribe casos de prueba, es posible que desee verificar si sus excepciones se activan o no. Esto es especialmente útil cuando desea saber si se lanzó una excepción o no.

Esto puede ayudarlo a detectar antes en sus casos de prueba si algo sale mal.

Módulos de terceros simulados

Los módulos de terceros, dependiendo de nuestra implementación, pueden variar desde casi ninguno hasta muchos. Para probar si un módulo de terceros se comporta según lo previsto, también debemos probar su comportamiento con salidas variadas.

Un ejemplo sería request, un módulo muy utilizado cuando se comunica a través de una red o se envían/reciben datos mediante HTTP.

Generar manualmente una excepción de una función

Para lanzar manualmente una excepción desde la función computeData, tenemos que usar el atributo side_effect.

Considere el siguiente código:

import unittest
from unittest.mock import patch
from unittest.mock import MagicMock


class A:
    def __init__(self) -> None:
        pass

    def computeData(self):
        return 0


class B:
    def method_to_test():
        obj = A()
        try:
            print(obj.computeData())
        except Exception as e:
            print("Exception at method_to_test: " + str(e))


class Test(unittest.TestCase):
    @patch.object(A, "computeData", MagicMock(side_effect=Exception("Test")))
    def test_method(self):
        B.method_to_test()


if __name__ == "__main__":
    Test().test_method()

Producción :

Exception at method_to_test: Test

En la solución, se crea un nuevo método, test_method, para probar el método method_to_test. También es importante tener en cuenta que la función está decorada con patch.object.

El decorador parche se proporciona en el módulo para parchear módulos y atributos de nivel de clase. Para ser más específicos sobre qué parchear, usamos patch.object en lugar de patch para parchear el método directamente.

En el decorador, se pasa el nombre de clase A. Esto indica que el objeto que se va a parchear es una parte de A con el nombre de miembro computeData que se pasa.

El último parámetro es un objeto MagicMock, una subclase de Mock, donde asociamos nuestro comportamiento requerido a computeData usando el atributo side_effect, que es lanzar una excepción en nuestro caso.

El flujo general del programa es el siguiente:

  1. Se llama al test_method.
  2. Se parchea el computeData de la clase A. Se asigna un side_effect, que en nuestro caso es una excepción.
  3. Se llama al método_a_probar de la clase B.
  4. Se llama al constructor de la clase A, con la instancia almacenada en obj.
  5. Se llama a computeData. Debido a que el método está parcheado, se lanza una excepción.

Generar una excepción una vez

Para generar la excepción solo una vez o tener más control sobre el comportamiento de la función, side_effect admite más que solo una llamada de función.

Actualmente, side_effect admite:

  1. Iterable
  2. Invocable
  3. Excepción (Instancia o Clase)

Usando Iterable, podemos hacer que el método solo genere una excepción una vez.

class Test(unittest.TestCase):
    @patch.object(A, "computeData", MagicMock(side_effect=[1, Exception("Test"), 3]))
    def test_method(self):
        B.method_to_test()


if __name__ == "__main__":
    testClass = Test()
    testClass.test_method()
    testClass.test_method()
    testClass.test_method()

Producción :

1
Exception at method_to_test: Test
3

Debido al Iterable pasado, los elementos de nuestra elección se parchean y ganamos control sobre cuándo generar excepciones. Por lo tanto, la excepción solo se lanza la segunda vez que se llama a la función, y 1 y 3 se devuelven en otros casos.

Afirmar excepción

Para determinar si ocurrió una excepción específica o no, podemos usar unittest.TestCase.assertRaises en caso de que no se produzca nuestra excepción deseada.

class Test(unittest.TestCase):
    @patch.object(A, "computeData", MagicMock(side_effect=Exception("Test")))
    def test_method(self):
        self.assertRaises(KeyError, A.computeData)


if __name__ == "__main__":
    Test().test_method()

Producción :

Exception at method_to_test: Test
Traceback (most recent call last):
  File "d:\Python Articles\a.py", line 28, in <module>
    Test().test_method()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
  File "d:\Python Articles\a.py", line 25, in test_method
    self.assertRaises(KeyError, A.computeData)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 738, in assertRaises
    return context.handle('assertRaises', args, kwargs)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 201, in handle
    callable_obj(*args, **kwargs)
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1104, in __call__
    return self._mock_call(*args, **kwargs)
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1108, in _mock_call
    return self._execute_mock_call(*args, **kwargs)
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1163, in _execute_mock_call
    raise effect
  File "d:\Python Articles\a.py", line 17, in method_to_test
Exception: Test

Estamos haciendo cambios menores a nuestro método test_method, llamando a assertRaises con dos parámetros, lo que resultó en el resultado anterior.

Los dos parámetros se pueden definir como:

  1. KeyError: aquí se pasa cualquier excepción que desee desencadenar.
  2. A.computeData - El método que se supone que lanza la excepción.

Módulos de terceros simulados

Para burlarse de un post, como antes, debes usar el decorador parche. Un ejemplo sencillo puede ser el siguiente:

class Test(unittest.TestCase):
    @patch(
        "requests.post", MagicMock(side_effect=requests.exceptions.ConnectionError())
    )
    def test_method(self):
        requests.post()

Producción :

Traceback (most recent call last):
  File "d:\Python Articles\a.py", line 33, in <module>
    Test().test_method()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
  File "d:\Python Articles\a.py", line 30, in test_method
    requests.post()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1104, in __call__
    return self._mock_call(*args, **kwargs)
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1108, in _mock_call
    return self._execute_mock_call(*args, **kwargs)
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1163, in _execute_mock_call
    raise effect
requests.exceptions.ConnectionError

Las funciones de módulos de terceros también se pueden parchear utilizando el decorador patch, lo que permite una mejor depuración y más control sobre la escala de confiabilidad del producto/programa.

Salman Mehmood avatar Salman Mehmood avatar

Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.

LinkedIn

Artículo relacionado - Python Exception

Artículo relacionado - Python Unit Test