Módulo 4: TDD Workflow Completo
Red-Green-Refactor con AI
Red-Green-Refactor con AI
Descripción de la cápsula
En TDD clásico, tú escribes el test que falla, tú escribes el código mínimo para pasar, tú refactorizas. El ciclo es tuyo de punta a punta. Con Claude Code, el ciclo cambia: tú escribes el test, Claude Code escribe la implementación, y el refactor es colaborativo. El bottleneck deja de ser "escribir código" y pasa a ser "calidad de los tests" y "tu juicio para evaluar". Esta cápsula te muestra exactamente cómo funciona el ciclo adaptado y cuál es tu nuevo rol como arquitecto y evaluador.
El ciclo TDD clásico: Recap
Las tres fases
Todo ciclo TDD tiene tres fases bien definidas:
┌─────────────────────────────────────────────────────────────────────┐
│ CICLO RED-GREEN-REFACTOR │
└─────────────────────────────────────────────────────────────────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ RED │ ──────► │ GREEN │ ──────► │ REFACTOR │
└──────────┘ └──────────┘ └──────────┘
│ │ │
│ │ │
▼ ▼ ▼
Escribe test Escribe código Mejora código
que FALLA MÍNIMO para SIN romper
que pase tests verdes
│ │ │
│ │ │
└─────────────────────┴─────────────────────┘
│
│ vuelve a RED
│ (nuevo test)
└──────────────────────►
RED: Escribe un test que falle
Escribes un test que describe el comportamiento que quieres. No existe implementación aún — el test falla. Objetivo: definir qué debe hacer el código antes de escribirlo.
# test_email_validator.py — RED
import pytest
def test_validates_correct_email():
from validators import validate_email
assert validate_email("user@example.com") is True
Si ejecutas pytest test_email_validator.py, falla: ModuleNotFoundError o ImportError porque validate_email no existe.
GREEN: Escribe el código mínimo para pasar
Implementas lo mínimo necesario para que el test pase. No te preocupas por elegancia, DRY, ni edge cases — solo por hacer pasar el test.
# validators.py — GREEN
def validate_email(email: str) -> bool:
return "@" in email and "." in email.split("@")[-1]
El test pasa. Listo.
REFACTOR: Mejora sin romper
Con el test verde como red de seguridad, refactorizas: mejoras nombres, extraes funciones, manejas edge cases. Si algo rompe, los tests te avisan.
# validators.py — REFACTOR
import re
def validate_email(email: str) -> bool:
"""Validate email format using RFC 5322 simplified pattern."""
if not email or not isinstance(email, str):
return False
pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return bool(re.fullmatch(pattern, email))
Ejecutas tests. Siguen pasando. Puedes continuar con más confianza.
Por qué importa el orden: RED antes que GREEN
En TDD, el orden es sagrado. Si escribes la implementación primero y luego el test, no es TDD — es test-after. La diferencia psicológica es enorme:
- Test primero: El test es un contrato. Defines qué quieres sin sesgarte por lo que "es fácil de implementar". La implementación está forzada a cumplir el contrato.
- Test después: El test verifica lo que la implementación ya hace. Si la implementación tiene un bug, es fácil que el test lo refleje (o que ni siquiera lo detecte, porque escribes el test mirando el código).
Con Claude Code, esta disciplina es aún más crítica. Si le das código existente y pides "escribe tests para esto", los tests tenderán a validar el comportamiento actual — incluyendo bugs. Si le das tests y pides "implementa esto", la implementación está forzada a cumplir la especificación. Siempre RED antes de GREEN.
Cómo cambia el ciclo con Claude Code
Lo que sigue igual: RED
RED no cambia. Tú escribes el test que falla. Nadie más. Es tu responsabilidad definir qué debe hacer el sistema. Claude Code no puede adivinar tu intención — necesita el test como especificación.
RED (sin cambios):
┌─────────────────────────────────────┐
│ TÚ escribes el test que falla │
│ Defines el comportamiento esperado │
│ El test ES la especificación │
└─────────────────────────────────────┘
Lo que cambia: GREEN
GREEN lo hace Claude Code. Tú le das el test (y opcionalmente contexto). Claude Code genera la implementación. El tiempo de "implementación" pasa de 10-30 minutos a ~30 segundos.
GREEN (cambiado):
┌─────────────────────────────────────┐
│ TÚ: "Implementa validate_email │
│ que pase estos tests" │
│ CLAUDE CODE: genera implementación │
│ Tiempo: ~30 segundos │
└─────────────────────────────────────┘
Lo que es colaborativo: REFACTOR
REFACTOR es colaborativo. Claude Code puede proponer refactorings (simplificar, extraer función, mejorar nombres). Tú apruebas, modificas o rechazas. Los tests siguen siendo tu red de seguridad — antes de aceptar cualquier cambio, verificas que los tests pasen.
REFACTOR (colaborativo):
┌─────────────────────────────────────┐
│ Claude Code propone mejoras │
│ Tú evalúas: ¿vale la pena? │
│ Tests verdes = seguridad │
└─────────────────────────────────────┘
El bottleneck se desplaza
En TDD clásico, el bottleneck suele ser escribir la implementación. Con Claude Code, el bottleneck pasa a ser:
- Calidad de los tests: Si tu test es ambiguo, Claude Code puede implementar algo que "pasa" pero no es lo que querías.
- Tu juicio: ¿Claude Code falló porque la implementación es mala? ¿O porque tu test especificaba mal el comportamiento?
TDD Clásico:
Bottleneck: escribir código (10-30 min por ciclo)
TDD con Claude Code:
Bottleneck: calidad de tests + juicio para evaluar
El ritmo del TDD agentic
TDD clásico: distribución del tiempo
Un ciclo típico en TDD manual:
Ciclo TDD clásico (ejemplo: 17 min total):
├── RED: Escribir test ~2 min (12%)
├── GREEN: Implementar código ~10 min (59%) ← la mayor parte
└── REFACTOR: Mejorar código ~5 min (29%)
La mayor parte del tiempo está en implementar. Escribir el test es rápido; escribir código es lento.
TDD agentic: nueva distribución
Con Claude Code como implementador:
Ciclo TDD agentic (ejemplo: 10 min total):
├── RED: Escribir test (más pensado) ~5 min (50%) ← más tiempo aquí
├── GREEN: Claude Code implementa ~30 seg (5%)
├── EVALUAR: ¿Pasa? ¿Es correcto? ~3 min (30%) ← skill nuevo
└── REFACTOR: Ajustar, simplificar ~2 min (20%)
El developer pasa más tiempo pensando en tests y evaluando que en esperar código. La implementación llega en segundos; el juicio sigue siendo humano.
Tu rol cambia
| Antes (TDD clásico) | Ahora (TDD agentic) |
|---|---|
| Escribir código | Diseñar tests efectivos |
| Implementar features | Evaluar si la implementación es correcta |
| Refactorizar manualmente | Aprobar/rechazar propuestas de refactor |
| Coder | Arquitecto + Evaluador |
No eres un espectador. Guías el proceso: defines qué se construye (tests), apruebas o rechazas lo que Claude Code produce, y refinas iterativamente.
Anti-patrones a evitar en TDD agentic
| Anti-patrón | Qué pasa | Qué hacer en su lugar |
|---|---|---|
| Test gigante que valida toda la feature | Claude Code se abruma, implementación incompleta | Tests pequeños: un comportamiento por test |
| Pedir implementación sin tests | No es TDD; validación manual | Siempre RED primero |
| Aceptar código sin ejecutar tests | Bugs silenciosos | Siempre pytest antes de aprobar |
| Iterar sin dar contexto | Claude Code repite el mismo error | Incluye output de pytest, archivo de tests |
| Dejar que Claude Code escriba los tests | Tests reflejan implementación, no requisitos | Tú escribes tests; Claude implementa |
| Aceptar todo lo que genera la AI | Código que pasa pero no cumple estándar de calidad | Evalúa siempre; pide refactor si hace falta |
Recordatorio: ✅ Tú escribes tests. ❌ No pidas implementación sin tests. ⚠️ No aceptes sin ejecutar pytest.
Ejemplo completo: un ciclo de punta a punta
Feature: validador de email
Vas a construir validate_email(email: str) -> bool usando TDD con Claude Code.
Paso 1: RED — Escribes los tests
Creas tests/test_validators.py:
# tests/test_validators.py
import pytest
def test_validates_correct_email():
from validators import validate_email
assert validate_email("user@example.com") is True
assert validate_email("admin@company.co.uk") is True
def test_rejects_invalid_email():
from validators import validate_email
assert validate_email("invalid") is False
assert validate_email("user@") is False
assert validate_email("@domain.com") is False
assert validate_email("") is False
Ejecutas: pytest tests/test_validators.py → FAILED (módulo o función no existe).
Paso 2: Prompt a Claude Code
Abres el archivo de tests en Claude Code y escribes algo como:
Implementa la función validate_email en validators.py que pase estos tests.
La función recibe un string y retorna True si es un email válido, False en caso contrario.
Mantén la interfaz: validate_email(email: str) -> bool
Paso 3: GREEN — Claude Code implementa
Claude Code genera validators.py:
# validators.py
import re
def validate_email(email: str) -> bool:
"""Validate email format."""
if not email or not isinstance(email, str):
return False
pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return bool(re.fullmatch(pattern, email))
Ejecutas: pytest tests/test_validators.py → PASSED.
Paso 4: Evaluar
Esta es la fase que no existía como tal en TDD clásico. Cuando tú implementas, "evaluar" suele ser automático (sabes si está bien porque lo escribiste). Con Claude Code, debes juzgar explícitamente:
Checklist de evaluación:
- ¿Pasan los tests? Ejecuta
pytest. Si fallan, vuelve a Claude Code con el output del error. - ¿Maneja los edge cases que pensaste? Revisa: strings vacíos,
None, tipos incorrectos. Si no los testeaste, considera añadir un ciclo. - ¿La implementación es razonable? ¿Usa patrones estándar? ¿Es legible?
- ¿Hay algo que te haga ruido? Si algo "se ve raro" pero pasa los tests, podría ser un bug latente. Añade un test para ese caso.
En este ejemplo: los tests pasan, la regex es estándar, y los edge cases (vacío, tipo) están cubiertos. Aprobado.
Paso 5: REFACTOR (si aplica)
Puedes pedir a Claude Code: "Simplifica o mejora el código manteniendo los tests verdes." O puedes dejarlo como está si ya es suficiente.
Interacción real con Claude Code (ejemplo de prompt):
Tengo estos tests en tests/test_validators.py que actualmente fallan porque
validate_email no existe. Necesito que crees el archivo validators.py con una
función validate_email(email: str) -> bool que los haga pasar.
Requisitos:
- Acepta emails con formato usuario@dominio.tld
- Rechaza: vacío, sin @, sin dominio, sin TLD
Claude Code responde con el código. Ejecutas pytest. Si pasa, pasas a evaluar. Si falla, copias el mensaje de error y pides: "Este test falla con este error: [output]. Corrige la implementación."
Múltiples ciclos para una feature
Una feature real rara vez se hace en un solo ciclo. Construyes por capas.
Ciclo 1: validación básica (formato)
Tests:
def test_validates_correct_email():
assert validate_email("user@example.com") is True
def test_rejects_invalid_format():
assert validate_email("invalid") is False
assert validate_email("user@") is False
Implementación: Claude Code genera validación por formato (regex básica). Verde.
Ciclo 2: validación de dominio
Tests adicionales:
def test_rejects_invalid_domain():
assert validate_email("user@.com") is False
assert validate_email("user@domain") is False # sin TLD
def test_accepts_common_tlds():
assert validate_email("user@example.com") is True
assert validate_email("user@example.org") is True
Implementación: Claude Code refina la regex o añade validación de dominio. Verde.
Código resultante típico tras ciclo 2:
# validators.py — después de ciclo 2
import re
def validate_email(email: str) -> bool:
if not email or not isinstance(email, str):
return False
# Asegura que hay dominio y TLD (al menos 2 chars)
pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if not re.fullmatch(pattern, email):
return False
# Rechaza user@.com (dominio vacío) y user@domain (sin TLD)
local, domain = email.rsplit("@", 1)
if not domain or "." not in domain or len(domain.split(".")[-1]) < 2:
return False
return True
Ciclo 3: edge cases (unicode, longitud)
Tests adicionales:
def test_rejects_unicode_in_local_part():
# Según política: rechazar o aceptar
assert validate_email("usér@example.com") is False # ejemplo estricto
def test_rejects_email_too_long():
long_email = "a" * 254 + "@b.com" # > 254 chars
assert validate_email(long_email) is False
Implementación: Claude Code añade chequeos de longitud y caracteres. Verde.
Nota: El test de unicode es una decisión de política. Algunos sistemas aceptan usér@example.com (RFC 6531). Si tu dominio requiere ASCII estricto, el test tiene sentido. Si no, podrías eliminar ese test o cambiarlo a is True. Eso es spec refinement en acción.
Resumen de ciclos
Ciclo 1: Formato básico → test_validates_correct_email, test_rejects_invalid_format
Ciclo 2: Dominio → test_rejects_invalid_domain, test_accepts_common_tlds
Ciclo 3: Edge cases → test_rejects_unicode, test_rejects_email_too_long
Cada ciclo parte del anterior. Los tests se acumulan; la implementación evoluciona.
Tu nuevo rol: arquitecto, evaluador, refiner
Arquitecto: defines QUÉ hace el sistema
Tú decides el comportamiento. Los tests son tu especificación. No implementas tú, pero sí defines qué debe pasar. Si no escribes un test para un edge case, es probable que Claude Code no lo maneje.
Ejemplo: Si nunca escribes test_rejects_email_with_spaces, Claude Code podría devolver True para "user @example.com". No es que Claude Code sea tonto — es que no tiene forma de saber que ese caso importa. El test es la única forma de comunicarlo.
Lista de responsabilidades como arquitecto:
- ✅ Decidir qué casos de éxito y fracaso cubrir
- ✅ Definir la interfaz (nombres de funciones, parámetros, tipos)
- ✅ Priorizar: qué ciclo va primero, qué edge cases son críticos
- ❌ No implementar — eso es trabajo de Claude Code
Evaluador: juzgas si es correcto Y bueno
Cuando Claude Code genera código, tu trabajo es:
- ¿Pasa los tests? Objetivo.
pytestte lo dice. - ¿Es correcto? ¿Cubre los casos que imaginaste? ¿Hay lógica que "funciona por casualidad"?
- ¿Es bueno? ¿Legible? ¿Mantenible? ¿Nombres claros? ¿Usa bibliotecas estándar en vez de reinventar la rueda?
No basta con que pase. Debe ser código que quieras mantener. Si Claude Code implementa un regex de 500 caracteres cuando email-validator existe, puedes pedir un refactor: "Usa la biblioteca estándar o email-validator en vez de regex manual."
Lista de responsabilidades como evaluador:
- ✅ Ejecutar tests y verificar que pasen
- ✅ Revisar edge cases no cubiertos por tests
- ✅ Juzgar calidad: legibilidad, mantenibilidad, dependencias
- ❌ No aceptar código "porque viene de AI" — el estándar es el mismo que para código humano
Refiner: mejoras tests e implementación iterativamente
Descubres que tu spec estaba incompleta. Añades tests. Pides a Claude Code que adapte la implementación. O viceversa: la implementación sugiere que el test era demasiado estricto — ajustas el test. El refinamiento es continuo.
Ejemplo de spec refinement: Escribiste test_rejects_invalid_email asumiendo que "user@.com" debía ser rechazado. Claude Code implementa y pasa. Pero luego notas que "user@.com" en realidad es un dominio válido en algunos contextos (ej. .com como TLD con subdominio vacío). Tienes dos opciones: (1) ajustar el test para aceptar ese caso, o (2) mantener el test y documentar que tu política es rechazarlo. En ambos casos, refinaste la spec basándote en lo que aprendiste durante el ciclo.
NO eres espectador
No te limitas a aprobar todo. Activamente guías:
- Escribes tests claros.
- Das contexto suficiente a Claude Code.
- Decides cuándo iterar y cuándo intervenir.
- Refinas la spec cuando hace falta.
Práctica: Estructura de proyecto mínima
Para replicar el ciclo en tu máquina, usa esta estructura:
mi_proyecto/
├── validators.py # Claude Code creará/modificará este archivo
├── tests/
│ └── test_validators.py # Tú escribes los tests primero
├── pyproject.toml # o requirements.txt con pytest
Setup mínimo:
# Crear estructura
mkdir -p tests
touch validators.py tests/test_validators.py
# Instalar pytest si no lo tienes
pip install pytest
Luego: escribe los tests en test_validators.py, ejecuta pytest, verás RED. Pasa el archivo y el prompt a Claude Code, obtendrás GREEN.
Código completo para el primer ciclo
Archivo tests/test_validators.py (tú lo escribes — RED):
# tests/test_validators.py
"""Tests para validate_email. Ejecutar con: pytest tests/test_validators.py -v"""
def test_validates_correct_email():
from validators import validate_email
assert validate_email("user@example.com") is True
assert validate_email("admin@company.co.uk") is True
def test_rejects_invalid_email():
from validators import validate_email
assert validate_email("invalid") is False
assert validate_email("user@") is False
assert validate_email("@domain.com") is False
assert validate_email("") is False
Archivo validators.py (Claude Code lo genera — GREEN):
# validators.py
"""Validador de emails. Generado para pasar tests/test_validators.py"""
import re
def validate_email(email: str) -> bool:
"""Valida formato de email. Retorna True si es válido, False en caso contrario."""
if not email or not isinstance(email, str):
return False
pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return bool(re.fullmatch(pattern, email))
Comandos para verificar:
# 1. Sin validators.py: RED (ModuleNotFoundError)
pytest tests/test_validators.py -v
# 2. Con validators.py creado por Claude Code: GREEN
pytest tests/test_validators.py -v
# Deberías ver: 2 passed
Comparación: TDD clásico vs TDD agentic
| Aspecto | TDD clásico | TDD agentic |
|---|---|---|
| Quién escribe el test | Tú | Tú |
| Quién implementa | Tú | Claude Code |
| Quién refactoriza | Tú | Colaborativo (Claude propone, tú apruebas) |
| Tiempo RED | ~2 min | ~5 min (tests más pensados) |
| Tiempo GREEN | ~10-30 min | ~30 seg |
| Tiempo REFACTOR | ~5 min | ~2 min |
| Bottleneck | Escribir código | Calidad tests + juicio |
| Skills clave | Código, diseño, tests | Diseño de tests, evaluación, criterio |
| Rol principal | Coder | Arquitecto + evaluador |
Conclusión: En TDD agentic, tu valor no está en escribir más código — está en diseñar mejores specs (tests) y en tener el criterio para evaluar si lo que Claude Code produce cumple con tu estándar. El código es commodity; el juicio es el diferencial.
Heurísticas para cuándo intervenir vs dejar iterar
Cuando Claude Code falla un test, tienes que decidir: ¿le das otra oportunidad con más contexto, o intervienes tú?
| Situación | Acción recomendada |
|---|---|
| Fallo claro: el test es explícito y el error obvio | Pide a Claude Code que corrija; incluye el output de pytest |
| Fallo repetido: Claude Code comete el mismo error 2+ veces | Intervienes tú o cambias el enfoque del prompt |
| El test podría ser incorrecto | Revisa la spec; ajusta el test si corresponde |
| Varios tests fallan y hay un patrón común | Da más contexto: "El problema parece ser X. Prioriza pasar test A y B primero." |
| La implementación es correcta pero desordenada | Pide refactor manteniendo tests verdes; evalúa el resultado |
No hay regla universal. Con práctica desarrollarás intuición. La clave: no asumas que el fallo siempre es de Claude Code. A veces el test es el que está mal.
Ejercicios
Ejercicio 1: Identificar las fases (Fácil)
Lee este flujo y etiqueta cada paso como RED, GREEN o REFACTOR:
1. Escribes test_parse_date_accepts_iso_format que falla
2. Claude Code genera parse_date con datetime.strptime
3. Pytest pasa
4. Pides a Claude Code que extraiga la validación a una función auxiliar
5. Los tests siguen pasando
Ver solución
- Paso 1: RED — Escribes un test que falla.
- Paso 2: GREEN — Claude Code implementa lo mínimo para pasar.
- Paso 3: GREEN — Verificas que los tests pasan.
- Paso 4: REFACTOR — Mejoras la estructura sin cambiar comportamiento.
- Paso 5: REFACTOR — Validas que el refactor no rompió nada.
Ejercicio 2: Escribir tests para el ciclo 1 (Medio)
Quieres una función is_strong_password(password: str) -> bool que retorne True si la contraseña tiene al menos 8 caracteres, una mayúscula y un número. Escribe 4 tests para el primer ciclo (RED) que definan este comportamiento.
Ver solución
# tests/test_password_validator.py
import pytest
def test_accepts_valid_password():
from validators import is_strong_password
assert is_strong_password("SecurePass1") is True
assert is_strong_password("MyP4ssw0rd") is True
def test_rejects_short_password():
from validators import is_strong_password
assert is_strong_password("Short1") is False
def test_rejects_without_uppercase():
from validators import is_strong_password
assert is_strong_password("alllowercase1") is False
def test_rejects_without_number():
from validators import is_strong_password
assert is_strong_password("NoNumbersHere") is False
Estos 4 tests definen el contrato mínimo. Cada uno prueba un criterio independiente.
Ejercicio 3: Segundo ciclo para la misma feature (Medio)
Para is_strong_password, el ciclo 1 cubrió longitud, mayúscula y número. En el ciclo 2 quieres añadir: al menos un carácter especial (!@#$%^&*). Escribe 2 tests nuevos para ese ciclo.
Ver solución
def test_accepts_password_with_special_char():
from validators import is_strong_password
assert is_strong_password("SecureP4ss!") is True
assert is_strong_password("Pass@word1") is True
def test_rejects_without_special_char():
from validators import is_strong_password
assert is_strong_password("SecurePass1") is False # ahora requiere especial
Nota: Si en ciclo 1 SecurePass1 era válida, en ciclo 2 cambia la spec: ahora debe tener carácter especial. El test test_accepts_valid_password del ciclo 1 podría necesitar ajuste — o podrías decidir que la spec evoluciona y los tests anteriores se actualizan. Eso es spec refinement.
Ejercicio 4: Comparar tiempos (Fácil)
En TDD clásico, un ciclo para una función simple toma ~15 min (2 RED + 10 GREEN + 3 REFACTOR). En TDD agentic, toma ~8 min (4 RED + 0.5 GREEN + 2 EVALUAR + 1.5 REFACTOR). ¿Cuántos ciclos completas en 1 hora con cada enfoque? ¿Qué implica para la velocidad de desarrollo?
Ver solución
TDD clásico: 60 min / 15 min ≈ 4 ciclos/hora
TDD agentic: 60 min / 8 min ≈ 7.5 ciclos/hora
Con TDD agentic haces casi el doble de ciclos en el mismo tiempo. La ganancia no es solo velocidad: más ciclos significan feedback más frecuente y menos código acumulado antes de validar.
Ejercicio 5: Cuándo intervenir (Difícil)
Claude Code implementa validate_email y tu test test_rejects_empty_string falla: la función retorna True para "". ¿Cuándo intervienes tú mismo vs cuándo pides a Claude Code que lo arregle? Escribe un criterio de decisión en 3-4 puntos.
Ver solución
Intervienes tú cuando:
- El fix es trivial (1-2 líneas) y sabes exactamente qué cambiar
- Quieres practicar y entender el código
- Es un proyecto de aprendizaje y quieres tocar el código
Pides a Claude Code que lo arregle cuando:
- El fix no es obvio o requiere más contexto del que ya tienes
- Prefieres mantener el flujo "test → Claude implementa"
- Hay varios fallos y quieres que Claude Code los resuelva en bloque
Criterio general: Si el test es claro y el fallo es "la implementación no cumple la spec", Claude Code suele poder corregir. Si el fallo indica que el test era ambiguo o incorrecto, ajusta el test tú primero.
Ejercicio 6: Prompt efectivo (Medio)
Tienes estos tests escritos para format_currency(amount: float, currency: str) -> str:
def test_formats_usd():
assert format_currency(99.99, "USD") == "$99.99"
def test_formats_eur():
assert format_currency(99.99, "EUR") == "99,99 €"
Escribe un prompt que le darías a Claude Code para implementar la función. Incluye: archivo de tests, ubicación del módulo, y cualquier constraint (por ejemplo, locale fijo).
Ver solución
Implementa la función format_currency(amount: float, currency: str) -> str en el módulo formatters.py.
Debe pasar todos los tests en tests/test_formatters.py.
Comportamiento esperado:
- USD: prefijo $, punto como separador decimal (ej: "$99.99")
- EUR: sufijo €, coma como separador decimal (ej: "99,99 €")
Usa el módulo estándar de Python para formateo (locale o decimal).
Si usas locale, configura "en_US" para USD y "de_DE" o similar para EUR para mantener consistencia con los tests.
Un buen prompt incluye: ubicación del código, referencia a los tests, y ejemplos concretos de salida.
Troubleshooting
Problema 1: Claude Code implementa algo que pasa el test pero no es lo que querías
Síntoma: Los tests pasan, pero el comportamiento en casos reales es incorrecto.
Causa: El test era demasiado débil o ambiguo. Por ejemplo, solo testeabas un caso feliz.
Ejemplo: Pediste validate_email y testeaste solo "user@example.com". Claude Code implementó return "@" in email — técnicamente pasa, pero acepta "@" o "a@b" como válidos.
Solución: Refuerza el test. Añade casos que capturen el comportamiento que esperabas: assert validate_email("a@b") is False, assert validate_email("@") is False. Ejecuta de nuevo. Si el nuevo test falla, pide a Claude Code que actualice la implementación para pasar también ese test.
Problema 2: Claude Code genera código que falla múltiples tests a la vez
Síntoma: Tras generar la implementación, varios tests fallan.
Causa: El prompt era poco claro, había demasiados requisitos a la vez, o los tests tienen dependencias ocultas.
Solución:
- Reduce el alcance: pide implementar solo para 1-2 tests primero.
- Dale el archivo de tests completo y el mensaje de error de pytest.
- Si un test depende de otro (por ejemplo, fixtures), acláralo en el prompt.
Problema 3: No sabes si el problema es el test o la implementación
Síntoma: Un test falla y no está claro si el test es incorrecto o la implementación.
Causa: Spec incompleta o ambigüedad en los requisitos.
Solución:
- Revisa el requisito original. ¿El test lo refleja bien?
- Pregunta: "Si un humano implementara esto correctamente, ¿pasaría mi test?" Si la respuesta es no, corrige el test.
- En caso de duda, pide a Claude Code: "Este test falla. ¿El comportamiento esperado en el test es correcto según los requisitos típicos de [dominio]?"
Problema 4: Los ciclos se hacen demasiado largos
Síntoma: Cada ciclo toma 20+ minutos porque los tests son enormes o el prompt es muy amplio.
Causa: Tests que validan demasiado a la vez o prompts sin foco.
Solución:
- Un test, un propósito. Divide en tests más pequeños.
- Ciclo 1: solo el caso más básico. Ciclo 2: un edge case. Ciclo 3: otro.
- En el prompt, indica explícitamente: "Implementa solo para pasar estos 2 tests por ahora."
Problema 5: Claude Code refactoriza y rompe tests
Síntoma: Tras un refactor propuesto por Claude Code, algún test falla.
Causa: El refactor cambió comportamiento sin querer, o el test dependía de detalles de implementación.
Solución:
- No aceptes refactors sin ejecutar los tests antes.
- Si falla: revierte el refactor o pide a Claude Code que lo ajuste manteniendo los tests verdes.
- Si el test fallaba por depender de implementación (por ejemplo, orden de llamadas internas), ajusta el test para que valide comportamiento, no implementación.
Resumen rápido de troubleshooting:
| Síntoma | Causa probable | Acción |
|---|---|---|
| Pasa el test pero comportamiento incorrecto | Test débil | Refuerza tests, añade casos |
| Varios tests fallan a la vez | Prompt amplio o tests complejos | Reduce alcance, 1-2 tests por ciclo |
| No sabes si el error es test o implementación | Spec ambigua | Revisa requisitos, pide segundo criterio |
| Ciclos muy largos | Tests gigantes | Divide: un test, un propósito |
| Refactor rompe tests | Cambio de comportamiento o test frágil | Revertir o ajustar test |
| Claude Code repite el mismo error | Falta de contexto o test ambiguo | Intervén manualmente o reformula el test |
Conexión con Proyecto
El proyecto de este módulo es construir un sistema de autenticación con TDD. El ciclo red-green-refactor con AI que practicaste aquí es exactamente el que usarás:
Ciclo 1: test_register_validates_email → Claude implementa validación de email
Ciclo 2: test_register_hashes_password → Claude implementa hashing
Ciclo 3: test_register_creates_user → Claude implementa creación en DB
Ciclo 4: test_login_returns_token → Claude implementa login
Ciclo 5: test_token_validates_correctly → Claude implementa validación JWT
...
Cada feature se construye con múltiples ciclos pequeños. Tu rol será diseñar tests claros, dar contexto a Claude Code, y evaluar cada implementación antes de seguir. El proyecto te permitirá practicar el ritmo real: ciclos de minutos, spec refinement cuando descubras requisitos faltantes, y el juicio de cuándo intervenir.
Resumen
Lo esencial del ciclo adaptado:
- ✅ RED no cambia: tú escribes el test que falla. Es tu especificación.
- ✅ GREEN lo hace Claude Code: implementación en ~30 segundos.
- ✅ REFACTOR es colaborativo: Claude Code propone, tú apruebas. Los tests son tu red de seguridad.
- ✅ El bottleneck pasa de "escribir código" a "calidad de tests" y "juicio para evaluar".
- ✅ Tu rol: arquitecto (defines qué), evaluador (juzgas si es correcto y bueno), refiner (mejoras iterativamente). No espectador.
- ✅ Múltiples ciclos por feature: cada ciclo añade una capa (formato → dominio → edge cases).
- ✅ Tests pequeños, un propósito por test. Ciclos de minutos, no horas.
- ✅ Anti-patrones: evitar tests gigantes, implementación sin tests, aceptar sin ejecutar pytest.
Próxima cápsula: Writing Failing Tests First — cómo escribir tests rojos efectivos que den a Claude Code la información necesaria para implementar correctamente.
Recursos Adicionales
- Kent Beck: Test-Driven Development by Example — El libro que popularizó red-green-refactor
- Martin Fowler: Refactoring — Técnicas de refactoring con tests como safety net
- Tweag: Spec-Driven Development with LLMs — Spec-first aplicado a desarrollo con LLMs
- pytest: Writing and Running Tests — Referencia para estructura de tests
- The Pragmatic Programmer: Red-Green-Refactor — Contexto del ciclo TDD
- Anthropic: Claude Code Best Practices — Cómo dar contexto efectivo a Claude Code
Módulo 4, Cápsula 02 — Testing with Claude Code Guide