29. Mantenimiento de pruebas automatizadas legibles y confiables

29.1 Objetivo del tema

Una suite automatizada no termina cuando las pruebas pasan. Con el tiempo, las pruebas deben mantenerse: nombres, datos, fixtures, estructura, tiempos de ejecución y confiabilidad.

En este tema veremos criterios prácticos para mantener pruebas legibles y confiables antes de avanzar al caso integrador.

Objetivo práctico: aplicar una revisión de mantenimiento sobre una suite automatizada para que siga siendo útil a medida que crece.

29.2 Qué significa mantener una suite

Mantener una suite implica revisar que las pruebas sigan aportando valor. No basta con que existan muchos archivos de prueba.

Una suite mantenible debe ser:

  • Fácil de ejecutar.
  • Fácil de leer.
  • Rápida en lo posible.
  • Confiable en sus resultados.
  • Clara cuando falla.
  • Ordenada en estructura y datos.

29.3 Señales de mantenimiento pendiente

Revisa la suite si aparecen estas señales:

  • Pruebas con nombres como test_1 o test_ok.
  • Datos duplicados en muchos archivos.
  • Fixtures que nadie entiende.
  • Pruebas que fallan a veces sin razón clara.
  • Reportes o archivos generados mezclados con código.
  • Comandos de ejecución no documentados.

29.4 Mejorar nombres de pruebas

Una prueba difícil de entender:

def test_1():
    assert validar_cupon("DESC10") is True

Versión mantenible:

def test_validar_cupon_con_codigo_correcto_devuelve_true():
    assert validar_cupon("DESC10") is True

El nombre debe explicar qué comportamiento se verifica.

29.5 Evitar pruebas con demasiadas verificaciones

Una prueba con demasiadas ideas es más difícil de diagnosticar:

def test_usuario():
    usuario = crear_usuario()
    assert usuario_esta_activo(usuario) is True
    assert obtener_email(usuario) == "ana@example.com"
    assert usuario["nombre"] == "Ana"

Puede dividirse en pruebas más enfocadas:

def test_usuario_esta_activo_con_usuario_valido_devuelve_true(usuario_valido):
    assert usuario_esta_activo(usuario_valido) is True


def test_obtener_email_con_usuario_valido_devuelve_email_normalizado(usuario_valido):
    assert obtener_email(usuario_valido) == "ana@example.com"

29.6 Revisar duplicación de datos

Si repites el mismo diccionario en muchas pruebas, considera una fixture o factory.

Datos duplicados:

usuario = {
    "nombre": "Ana",
    "email": "ana@example.com",
    "activo": True,
}

Factory reutilizable:

def crear_usuario(**overrides):
    usuario = {
        "nombre": "Ana",
        "email": "ana@example.com",
        "activo": True,
    }
    usuario.update(overrides)
    return usuario

29.7 Revisar fixtures

Una fixture debe tener un propósito claro. Si una fixture prepara demasiadas cosas, puede ocultar información importante.

Preguntas útiles:

  • ¿El nombre explica qué entrega?
  • ¿Se usa en más de una prueba?
  • ¿Está en el lugar correcto?
  • ¿Hace limpieza si crea recursos?
  • ¿Podría ser una factory más simple?

29.8 Mantener conftest.py ordenado

conftest.py no debe convertirse en un depósito de todo. Úsalo para fixtures compartidas, no para cualquier función auxiliar.

Si una construcción crece, muévela a tests/helpers y deja en conftest.py solo la fixture que la expone.

29.9 Revisar datos externos

Los archivos de tests/data también requieren mantenimiento.

  • Eliminar casos duplicados.
  • Usar nombres de archivo claros.
  • Agregar IDs legibles a casos JSON.
  • Separar datos por intención.
  • Validar campos esperados antes de usarlos.

29.10 Revisar marcadores

Los marcadores deben representar categorías útiles. Si hay demasiados, pierden valor.

Revisa:

  • ¿Las pruebas lentas están marcadas como lento?
  • ¿Las críticas son realmente críticas?
  • ¿Las pruebas de regresión protegen errores o comportamientos importantes?
  • ¿Hay marcadores declarados que ya no se usan?

29.11 Revisar tiempos de ejecución

Ejecuta:

python -m pytest --durations=10

Si una prueba aparece siempre entre las más lentas, revisa si puede optimizarse, marcarse como lento o separarse en otra suite.

29.12 Revisar fragilidad

