Cómo usar PyTest para pruebas unitarias en Python

Cuando escribes código en Python, es importante asegurarte de que tu código funcione como se espera. Una de las mejores maneras de hacer esto es utilizando pruebas unitarias, que te ayudan a verificar si pequeñas partes (o unidades) de tu código están funcionando correctamente.

En este artículo, aprenderemos cómo escribir y ejecutar pruebas unitarias efectivas en Python utilizando PyTest, uno de los marcos de pruebas más populares para Python.

¿Qué son las Pruebas Unitarias?

Las pruebas unitarias son pruebas pequeñas y simples que se centran en verificar una sola función o un pequeño fragmento de código. Ayudan a asegurar que tu código funcione como se espera y pueden detectar errores temprano.

Las pruebas unitarias se pueden escribir para diferentes partes de tu código, como funciones, métodos e incluso clases. Al escribir pruebas unitarias, puedes probar tu código sin ejecutar todo el programa.

¿Por qué usar PyTest?

PyTest es un marco de pruebas popular para Python que facilita la escritura y ejecución de pruebas.

Es simple de usar y tiene muchas características útiles como:

  • Te permite escribir casos de prueba simples y claros.
  • Proporciona características avanzadas como fixtures, pruebas parametrizadas y complementos.
  • Funciona bien con otras herramientas y bibliotecas de pruebas.
  • Genera resultados y informes de pruebas fáciles de leer.

Configurando PyTest en Linux

Antes de comenzar a escribir pruebas, necesitamos instalar PyTest. Si no tienes PyTest instalado, puedes hacerlo usando el gestor de paquetes de Python llamado pip.

pip install pytest

Una vez que PyTest esté instalado, ¡estás listo para comenzar a escribir pruebas!

Escribiendo tu primera prueba con PyTest

Comencemos escribiendo una función simple y luego escribimos una prueba para ella.

Paso 1: Escribir una función simple

Primero, creemos una función en Python que queremos probar. Digamos que tenemos una función que suma dos números:

# add.py
def add(a, b):
    return a + b

Esta es una función simple que toma dos números a y b, los suma y devuelve el resultado.

Paso 2: Escribir una prueba para la función

Ahora, escribamos una prueba para la función de suma. En PyTest, las pruebas se escriben en archivos separados, generalmente nombrados test_*.py para identificar fácilmente los archivos de prueba.

Crea un nuevo archivo llamado test_add.py y escribe el siguiente código de prueba:

# test_add.py
from add import add

def test_add_numbers():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
    assert add(0, 0) == 0

Explicación del código anterior:

  • Importamos la función add del archivo add.py.
  • Definimos una función de prueba llamada test_add_numbers(). En PyTest, una función de prueba debe comenzar con la palabra test_.
  • Dentro de la función de prueba, utilizamos la declaración assert para verificar si el resultado de llamar a la función add coincide con el valor esperado. Si la condición en la declaración assert es True, la prueba pasa; de lo contrario, falla.

Paso 3: Ejecutar la Prueba

Para ejecutar la prueba, abre tu terminal y navega hasta el directorio donde se encuentra tu archivo test_add.py y luego ejecuta el siguiente comando:

pytest

PyTest encontrará automáticamente todos los archivos de prueba (aquellos que comienzan con test_) y ejecutará las pruebas dentro de ellos. Si todo está funcionando correctamente, deberías ver una salida como esta:

Verifying Python Code Functionality

El punto (.) indica que la prueba pasó. Si hubo algún problema, PyTest mostraría un mensaje de error.

Escribiendo Pruebas Más Avanzadas

Ahora que sabemos cómo escribir y ejecutar una prueba básica, exploremos algunas características más avanzadas de PyTest.

Pruebas de Excepciones Esperadas

A veces, quieres probar si tu código lanza las excepciones correctas cuando algo sale mal. Puedes hacer esto con la función pytest.raises().

