28. Diagnóstico de fallas en suites automatizadas

28.1 Objetivo del tema

Cuando una prueba falla, no alcanza con mirar que la suite está en rojo. Necesitamos diagnosticar qué falló, dónde falló, si el problema está en el código, en la prueba o en los datos.

En este tema construiremos una forma ordenada de analizar fallas en suites automatizadas usando las herramientas de pytest que ya venimos practicando.

Objetivo práctico: seguir un proceso claro para reproducir, aislar y entender una falla antes de corregirla.

28.2 Primer paso: leer la falla completa

El primer error frecuente es corregir sin leer. Una salida de pytest suele mostrar:

  • Nombre de la prueba fallida.
  • Archivo y línea.
  • Valor esperado.
  • Valor obtenido.
  • Excepción o mensaje de error.
  • Salida capturada o logs, si existen.

28.3 Crear una falla controlada

Crea temporalmente tests/test_diagnostico.py:

from app.carrito import calcular_total


def test_calcular_total_falla_de_ejemplo():
    productos = [
        {"precio": 100, "cantidad": 2},
        {"precio": 50, "cantidad": 3},
    ]

    assert calcular_total(productos) == 300

El resultado correcto debería ser 350. Esta prueba fallará para practicar diagnóstico.

28.4 Ejecutar solo la prueba fallida

Ejecuta el archivo específico:

python -m pytest tests/test_diagnostico.py

Luego ejecuta la prueba exacta:

python -m pytest tests/test_diagnostico.py::test_calcular_total_falla_de_ejemplo

Aislar la prueba reduce ruido y permite concentrarse en una falla.

28.5 Usar trazas más cortas

Para una salida compacta:

python -m pytest tests/test_diagnostico.py --tb=short

Para una línea por falla:

python -m pytest tests/test_diagnostico.py --tb=line

La traza corta ayuda cuando el error es claro. La traza larga ayuda cuando necesitas más contexto.

28.6 Detener en la primera falla

Si hay muchas fallas, comienza con una:

python -m pytest -x

Corregir la primera falla puede hacer desaparecer varias fallas derivadas.

28.7 Reejecutar solo lo relacionado

Si el problema está en carrito, puedes usar -k:

python -m pytest -k carrito

O ejecutar el archivo relacionado:

python -m pytest tests/test_carrito.py

28.8 Revisar si falló el código o la prueba

Ante una falla, no asumas automáticamente que el código está mal. También puede estar mal el valor esperado de la prueba.

Preguntas útiles:

  • ¿El resultado esperado coincide con la regla de negocio?
  • ¿Los datos de entrada representan el caso correcto?
  • ¿La prueba está verificando una sola idea?
  • ¿El código cambió y la prueba quedó desactualizada?
  • ¿La prueba depende de estado previo?

28.9 Agregar mensajes de aserción con cuidado

A veces un mensaje ayuda a diagnosticar:

def test_calcular_total_falla_de_ejemplo():
    productos = [
        {"precio": 100, "cantidad": 2},
        {"precio": 50, "cantidad": 3},
    ]

    total = calcular_total(productos)

    assert total == 350, f"Total calculado incorrecto: {total}"

No hace falta agregar mensajes a todas las aserciones. pytest ya muestra buenas diferencias en muchos casos.

28.10 Usar print de forma temporal

Durante diagnóstico puedes usar print y ejecutar con -s:

python -m pytest tests/test_diagnostico.py -s

Pero no dejes prints de depuración permanentes si no aportan valor a la prueba.

28.11 Usar caplog y logs existentes

Si el código ya registra logs, úsalos para diagnosticar. Por ejemplo:

python -m pytest tests/test_importador.py -o log_cli=true --log-cli-level=INFO

Esto puede mostrar información útil sin modificar la prueba.

28.12 Revisar datos de prueba

Muchas fallas vienen de datos incorrectos. Revisa archivos JSON, CSV, fixtures y factories.

Ejemplo de error frecuente:

{
  "precio": "100",
  "descuento": "10",
  "esperado": "90"
}

Si el código espera números, esos valores como texto pueden requerir conversión.

28.13 Ver pruebas recolectadas

