20. Ciclo de ejecución: ejecutar, fallar, corregir y repetir

20.1 Introducción

Las pruebas unitarias aportan valor cuando se ejecutan con frecuencia. No son archivos que escribimos una vez y olvidamos; forman parte del ciclo diario de desarrollo.

Un ciclo práctico es: ejecutar, fallar, corregir y repetir. Ejecutamos pruebas para obtener información, analizamos las fallas, corregimos el código o la prueba si corresponde, y volvemos a ejecutar para confirmar el resultado.

En este tema veremos cómo trabajar con ese ciclo sin perder tiempo ni confianza en la suite.

20.2 El ciclo básico

El ciclo puede resumirse en cuatro pasos:

  1. Ejecutar: correr una prueba, un archivo o toda la suite.
  2. Fallar: observar si alguna prueba no cumple su expectativa.
  3. Corregir: ajustar el código o la prueba según la causa real.
  4. Repetir: ejecutar nuevamente para confirmar que el problema se resolvió.

Este ciclo es útil tanto al escribir código nuevo como al modificar código existente.

20.3 Ejecutar una prueba no es el final

Ejecutar una prueba solo nos da información. El valor aparece cuando usamos esa información para tomar una decisión.

Posibles resultados:

  • La prueba pasa: el comportamiento cubierto coincide con lo esperado.
  • La prueba falla: hay una diferencia entre resultado obtenido y esperado.
  • La prueba no se ejecuta: hay un problema de configuración, nombre o descubrimiento.
  • La prueba queda colgada o tarda demasiado: puede haber dependencia externa, bucle o mala preparación.

Cada resultado requiere una acción distinta.

20.4 Ejemplo de falla inicial

Supongamos una función que debería aplicar un descuento del 10%.

def aplicar_descuento(precio):
    return precio * 0.10


def test_aplicar_descuento_del_10_por_ciento():
    assert aplicar_descuento(1000) == 900

La prueba falla porque la función devuelve 100, no 900. El código calcula el monto del descuento, pero la prueba espera el precio final.

20.5 Analizar antes de corregir

Cuando una prueba falla, no conviene cambiar código al azar. Primero debemos entender qué está mal.

Preguntas útiles:

  • ¿La expectativa de la prueba es correcta?
  • ¿El código implementa otra interpretación de la regla?
  • ¿La prueba preparó bien los datos?
  • ¿La falla aparece por un cambio reciente?
  • ¿La prueba está verificando el comportamiento adecuado?

En el ejemplo anterior, debemos decidir si la función debe devolver el descuento o el precio final. La corrección depende de esa decisión.

20.6 Corregir el código

Si la prueba expresa la regla correcta y el código está mal, corregimos el código.

def aplicar_descuento(precio):
    return precio - (precio * 0.10)


def test_aplicar_descuento_del_10_por_ciento():
    assert aplicar_descuento(1000) == 900

Después de corregir, repetimos la ejecución. Si la prueba pasa, confirmamos que el comportamiento cubierto quedó resuelto.

20.7 Corregir la prueba

A veces la prueba está mal. Puede tener un dato equivocado, una expectativa incorrecta o un nombre que no coincide con lo que verifica.

Por ejemplo, si la función se llama calcular_monto_descuento, quizá el resultado esperado correcto sea 100, no 900.

def calcular_monto_descuento(precio):
    return precio * 0.10


def test_calcular_monto_descuento_del_10_por_ciento():
    assert calcular_monto_descuento(1000) == 100

No debemos asumir que toda falla implica un defecto en el código productivo. Las pruebas también son código y pueden tener errores.

20.8 Ejecutar pruebas pequeñas durante el cambio

Mientras desarrollamos, suele ser más rápido ejecutar solo la prueba o archivo relacionado con el cambio actual. Esto da retroalimentación inmediata.

pytest tests/unit/test_descuentos.py

El comando exacto depende del lenguaje y del framework, pero la idea es la misma: correr el conjunto más pequeño que aporte información útil.

20.9 Ejecutar toda la suite antes de finalizar

Después de hacer cambios, conviene ejecutar una suite más amplia. Una prueba específica puede pasar, pero otro comportamiento relacionado podría haberse roto.

pytest tests/unit

Durante el trabajo usamos ejecuciones pequeñas para velocidad. Antes de finalizar, usamos ejecuciones más amplias para mayor confianza.

20.10 Falla esperada al escribir una prueba nueva

Cuando escribimos una prueba antes de implementar o corregir el código, es normal que primero falle. Esa falla confirma que la prueba detecta el comportamiento faltante.

Este ciclo se relaciona con TDD, aunque no profundizaremos aquí porque tendrá un curso propio.

La idea práctica es: una prueba nueva debe poder fallar por la razón correcta. Si una prueba nueva pasa sin haber implementado nada, quizá no está verificando lo que creemos.

20.11 Fallar por la razón correcta

No toda falla inicial es útil. La prueba debe fallar por el comportamiento esperado, no por un error de sintaxis, importación o preparación.

Ejemplos de fallas que no validan la intención:

  • El archivo de prueba no importa la función correcta.
  • La prueba usa un nombre de variable inexistente.
  • El framework no descubre la prueba.
  • La preparación crea datos inválidos por accidente.