Una prueba confiable debe fallar por una causa real. Revisa pruebas que:

  • Usan time.sleep.
  • Dependen de fecha actual.
  • Usan aleatoriedad sin semilla.
  • Escriben archivos compartidos.
  • Fallan solo en paralelo.

29.13 Revisar comandos de ejecución

Los comandos principales deben estar documentados y funcionar:

python run_tests.py completa
python run_tests.py rapida
python run_tests.py critica
python run_tests.py reporte

Si un comando queda obsoleto, actualiza el script o el README.

29.14 Revisar reportes y artefactos

Los reportes deben generarse en reports u otra carpeta clara. Evita mezclar reportes con código o datos versionados.

Verifica:

  • reports/ está en .gitignore si corresponde.
  • Los reportes tienen nombres claros.
  • El script puede limpiar reportes anteriores si se necesita.

29.15 Crear una checklist de mantenimiento

Agrega al README.md una checklist:

## Checklist de mantenimiento de pruebas

- Los nombres de pruebas describen comportamiento.
- No hay datos duplicados innecesarios.
- Las fixtures tienen nombres claros.
- conftest.py no contiene lógica excesiva.
- Los datos de tests/data tienen nombres claros.
- Las pruebas lentas están marcadas.
- La suite rápida funciona.
- Los reportes se generan en reports.
- La suite completa pasa con python -m pytest.

29.16 Refactorizar pruebas sin cambiar comportamiento

Refactorizar pruebas significa mejorar su estructura sin cambiar lo que verifican.

Ejemplos:

  • Renombrar pruebas.
  • Extraer fixtures.
  • Eliminar duplicación de datos.
  • Separar una prueba demasiado grande.
  • Mejorar IDs de parametrización.

Después de refactorizar, ejecuta la suite completa.

29.17 No sobreoptimizar pruebas pequeñas

No toda repetición merece una abstracción. Si extraer una fixture hace que la prueba sea más difícil de leer, tal vez conviene dejar el dato explícito.

La prioridad es claridad. La reutilización debe ayudar, no esconder el escenario.

29.18 Mantener pruebas alineadas al comportamiento

Las pruebas deben verificar comportamientos observables. Evita acoplarlas demasiado a detalles internos que pueden cambiar sin afectar la funcionalidad.

Mejor probar:

assert calcular_total(productos) == 350

Que probar pasos internos que no forman parte del contrato de la función.

29.19 Problemas frecuentes

  • La suite crece pero nadie la entiende: revisa nombres, estructura y README.
  • Hay muchas fixtures confusas: elimina o renombra las que no aportan claridad.
  • Las pruebas tardan demasiado: revisa --durations y marcadores.
  • Hay fallas intermitentes: busca dependencias de tiempo, orden o recursos compartidos.
  • Se duplican pruebas: revisa si una parametrización puede reemplazarlas.

29.20 Ejercicio práctico

Revisa tres archivos de prueba del proyecto y aplica al menos tres mejoras:

  • Renombrar una prueba poco clara.
  • Extraer una fixture o factory útil.
  • Eliminar duplicación de datos.
  • Agregar IDs a una prueba parametrizada.
  • Marcar una prueba lenta, crítica o de regresión.
  • Actualizar el README con comandos o checklist.

29.21 Solución propuesta

Ejemplo de mejora con parametrización e IDs:

import pytest

from app.cupones import validar_cupon


@pytest.mark.parametrize(
    "cupon, esperado",
    [
        ("DESC10", True),
        ("desc10", True),
        ("DESC20", False),
    ],
    ids=["codigo_correcto", "minusculas", "codigo_incorrecto"],
)
def test_validar_cupon_devuelve_resultado_esperado(cupon, esperado):
    assert validar_cupon(cupon) is esperado

Después de aplicar cambios:

python -m pytest

29.22 Lista de verificación

Antes de continuar con el caso integrador, verifica lo siguiente:

  • Los nombres de pruebas son claros.
  • Las pruebas están enfocadas en una idea principal.
  • No hay duplicación innecesaria de datos.
  • Las fixtures y factories tienen sentido.
  • Los datos externos están ordenados.
  • Los marcadores reflejan categorías útiles.
  • La suite completa se ejecuta con python -m pytest.

29.23 Conclusión

En este tema revisamos cómo mantener pruebas automatizadas legibles y confiables. Una suite útil requiere atención continua: nombres claros, datos ordenados, fixtures simples y fallas fáciles de diagnosticar.

En el próximo tema construiremos un caso práctico integrador con una suite automatizada completa para un proyecto Python.