30. Registro de decisiones: cuándo escribir la siguiente prueba

30.1 Objetivo del tema

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.

30.2 Por qué registrar decisiones

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.

Registrar decisiones evita saltar de una idea a otra sin dirección. También permite separar lo que estamos implementando ahora de lo que queda pendiente.

30.3 Un registro liviano

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.

30.4 Caso práctico

Continuaremos con una función para calcular el precio final de una reserva. La historia dice:

Como cliente, quiero reservar una habitación y ver el precio final según noches, temporada y descuentos disponibles.

La historia es amplia. El registro de decisiones nos ayudará a elegir una secuencia de pruebas manejable.

30.5 Primera decisión: caso base

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.

30.6 Implementación mínima

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.

30.7 Segunda decisión: generalizar cantidad de noches

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.

30.8 Implementar la generalización

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.

30.9 Tercera decisión: temporada alta

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.

30.10 Implementar temporada alta

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.

30.11 Cuarta decisión: descuento porcentual

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.

30.12 Implementar descuento

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.

30.13 Cuándo escribir la siguiente prueba

Conviene escribir una nueva prueba cuando aparece una de estas situaciones:

  • Hay una regla de negocio que todavía no está representada.
  • Existe un borde importante no probado.
  • Un ejemplo nuevo obliga a generalizar una implementación demasiado específica.
  • Hay una decisión que debe quedar documentada con comportamiento ejecutable.
  • Un error posible todavía no tiene una expectativa clara.

30.14 Cuándo no escribir otra prueba todavía

A veces el siguiente paso no es escribir una prueba nueva.

  • Si la prueba actual todavía está roja, primero hay que llevarla a verde.
  • Si hay duplicación evidente y la suite está en verde, puede ser momento de refactor.
  • Si no entendemos el requisito, conviene aclarar la regla antes de codificar.
  • Si la próxima prueba no agrega información nueva, puede ser redundante.

30.15 Registrar preguntas abiertas

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.

30.16 Elegir un borde: noches inválidas

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.

30.17 Implementar la validación

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.

30.18 Refactor guiado por decisiones

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.

30.19 Código refactorizado

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.

30.20 Registro final de ejemplo

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.

30.21 Errores frecuentes

  • Escribir la prueba más compleja primero.
  • Agregar varias reglas en una sola prueba.
  • Seguir escribiendo pruebas con la suite en rojo.
  • No anotar dudas y convertir supuestos en código definitivo.
  • Refactorizar antes de tener ejemplos suficientes.

30.22 Ejercicio práctico

Creá un registro de decisiones para una función calcular_envio.

  1. Anotá el caso base más simple.
  2. Escribí la primera prueba roja.
  3. Implementá el mínimo código para pasarla.
  4. Anotá tres reglas pendientes.
  5. Elegí la siguiente prueba justificando por qué aporta información nueva.

30.23 Checklist del tema

  • El registro separa decisiones actuales, pendientes y preguntas abiertas.
  • La siguiente prueba agrega información real al diseño.
  • Las dudas no confirmadas no se codifican como reglas definitivas.
  • El refactor ocurre con decisiones ya protegidas por pruebas.
  • La suite y el registro ayudan a retomar el trabajo con contexto.

30.24 Conclusión

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.