Si una prueba no se ejecuta, revisa la recolección:

python -m pytest --collect-only

Esto ayuda a detectar nombres incorrectos de archivos o funciones.

28.14 Diagnosticar pruebas intermitentes

Si una prueba falla solo a veces, revisa:

  • Uso de fechas reales.
  • Aleatoriedad sin semilla.
  • Archivos compartidos.
  • Dependencias externas.
  • Orden de ejecución.
  • Ejecución paralela.

No agregues reintentos antes de entender la causa.

28.15 Comparar ejecución secuencial y paralela

Si una falla aparece solo con paralelización, compara:

python -m pytest
python -m pytest -n auto

Una falla solo en paralelo suele indicar estado compartido, archivos compartidos o dependencia de orden.

28.16 Usar --pdb como herramienta avanzada

pytest puede abrir el depurador cuando una prueba falla:

python -m pytest tests/test_diagnostico.py --pdb

Esto es útil para inspeccionar variables en el momento del error. Es una herramienta más avanzada y conviene usarla cuando la salida normal no alcanza.

28.17 Reproducir antes de corregir

Antes de modificar el código, intenta reproducir la falla con el comando más pequeño posible.

Buen flujo:

  1. Ejecutar la suite.
  2. Identificar la prueba fallida.
  3. Ejecutar solo esa prueba.
  4. Leer valores esperado y obtenido.
  5. Revisar datos y preparación.
  6. Corregir código o prueba.
  7. Ejecutar la prueba y luego la suite completa.

28.18 No corregir borrando la prueba

Si una prueba falla, eliminarla rara vez es la solución correcta. Antes de borrar una prueba, entiende qué protegía.

Una prueba puede eliminarse si:

  • Verifica un comportamiento que ya no existe.
  • Está duplicada por otra prueba mejor.
  • Es incorrecta y no aporta una regla útil.

Pero no debe borrarse solo porque falla.

28.19 Registrar el diagnóstico

Cuando una falla fue difícil, conviene registrar la causa en el mensaje del cambio, en una nota interna o convirtiéndola en una prueba de regresión.

Si fue un error real, agrega o ajusta una prueba para que no vuelva a ocurrir.

28.20 Problemas frecuentes

  • Se corrige sin leer la falla: puede introducir otro error.
  • Solo se ejecuta la prueba aislada: después de corregir, ejecuta la suite completa.
  • Se culpa al código sin revisar datos: revisa fixtures, factories, JSON y CSV.
  • Se agregan reintentos sin causa: primero identifica la fuente de fragilidad.
  • Se borra una prueba fallida: entiende qué comportamiento protegía.

28.21 Ejercicio práctico

Usa la prueba fallida de ejemplo de tests/test_diagnostico.py. Haz lo siguiente:

  • Ejecuta solo esa prueba.
  • Ejecuta con --tb=short.
  • Agrega una variable total antes del assert.
  • Corrige el valor esperado.
  • Ejecuta la suite completa.

28.22 Solución propuesta

Prueba corregida:

from app.carrito import calcular_total


def test_calcular_total_con_varios_productos_devuelve_suma_total():
    productos = [
        {"precio": 100, "cantidad": 2},
        {"precio": 50, "cantidad": 3},
    ]

    total = calcular_total(productos)

    assert total == 350

Comandos:

python -m pytest tests/test_diagnostico.py --tb=short
python -m pytest

28.23 Lista de verificación

Antes de continuar con el próximo tema, verifica lo siguiente:

  • Lees la falla completa antes de corregir.
  • Ejecutas la prueba fallida de forma aislada.
  • Usas --tb, -x, -k o --collect-only cuando corresponde.
  • Revisas datos de prueba y fixtures.
  • Distingues entre error del código y error de la prueba.
  • No ocultas fallas con reintentos sin diagnóstico.
  • Ejecutas la suite completa después de corregir.

28.24 Conclusión

En este tema practicamos un proceso de diagnóstico para fallas en suites automatizadas. Una corrección confiable empieza por reproducir, aislar y entender el problema.

En el próximo tema veremos cómo mantener pruebas automatizadas legibles y confiables a lo largo del tiempo.