Antes de corregir el código productivo, debemos asegurarnos de que la prueba esté fallando por el comportamiento que queremos implementar.

20.12 Repetir hasta estabilizar

El ciclo puede repetirse varias veces. Cada repetición debería acercarnos a una suite estable.

  1. La prueba falla.
  2. Corregimos una parte del código.
  3. La prueba cambia de error o pasa.
  4. Ejecutamos de nuevo para confirmar.
  5. Agregamos otro caso si descubrimos una situación no cubierta.

El objetivo no es que todo pase a la primera, sino usar las fallas como información.

20.13 Cuando una corrección rompe otra prueba

Si al corregir una prueba falla otra, puede haber una regresión o una expectativa que necesita actualizarse.

Antes de cambiar la segunda prueba, debemos analizar:

  • ¿La segunda prueba protege un comportamiento que debe seguir vigente?
  • ¿El requisito cambió realmente?
  • ¿La primera corrección fue demasiado amplia?
  • ¿Hay una regla no entendida entre ambos casos?

No conviene actualizar pruebas solo para que pasen. Cada cambio en una prueba debe tener una razón.

20.14 Regresión detectada

Supongamos que corregimos descuentos VIP y se rompe el descuento de empleados. La suite nos está avisando que tocamos una regla relacionada.

def obtener_descuento(tipo_cliente):
    if tipo_cliente == "vip":
        return 15
    if tipo_cliente == "empleado":
        return 20
    return 0

Si una modificación elimina accidentalmente la rama de empleados, la prueba correspondiente debería fallar. Esa es una regresión detectada a tiempo.

20.15 No ignorar pruebas fallidas

Una suite con pruebas fallidas de manera permanente pierde valor. Si el equipo se acostumbra a ver fallas, deja de confiar en la retroalimentación.

Cuando una prueba falla, debemos decidir:

  • Corregir el código productivo.
  • Corregir la prueba si la expectativa estaba mal.
  • Eliminar la prueba si ya no representa un comportamiento válido.
  • Marcarla temporalmente con una razón clara si existe un bloqueo real.

Lo peor es dejarla fallando sin explicación.

20.16 Cuidar el tiempo de ejecución

El ciclo funciona mejor cuando las pruebas son rápidas. Si una suite unitaria tarda demasiado, se ejecuta menos y pierde su propósito de retroalimentación frecuente.

Si las pruebas unitarias tardan mucho, conviene revisar:

  • Si están usando base de datos o red.
  • Si cargan archivos grandes.
  • Si hay esperas innecesarias.
  • Si mezclan integración con unitarias.
  • Si procesan demasiados datos para probar una regla simple.

20.17 Automatizar la ejecución frecuente

Muchas herramientas permiten ejecutar pruebas automáticamente al guardar archivos o antes de integrar cambios. Esto refuerza el ciclo de retroalimentación.

Aun sin automatización avanzada, podemos adoptar una práctica simple:

  • Ejecutar pruebas relacionadas durante el cambio.
  • Ejecutar toda la suite unitaria antes de finalizar.
  • No dejar fallas sin analizar.

La disciplina de ejecución importa tanto como escribir las pruebas.

20.18 Tabla del ciclo

Paso Objetivo Pregunta clave
Ejecutar Obtener retroalimentación. ¿Qué dice la suite?
Fallar Identificar diferencia entre esperado y obtenido. ¿Falló por la razón correcta?
Corregir Ajustar código o prueba. ¿Qué debe cambiar realmente?
Repetir Confirmar la corrección. ¿La suite quedó estable?

20.19 Lista de comprobación

Durante el ciclo de ejecución, revisa:

  • ¿Ejecutaste la prueba relacionada con el cambio?
  • ¿La falla se entiende?
  • ¿La prueba falla por el comportamiento correcto?
  • ¿Corregiste la causa real y no solo el síntoma?
  • ¿Volviste a ejecutar después de corregir?
  • ¿Ejecutaste una suite más amplia antes de finalizar?
  • ¿La suite quedó sin fallas inesperadas?

20.20 Qué debes recordar de este tema

  • Las pruebas unitarias aportan valor cuando se ejecutan con frecuencia.
  • El ciclo básico es ejecutar, fallar, corregir y repetir.
  • Una falla debe analizarse antes de cambiar código.
  • A veces falla el código productivo y a veces falla la prueba.
  • Conviene ejecutar pruebas pequeñas durante el desarrollo y suites más amplias antes de finalizar.
  • No debemos acostumbrarnos a pruebas fallidas permanentes.
  • La velocidad de la suite es clave para mantener el ciclo activo.

20.21 Conclusión

El ciclo ejecutar, fallar, corregir y repetir convierte las pruebas unitarias en una herramienta diaria de trabajo. Su valor está en la retroalimentación rápida y en la capacidad de detectar problemas cerca del cambio que los produjo.

Una suite estable, rápida y confiable permite modificar código con mayor seguridad. Pero esa confianza se construye ejecutando pruebas con disciplina y analizando cada falla con criterio.

En el próximo tema veremos cómo leer resultados de ejecución: pruebas exitosas, fallidas y omitidas.