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.
El ciclo puede resumirse en cuatro pasos:
Este ciclo es útil tanto al escribir código nuevo como al modificar código existente.
Ejecutar una prueba solo nos da información. El valor aparece cuando usamos esa información para tomar una decisión.
Posibles resultados:
Cada resultado requiere una acción distinta.
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.
Cuando una prueba falla, no conviene cambiar código al azar. Primero debemos entender qué está mal.
Preguntas útiles:
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.
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.
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.
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.
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.
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.
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:
Antes de corregir el código productivo, debemos asegurarnos de que la prueba esté fallando por el comportamiento que queremos implementar.
El ciclo puede repetirse varias veces. Cada repetición debería acercarnos a una suite estable.
El objetivo no es que todo pase a la primera, sino usar las fallas como información.
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:
No conviene actualizar pruebas solo para que pasen. Cada cambio en una prueba debe tener una razón.
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.
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:
Lo peor es dejarla fallando sin explicació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:
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:
La disciplina de ejecución importa tanto como escribir las pruebas.
| 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? |
Durante el ciclo de ejecución, revisa:
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.