En este tema vamos a practicar una habilidad central de TDD: decidir cuál es la siguiente prueba que conviene escribir. No se trata de escribir pruebas al azar, sino de avanzar con una secuencia que descubra el diseño y reduzca incertidumbre.
Usaremos un registro simple de decisiones para anotar reglas pendientes, supuestos, preguntas y próximos ejemplos.
Durante TDD aparecen muchas preguntas: qué caso probar ahora, qué borde falta, cuándo refactorizar, qué regla todavía no está clara y qué supuesto estamos haciendo.
No necesitamos una herramienta compleja. Puede ser una sección en un archivo de notas o una lista al inicio del archivo de pruebas mientras estamos desarrollando.
Decisiones TDD
- Ahora: cupón válido de porcentaje.
- Siguiente: cupón inválido.
- Pendiente: vencimiento del cupón.
- Pendiente: descuento que no deje total negativo.
- Pregunta: ¿el cupón vence al inicio o al final del día?
La lista no reemplaza las pruebas. Solo ayuda a ordenar el camino.
Continuaremos con una función para calcular el precio final de una reserva. La historia dice:
La historia es amplia. El registro de decisiones nos ayudará a elegir una secuencia de pruebas manejable.
Empezamos por el caso más simple que define el comportamiento base.
Decisión
Probar primero una reserva de una noche, sin temporada especial y sin descuento.
Archivo a crear: tests/test_reservas.py
from reservas import calcular_precio
def test_reserva_de_una_noche_sin_descuento():
precio = calcular_precio(
noches=1,
precio_por_noche=100,
temporada="normal",
descuento=None
)
assert precio == 100
Ejecutamos python -m pytest. La primera prueba marca el punto de partida.
Creamos el código mínimo para pasar.
Archivo a crear: src/reservas.py
def calcular_precio(noches, precio_por_noche, temporada, descuento):
return precio_por_noche
Esta solución solo sirve para el primer ejemplo. La siguiente prueba debe forzar una regla nueva.
El siguiente ejemplo natural es cambiar la cantidad de noches.
Decisión
Antes de agregar descuentos, confirmar que el precio se multiplica por cantidad de noches.
Archivo a modificar: tests/test_reservas.py
def test_reserva_de_varias_noches_multiplica_el_precio():
precio = calcular_precio(
noches=3,
precio_por_noche=100,
temporada="normal",
descuento=None
)
assert precio == 300
Esta prueba evita saltar demasiado rápido a reglas complejas sin haber cerrado el cálculo base.
Ajustamos el código.
Archivo a modificar: src/reservas.py
def calcular_precio(noches, precio_por_noche, temporada, descuento):
return noches * precio_por_noche
Ejecutamos la suite. Ahora el cálculo base está protegido por dos ejemplos.
Agregamos una regla de negocio que cambia el precio.
Decisión
Probar temporada alta antes que descuentos porque modifica el subtotal base.
Archivo a modificar: tests/test_reservas.py
def test_temporada_alta_incrementa_precio_veinte_por_ciento():
precio = calcular_precio(
noches=2,
precio_por_noche=100,
temporada="alta",
descuento=None
)
assert precio == 240
La prueba define que la temporada alta aplica 20% sobre el subtotal de noches.
Agregamos solo la regla necesaria.
Archivo a modificar: src/reservas.py
def calcular_precio(noches, precio_por_noche, temporada, descuento):
subtotal = noches * precio_por_noche
if temporada == "alta":
return subtotal * 1.20
return subtotal
Después de llegar a verde, revisamos el registro para elegir el siguiente paso.
Ahora agregamos descuentos. La decisión importante es aplicar el descuento después de la temporada.
Decisión
El descuento se aplica sobre el precio ya ajustado por temporada.
Archivo a modificar: tests/test_reservas.py
def test_descuento_porcentual_se_aplica_sobre_precio_final_de_temporada():
precio = calcular_precio(
noches=2,
precio_por_noche=100,
temporada="alta",
descuento=0.10
)
assert precio == 216
El subtotal es 200, temporada alta lo lleva a 240 y el 10% de descuento deja 216.
Sumamos la regla al final del cálculo.
Archivo a modificar: src/reservas.py
def calcular_precio(noches, precio_por_noche, temporada, descuento):
subtotal = noches * precio_por_noche
if temporada == "alta":
subtotal *= 1.20
if descuento is not None:
subtotal -= subtotal * descuento
return subtotal
Ejecutamos python -m pytest. Si todo pasa, el registro puede marcar esta decisión
como cerrada.
Conviene escribir una nueva prueba cuando aparece una de estas situaciones:
A veces el siguiente paso no es escribir una prueba nueva.
No todas las dudas deben resolverse en el momento. Algunas pueden quedar anotadas.
Preguntas abiertas
- ¿La temporada baja tiene descuento?
- ¿El descuento puede superar el 100%?
- ¿Una reserva de cero noches debe rechazarse?
- ¿Los precios deben redondearse a enteros?
Estas preguntas pueden convertirse en pruebas cuando el negocio confirme la respuesta.
Una pregunta abierta se convierte en regla: una reserva debe tener al menos una noche.
Archivo a modificar: tests/test_reservas.py
import pytest
def test_no_permite_reserva_de_cero_noches():
with pytest.raises(ValueError, match="La reserva debe tener al menos una noche"):
calcular_precio(
noches=0,
precio_por_noche=100,
temporada="normal",
descuento=None
)
Esta prueba deja de ser una duda y pasa a ser una decisión ejecutable.
Agregamos la validación al inicio de la función.
Archivo a modificar: src/reservas.py
def calcular_precio(noches, precio_por_noche, temporada, descuento):
if noches <= 0:
raise ValueError("La reserva debe tener al menos una noche")
subtotal = noches * precio_por_noche
if temporada == "alta":
subtotal *= 1.20
if descuento is not None:
subtotal -= subtotal * descuento
return subtotal
Ejecutamos toda la suite para confirmar que la nueva validación no rompe los casos válidos.
Cuando el registro muestra varias reglas cerradas, podemos refactorizar con seguridad.
Decisiones cerradas
- El precio base es noches por precio por noche.
- La temporada alta aumenta 20%.
- El descuento se aplica después de la temporada.
- Cero noches es inválido.
Estas decisiones pueden reflejarse en funciones con nombres claros.
Con la suite en verde, separamos reglas.
Archivo a modificar: src/reservas.py
def calcular_precio(noches, precio_por_noche, temporada, descuento):
validar_noches(noches)
subtotal = calcular_subtotal(noches, precio_por_noche)
subtotal = aplicar_temporada(subtotal, temporada)
return aplicar_descuento(subtotal, descuento)
def validar_noches(noches):
if noches <= 0:
raise ValueError("La reserva debe tener al menos una noche")
def calcular_subtotal(noches, precio_por_noche):
return noches * precio_por_noche
def aplicar_temporada(subtotal, temporada):
if temporada == "alta":
return subtotal * 1.20
return subtotal
def aplicar_descuento(subtotal, descuento):
if descuento is None:
return subtotal
return subtotal - subtotal * descuento
Este refactor no agrega comportamiento. Solo traduce decisiones ya probadas a una estructura más legible.
Al cerrar una sesión de trabajo, el registro podría quedar así:
Hecho
- Reserva de una noche sin descuento.
- Varias noches multiplican el precio.
- Temporada alta agrega 20%.
- Descuento se aplica luego de temporada.
- Cero noches se rechaza.
Pendiente
- Redondeo de precios.
- Temporada baja.
- Descuento máximo permitido.
- Moneda y formato de salida.
Esto permite retomar el desarrollo sin volver a reconstruir mentalmente todas las decisiones.
Creá un registro de decisiones para una función calcular_envio.
Elegir la siguiente prueba es una decisión de diseño. Un registro simple ayuda a mantener foco, ordenar reglas pendientes y evitar saltos innecesarios. TDD se vuelve más claro cuando cada prueba responde una pregunta concreta y deja el sistema un poco mejor entendido.
En el próximo tema veremos errores frecuentes al practicar TDD y cómo corregirlos.