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:
"""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:
[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.
pip install pytestLas convenciones de pytest importan: busca la carpeta tests, archivos que
empiecen por test_ y funciones que empiecen por test_.
from app.funciones.cifrado_cesar import cifrado_cesar
def test_cifrado_cesar():
assert cifrado_cesar(texto="mama", clave=1) == "NBNB"pytest -vLa 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:
"""
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_esperadaFí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 posibleOcultar la solución
@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
],
)