Pruebas unitarias en Python con Unittest

    Introducci贸n

    En casi todos los campos, los productos se prueban minuciosamente antes de ser lanzados al mercado para garantizar su calidad y que funcionan seg煤n lo previsto.

    Los medicamentos, los productos cosm茅ticos, los veh铆culos, los tel茅fonos y las computadoras port谩tiles se prueban para garantizar que mantengan un cierto nivel de calidad que se prometi贸 al consumidor. Dada la influencia y el alcance del software en nuestra vida diaria, es importante que probemos nuestro software minuciosamente antes de lanzarlo a nuestros usuarios para evitar que surjan problemas cuando est茅 en uso.

    Hay varias formas y m茅todos de probar nuestro software, y en este art铆culo nos concentraremos en probar nuestros programas Python usando el Prueba de unidad marco de referencia.

    Prueba unitaria frente a otras formas de prueba

    Hay varias formas de probar software que se agrupan principalmente en pruebas funcionales y no funcionales.

    • Pruebas no funcionales: Destinado a verificar y comprobar los aspectos no funcionales del software, como la fiabilidad, la seguridad, la disponibilidad y la escalabilidad. Ejemplos de pruebas no funcionales incluyen pruebas de carga y pruebas de estr茅s.
    • Pruebas funcionales: Implica probar nuestro software con los requisitos funcionales para garantizar que ofrece la funcionalidad requerida. Por ejemplo, podemos probar si nuestra plataforma de compras env铆a correos electr贸nicos a los usuarios despu茅s de realizar sus pedidos simulando ese escenario y verificando el correo electr贸nico.

    Las pruebas unitarias se incluyen en las pruebas funcionales junto con las pruebas de integraci贸n y las pruebas de regresi贸n.

    Las pruebas unitarias se refieren a un m茅todo de prueba en el que el software se divide en diferentes componentes (unidades) y cada unidad se prueba funcionalmente y de forma aislada de las otras unidades o m贸dulos.

    Aqu铆, una unidad se refiere a la parte m谩s peque帽a de un sistema que logra una 煤nica funci贸n y se puede probar. El objetivo de las pruebas unitarias es verificar que cada componente de un sistema funcione seg煤n lo esperado, lo que a su vez confirma que todo el sistema cumple y cumple los requisitos funcionales.

    Las pruebas unitarias generalmente se realizan antes de las pruebas de integraci贸n, ya que, para verificar que las partes de un sistema funcionan bien juntas, primero debemos verificar que funcionen como se espera individualmente. Generalmente tambi茅n lo llevan a cabo los desarrolladores que construyen los componentes individuales durante el proceso de desarrollo.

    Beneficios de las pruebas unitarias

    Las pruebas unitarias son beneficiosas porque corrigen errores y problemas al principio del proceso de desarrollo y, finalmente, lo acelera.

    El costo de corregir los errores identificados durante las pruebas unitarias tambi茅n es bajo en comparaci贸n con corregirlos durante las pruebas de integraci贸n o durante la producci贸n.

    Las pruebas unitarias tambi茅n sirven como documentaci贸n del proyecto al definir lo que hace cada parte del sistema a trav茅s de pruebas bien escritas y documentadas. Al refactorizar un sistema o agregar funciones, las pruebas unitarias ayudan a protegerse contra cambios que rompen la funcionalidad existente.

    Marco de prueba unitaria

    Inspirado en el marco de pruebas JUnit para Java, unittest es un marco de prueba para programas de Python que viene incluido con la distribuci贸n de Python desde Python 2.1. A veces se lo denomina PyUnit. El marco admite la automatizaci贸n y agregaci贸n de pruebas y el c贸digo com煤n de configuraci贸n y apagado para ellas.

    Lo logra y m谩s a trav茅s de los siguientes conceptos:

    • Accesorio de prueba: Define la preparaci贸n necesaria para la ejecuci贸n de las pruebas y las acciones que deben realizarse despu茅s de la conclusi贸n de una prueba. Los accesorios pueden incluir la configuraci贸n y conexi贸n de la base de datos, la creaci贸n de archivos o directorios temporales y la limpieza o eliminaci贸n posterior de los archivos despu茅s de que se haya completado la prueba.
    • Caso de prueba: Se refiere a la prueba individual que busca una respuesta espec铆fica en un escenario dado con entradas espec铆ficas.
    • Banco de pruebas: Representa una agregaci贸n de casos de prueba que est谩n relacionados y deben ejecutarse juntos.
    • Corredor de pruebas: Coordina la ejecuci贸n de las pruebas y proporciona los resultados del proceso de prueba al usuario a trav茅s de una interfaz gr谩fica de usuario, el terminal o un informe escrito en un archivo.

    unittest no es el 煤nico marco de prueba para Python que existe, otros incluyen Pytest, Marco de robot, Lechuga para BDD, y Behave Framework.

    Si est谩 interesado en leer m谩s sobre el desarrollo basado en pruebas en Python con PyTest, 隆lo tenemos cubierto!

    Marco Unittest en acci贸n

    Vamos a explorar el unittest framework construyendo una aplicaci贸n de calculadora simple y escribiendo las pruebas para verificar que funciona como se esperaba. Usaremos el proceso de desarrollo basado en pruebas comenzando con las pruebas y luego implementando la funcionalidad para que las pruebas pasen.

    Si bien es una buena pr谩ctica desarrollar nuestra aplicaci贸n Python en un entorno virtual, para este ejemplo no ser谩 obligatorio ya que unittest se env铆a con la distribuci贸n de Python y no necesitaremos ning煤n otro paquete externo para construir nuestra calculadora.

    Nuestra calculadora realizar谩 operaciones simples de suma, resta, multiplicaci贸n y divisi贸n entre dos n煤meros enteros. Estos requisitos guiar谩n nuestras pruebas funcionales utilizando el unittest marco de referencia.

    Probaremos las cuatro operaciones admitidas por nuestra calculadora por separado y escribiremos las pruebas para cada una en un conjunto de pruebas separado, ya que se espera que las pruebas de una operaci贸n en particular se ejecuten juntas. Nuestras suites de prueba se alojar谩n en un archivo y nuestra calculadora en un archivo separado.

    Nuestra calculadora ser谩 un SimpleCalculator clase con funciones para manejar las cuatro operaciones que se esperan de ella. Comencemos a probar escribiendo las pruebas para la operaci贸n de suma en nuestro test_simple_calculator.py:

    import unittest
    from simple_calculator import SimpleCalculator
    
    class AdditionTestSuite(unittest.TestCase):
        def setUp(self):
            """ Executed before every test case """
            self.calculator = SimpleCalculator()
    
        def tearDown(self):
            """ Executed after every test case """
            print("ntearDown executing after the test case. Result:")
    
        def test_addition_two_integers(self):
            result = self.calculator.sum(5, 6)
            self.assertEqual(result, 11)
    
        def test_addition_integer_string(self):
            result = self.calculator.sum(5, "6")
            self.assertEqual(result, "ERROR")
    
        def test_addition_negative_integers(self):
            result = self.calculator.sum(-5, -6)
            self.assertEqual(result, -11)
            self.assertNotEqual(result, 11)
    
    # Execute all the tests when the file is executed
    if __name__ == "__main__":
        unittest.main()
    

    Empezamos importando el unittest m贸dulo y creando una suite de pruebas (AdditionTestSuite) para la operaci贸n de adici贸n.

    En 茅l, creamos un setUp() m茅todo que se llama antes de cada caso de prueba para crear nuestro SimpleCalculator objeto que se utilizar谩 para realizar los c谩lculos.

    los tearDown() El m茅todo se ejecuta despu茅s de cada caso de prueba y, dado que no lo utilizamos mucho en este momento, solo lo usaremos para imprimir los resultados de cada prueba.

    Las funciones test_addition_two_integers(), test_addition_integer_string() y test_addition_negative_integers() son nuestros casos de prueba. Se espera que la calculadora sume dos n煤meros enteros positivos o negativos y devuelva la suma. Cuando se le presenta un n煤mero entero y una cadena, se supone que nuestra calculadora devuelve un error.

    los assertEqual() y assertNotEqual() son funciones que se utilizan para validar la salida de nuestra calculadora. los assertEqual() La funci贸n comprueba si los dos valores proporcionados son iguales, en nuestro caso, esperamos la suma de 5 y 6 ser – estar 11, por lo que compararemos esto con el valor devuelto por nuestra calculadora.

    Si los dos valores son iguales, la prueba ha pasado. Otras funciones de aserci贸n ofrecidas por unittest incluir:

    • assertTrue(a): Comprueba si la expresi贸n proporcionada es true
    • assertGreater(a, b): Comprueba si a es mayor que b
    • assertNotIn(a, b): Comprueba si a es en b
    • assertLessEqual(a, b): Comprueba si a es menor o igual a b
    • etc …

    Puede encontrar una lista de estas afirmaciones en esta hoja de trucos.

    Cuando ejecutamos el archivo de prueba, esta es la salida:

    $ python3 test_simple_calulator.py
    
    tearDown executing after the test case. Result:
    E
    tearDown executing after the test case. Result:
    E
    tearDown executing after the test case. Result:
    E
    ======================================================================
    ERROR: test_addition_integer_string (__main__.AdditionTestSuite)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "test_simple_calulator.py", line 22, in test_addition_integer_string
        result = self.calculator.sum(5, "6")
    AttributeError: 'SimpleCalculator' object has no attribute 'sum'
    
    ======================================================================
    ERROR: test_addition_negative_integers (__main__.AdditionTestSuite)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "test_simple_calulator.py", line 26, in test_addition_negative_integers
        result = self.calculator.sum(-5, -6)
    AttributeError: 'SimpleCalculator' object has no attribute 'sum'
    
    ======================================================================
    ERROR: test_addition_two_integers (__main__.AdditionTestSuite)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "test_simple_calulator.py", line 18, in test_addition_two_integers
        result = self.calculator.sum(5, 6)
    AttributeError: 'SimpleCalculator' object has no attribute 'sum'
    
    ----------------------------------------------------------------------
    Ran 3 tests in 0.001s
    
    FAILED (errors=3)
    

    En la parte superior de la salida, podemos ver la ejecuci贸n del tearDown() funci贸n mediante la impresi贸n del mensaje que especificamos. A esto le sigue la letra E y mensajes de error derivados de la ejecuci贸n de nuestras pruebas.

    Hay tres posibles resultados de una prueba, puede pasar, fallar o encontrar un error. los unittest marco indica los tres escenarios mediante:

    • Un punto final (.): Indica una prueba aprobada
    • La letra ‘F’: Indica una prueba fallida
    • La letra ‘E’: Indica que ocurri贸 un error durante la ejecuci贸n de la prueba.

    En nuestro caso, estamos viendo la letra E, lo que significa que nuestras pruebas encontraron errores que ocurrieron al ejecutar nuestras pruebas. Recibimos errores porque a煤n no hemos implementado el addition funcionalidad de nuestra calculadora:

    class SimpleCalculator:
        def sum(self, a, b):
            """ Function to add two integers """
            return a + b
    

    Nuestra calculadora ahora est谩 lista para sumar dos n煤meros, pero para estar seguros de que funcionar谩 como se espera, eliminemos el tearDown() funcionar de nuestras pruebas y ejecutar nuestras pruebas una vez m谩s:

    $ python3 test_simple_calulator.py
    E..
    ======================================================================
    ERROR: test_addition_integer_string (__main__.AdditionTestSuite)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "test_simple_calulator.py", line 22, in test_addition_integer_string
        result = self.calculator.sum(5, "6")
      File "/Users/robley/Desktop/code/python/unittest_demo/src/simple_calculator.py", line 7, in sum
        return a + b
    TypeError: unsupported operand type(s) for +: 'int' and 'str'
    
    ----------------------------------------------------------------------
    Ran 3 tests in 0.002s
    
    FAILED (errors=1)
    

    Nuestros errores se han reducido de 3 a solo una vez 1. El resumen del informe en la primera l铆nea E.. indica que una prueba result贸 en un error y no pudo completar la ejecuci贸n, y las dos restantes pasaron. Para realizar la primera prueba, tenemos que refactorizar nuestra funci贸n de suma de la siguiente manera:

        def sum(self, a, b):
            if isinstance(a, int) and isinstance(b, int):
                return a + b
    

    Cuando ejecutamos nuestras pruebas una vez m谩s:

    $ python3 test_simple_calulator.py
    F..
    ======================================================================
    FAIL: test_addition_integer_string (__main__.AdditionTestSuite)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "test_simple_calulator.py", line 23, in test_addition_integer_string
        self.assertEqual(result, "ERROR")
    AssertionError: None != 'ERROR'
    
    ----------------------------------------------------------------------
    Ran 3 tests in 0.001s
    
    FAILED (failures=1)
    

    Esta vez, nuestra funci贸n de suma se ejecuta hasta el final, pero nuestra prueba falla. Esto se debe a que no devolvimos ning煤n valor cuando una de las entradas no es un n煤mero entero. Nuestra afirmaci贸n se compara None a ERROR y como no son iguales, la prueba falla. Para hacer que nuestra prueba pase tenemos que devolver el error en nuestro sum() funci贸n:

    def sum(self, a, b):
        if isinstance(a, int) and isinstance(b, int):
            return a + b
        else:
            return "ERROR"
    

    Y cuando ejecutamos nuestras pruebas:

    $ python3 test_simple_calulator.py
    ...
    ----------------------------------------------------------------------
    Ran 3 tests in 0.000s
    
    OK
    

    Todas nuestras pruebas pasan ahora y obtenemos 3 paradas completas para indicar que todas nuestras 3 pruebas para la funcionalidad de adici贸n est谩n pasando. Los conjuntos de pruebas de resta, multiplicaci贸n y divisi贸n tambi茅n se implementan de manera similar.

    Tambi茅n podemos probar si se genera una excepci贸n. Por ejemplo, cuando un n煤mero se divide por cero, el ZeroDivisionError se plantea una excepci贸n. En nuestro DivisionTestSuite, podemos confirmar si se gener贸 la excepci贸n:

    class DivisionTestSuite(unittest.TestCase):
        def setUp(self):
            """ Executed before every test case """
            self.calculator = SimpleCalculator()
    
        def test_divide_by_zero_exception(self):
            with self.assertRaises(ZeroDivisionError):
                self.calculator.divide(10, 0)
    

    los test_divide_by_zero_exception() ejecutar谩 el divide(10, 0) funci贸n de nuestra calculadora y confirmar que la excepci贸n efectivamente se plante贸. Podemos ejecutar el DivisionTestSuite de forma aislada, como sigue:

    $ python3 -m unittest test_simple_calulator.DivisionTestSuite.test_divide_by_zero_exception
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    El conjunto de pruebas de funcionalidad de divisi贸n completa se puede encontrar en la esencia vinculada a continuaci贸n junto con los conjuntos de pruebas para la funcionalidad de multiplicaci贸n y resta.

    Conclusi贸n

    En este art铆culo, hemos explorado el unittest framework e identific贸 las situaciones en las que se utiliza al desarrollar programas Python. los unittest framework, tambi茅n conocido como PyUnit, viene con la distribuci贸n Python por defecto a diferencia de otros frameworks de prueba. De manera TDD, escribimos las pruebas para una calculadora simple, ejecutamos las pruebas y luego implementamos la funcionalidad para que las pruebas pasaran.

    los unittest framework proporcion贸 la funcionalidad para crear y agrupar casos de prueba y verificar la salida de nuestra calculadora con la salida esperada para verificar que est茅 funcionando como se esperaba.

    La calculadora completa y los conjuntos de pruebas se pueden encontrar aqu铆 en聽GitHub.

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *