Clase 03 · 28 minGratis

Pruebas unitarias con pytest

Clase 3 del curso gratis de buenas prácticas en Python: separamos el código en módulos, configuramos Pylint con .pylintrc y escribimos pruebas automáticas con pytest y parametrize.

Toca cualquier marca ▸ 0:00 de los apuntes y el video salta a ese momento. Abrir en YouTube

Un archivo por responsabilidad

La función se muda a app/funciones/cifrado_cesar.py, con su propio __init__.py, y main.py solo orquesta:

python
"""Este es el modulo principal"""
from funciones.cifrado_cesar import cifrado_cesar

MENSAJE = "Este es mi mensaje"
CLAVE = 2

resultado = cifrado_cesar(MENSAJE, CLAVE)
print(resultado)

Leer main.py ahora es leer el qué; el cómo vive en cada módulo. Esa es la idea que se repite el resto del curso: pasos de alto nivel en el archivo principal, detalles en funciones pequeñas con nombre propio .

Cuando Pylint se equivoca: .pylintrc

El código corre, pero Pylint no logra resolver el import y marca un error (E0401). En vez de pelear con él, se le pide que ignore esa regla puntual con un archivo de configuración en la raíz del proyecto:

ini
[MESSAGES CONTROL]
disable = E0401

Úsalo con criterio: silenciar una regla es una decisión que todo el equipo debería poder leer, no una forma de esconder problemas.

La primera prueba

Probar a mano (ejecutar y mirar la salida) no escala. Una prueba unitaria es una función que ejecuta tu código con una entrada conocida y comprueba que la salida sea la esperada.

bash
pip install pytest

Las convenciones de pytest importan: busca la carpeta tests, archivos que empiecen por test_ y funciones que empiecen por test_.

python
from app.funciones.cifrado_cesar import cifrado_cesar


def test_cifrado_cesar():
    assert cifrado_cesar(texto="mama", clave=1) == "NBNB"
bash
pytest -v

La primera vez falla, y está bien: se esperaba "nbnb" y la función devuelve mayúsculas. La prueba documenta cómo se comporta el código de verdad.

Muchos ejemplos, una sola prueba: parametrize

Copiar la función de prueba por cada ejemplo es repetir código, justo lo que el paradigma funcional evita. El decorador @pytest.mark.parametrize corre la misma prueba con una lista de tuplas:

python
"""
Aqui se prueba la funciona de cifrado cesar
"""
import pytest
from app.funciones.cifrado_cesar import cifrado_cesar


@pytest.mark.parametrize(
    "entrada,clave,salida_esperada",
    [
        ("mama", 1, "NBNB"),
        ("papa", 1, "QBQB"),
        ("A", 2, "C"),
        ("z", 1, "A"),
        ("Z!", 2, "B!"),
        ("@", 14, "@"),
        ("mama123", 1, "NBNB123"),
    ],
)
def test_cifrado_cesar(entrada, clave, salida_esperada):
    """Test para cifrado cesar"""
    texto_cifrado = cifrado_cesar(texto=entrada, clave=clave)
    assert texto_cifrado == salida_esperada

Fíjate en los casos de borde: la vuelta del alfabeto (z → A), los signos que no cambian y los números. Son justo los que nunca pruebas a mano .

La red de seguridad en acción

VS Code integra pytest (Ctrl + Shift + P → Python: Configure Tests). La demostración: un cambio «inocente», pasar el alfabeto a minúsculas, rompe seis pruebas al instante. Sin ellas, ese error habría llegado a producción.

TDD (test-driven development) va un paso más allá: primero escribes la prueba y después el código que la hace pasar . Lo aplicamos de lleno en las clases 4 y 6.

El reto de esta clase

Agrega a la prueba parametrizada tres casos nuevos que todavía no estén cubiertos: la Ñ, un texto con espacios y una clave negativa.

Ver una solución posible
python
@pytest.mark.parametrize(
    "entrada,clave,salida_esperada",
    [
        # ... los casos de la clase ...
        ("ñ", 1, "O"),                 # la Ñ es una letra más del alfabeto
        ("hola mundo", 1, "IPMB NVÑEP"),  # el espacio pasa tal cual
        ("IPMB", -1, "HOLA"),          # clave negativa: descifra
    ],
)

Sigue con la clase 4

Encriptación simétrica con TDD

Seguir el canal de WhatsApp