Supongamos que queremos probar una función que divide dos números. Queremos lanzar una excepción si el segundo número es cero (para evitar errores de división por cero).

Aquí está la función divide:

# divide.py
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

Ahora, escribamos una prueba para esta función que verifique si se lanza ValueError cuando intentamos dividir por cero:

# test_divide.py
from divide import divide
import pytest

def test_divide_numbers():
    assert divide(10, 2) == 5
    assert divide(-10, 2) == -5
    assert divide(10, -2) == -5

def test_divide_by_zero():
    with pytest.raises(ValueError):
        divide(10, 0)

Explicación del código:

  • Agregamos una nueva función de prueba llamada test_divide_by_zero().
  • Dentro de esta función, utilizamos pytest.raises(ValueError) para verificar si se lanza un ValueError cuando llamamos a la función de división con cero como segundo argumento.

Ejecuta las pruebas nuevamente con el comando pytest. Si todo está funcionando correctamente, deberías ver esta salida:

Test Your Code with PyTest

Uso de Fixtures para Configuración y Limpieza

En algunos casos, es posible que necesites establecer ciertas condiciones antes de ejecutar tus pruebas o limpiar después de que las pruebas hayan finalizado. PyTest proporciona fixtures para manejar esto.

Un fixture es una función que puedes usar para configurar o desmontar condiciones para tus pruebas. Los fixtures se utilizan a menudo para crear objetos o conectarse a bases de datos que son necesarios para las pruebas.

Aquí hay un ejemplo de cómo usar un fixture para configurar un directorio temporal para probar operaciones con archivos:

# test_file_operations.py
import pytest
import os

@pytest.fixture
def temporary_directory():
    temp_dir = "temp_dir"
    os.mkdir(temp_dir)
    yield temp_dir  # This is where the test will run
    os.rmdir(temp_dir)  # Cleanup after the test

def test_create_file(temporary_directory):
    file_path = os.path.join(temporary_directory, "test_file.txt")
    with open(file_path, "w") as f:
        f.write("Hello, world!")
    
    assert os.path.exists(file_path)

Explicación del código:

  • Definimos un fixture llamado temporary_directory que crea un directorio temporal antes de la prueba y lo elimina después.
  • La función de prueba test_create_file() utiliza este fixture para crear un archivo en el directorio temporal y verifica si el archivo existe.

Ejecuta las pruebas nuevamente con el comando pytest. PyTest detectará y utilizará automáticamente el fixture.

Parametriza Tus Pruebas con Pytest

A veces, deseas ejecutar la misma prueba con diferentes entradas. PyTest te permite hacer esto fácilmente utilizando parametrizar.

Digamos que queremos probar nuestra función add con varios pares de números. En lugar de escribir funciones de prueba separadas para cada par, podemos utilizar pytest.mark.parametrize para ejecutar la misma prueba con diferentes entradas.

# test_add.py
import pytest
from add import add

@pytest.mark.parametrize("a, b, expected", [
    (2, 3, 5),
    (-1, 1, 0),
    (0, 0, 0),
    (100, 200, 300)
])
def test_add_numbers(a, b, expected):
    assert add(a, b) == expected

Explicación del código:

  • Utilizamos el decorador pytest.mark.parametrize para definir múltiples conjuntos de entradas (a, b y expected).
  • La función de prueba test_add_numbers() se ejecutará una vez por cada conjunto de entradas.

Ejecuta las pruebas nuevamente con el comando pytest, que ejecutará la prueba cuatro veces, una vez por cada conjunto de entradas.

Conclusión

En este artículo, hemos aprendido cómo escribir y ejecutar pruebas unitarias efectivas en Python utilizando PyTest para detectar errores temprano y asegurarnos de que tu código funcione como se espera.

PyTest facilita la escritura y ejecución de estas pruebas, y con sus potentes características, puedes manejar necesidades de prueba más complejas a medida que avanzas en tu viaje con Python.

Source:
https://www.tecmint.com/unit-testing-python-code-with-pytest/