Módulo 6: Mocking, Fixtures y Validation Loops

Validation Loops con Claude Code

Validation Loops con Claude Code

Descripción de la cápsula

Tienes tests que definen el comportamiento. Claude Code implementa. Los tests fallan. ¿Qué pasa ahora? En TDD clásico, tú lees el error, identificas la causa, corriges, re-ejecutas, y repites hasta verde. Es manual, lento, y repetitivo. Los validation loops automáticos cambian el juego: le pides a Claude Code que no solo implemente, sino que lea los errores, corrija, y re-ejecute hasta que todos los tests pasen. Claude Code hace el ciclo fix-and-rerun por ti. Esta cápsula es la diferenciadora de todo el guide: aquí es donde agentic TDD se vuelve realmente automático.

Al final dominarás el patrón de prompt para validation loops, sabrás cuándo funcionan y cuándo fallan, y conocerás tu rol durante el ciclo: escribir specs claras, revisar el resultado, e intervenir cuando Claude se queda atascado.


Qué es un Validation Loop

El ciclo manual en TDD tradicional

En TDD clásico el flujo es:

  1. Escribes un test rojo
  2. Implementas código mínimo para que pase
  3. Ejecutas tests
  4. Si fallan: lees el error, identificas la causa, corriges manualmente, re-ejecutas, repites
  5. Si pasan → refactor → siguiente test

El paso 4 es enteramente humano. Tú lees el traceback de pytest, entiendes qué está mal, editas el código, vuelves a correr. Puede tomar 5 minutos o 30 según la complejidad. Y si hay 10 tests fallando a la vez, te pierdes en la cantidad de errores.

El ciclo automático con validation loops

Un validation loop es la automatización de ese paso 4:

  1. Escribes los tests (la spec)
  2. Le pides a Claude Code: "Implementa X para que pasen estos tests. Si algún test falla, lee el error y corrige hasta que todos pasen."
  3. Claude Code: implementa → ejecuta tests → lee los fallos → corrige → re-ejecuta → repite hasta verde
  4. Tú revisas el resultado final

Claude Code hace el fix-and-rerun por ti. No necesitas copiar errores, explicar qué significa cada traceback, ni decir "corrige otra vez". El agente itera hasta convergencia.

Diferencia clave

AspectoTDD tradicionalAgentic TDD con validation loops
Quién lee el errorTúClaude Code
Quién corrigeTúClaude Code
Quién re-ejecutaTúClaude Code
IteracionesManuales, una por unaAutomáticas, hasta verde
Tu trabajoTodo el cicloEscribir tests + revisar final

La ganancia es brutal: pasas de decenas de interacciones manuales a una sola instrucción que dispara el ciclo completo.


Cómo Funcionan los Validation Loops con Claude Code

El flujo paso a paso

Tú escribes tests (spec)
        │
        ▼
Tú escribes prompt: "Implementa X. Si fallan tests, corrige hasta verde."
        │
        ▼
Claude Code implementa
        │
        ▼
Claude Code ejecuta: pytest test_file.py -v
        │
        ├── Todos pasan ──► Claude termina. Tú revisas.
        │
        └── Algunos fallan
                │
                ▼
        Claude Code lee el output (traceback, AssertionError, valores)
                │
                ▼
        Claude Code identifica causa y corrige
                │
                ▼
        Claude Code re-ejecuta tests ──► (vuelve al chequeo: ¿todos pasan?)

Claude Code tiene acceso al output del terminal. Cuando pytest falla, ve el AssertionError, el valor esperado vs obtenido, el traceback. Con eso puede inferir qué cambiar. No necesitas pastear errores en el chat — ya los tiene.

Qué necesitas darle

  • ✅ El archivo de tests (o la ruta si tiene acceso al repo)
  • ✅ El comando exacto a ejecutar: pytest tests/test_X.py -v
  • ✅ La instrucción explícita: "Si algún test falla, lee el error, identifica la causa, corrige y re-ejecuta hasta que todos pasen"
  • ✅ Constraints opcionales: "solo stdlib", "máximo 50 líneas", "sin dependencias externas"

Qué hace Claude Code por sí solo

  • Ejecuta el comando
  • Lee stdout/stderr
  • Interpreta el fallo
  • Edita el código
  • Vuelve a ejecutar
  • Repite hasta verde (o hasta un límite de iteraciones)

Tu intervención durante el loop es mínima. Solo intervienes si ves que Claude repite el mismo error varias veces o se va por un camino equivocado.


El Patrón de Prompt para Validation Loops

Template base

Implementa [función/módulo] para que pasen los tests en [test_file].

Después de implementar, ejecuta: pytest [test_file] -v

Si algún test falla:
1. Lee el error message
2. Identifica la causa
3. Corrige la implementación
4. Re-ejecuta los tests
5. Repite hasta que todos pasen

Constraints: [list constraints]

Variantes útiles

Con archivo destino explícito:

Implementa la función validate_password en auth/validation.py 
para que pasen los tests en tests/test_validation.py.

Ejecuta: pytest tests/test_validation.py -v

Si falla algún test, lee el error, corrige y re-ejecuta hasta que todos pasen.

Constraints:
- Solo stdlib
- Sin librerías externas
- Implementación mínima

Con contexto de uso:

Implementa parse_config en config/loader.py para que pasen tests/test_config.py.
Esta función será llamada desde main.py para cargar variables de entorno.

pytest tests/test_config.py -v

Si hay fallos, itera corrigiendo hasta verde.

Constraints: solo stdlib, líneas vacías ignoradas

Errores frecuentes en el prompt

ErrorCorrección
No especificar el comando a ejecutarIncluir siempre pytest [archivo] -v o el comando exacto
No pedir el ciclo de correcciónAñadir explícitamente "Si falla, lee, corrige y re-ejecuta hasta verde"
Constraints enterrados en párrafosLista clara al final: "Constraints: A, B, C"
Test file ambiguoRuta completa: tests/test_auth.py, no "los tests"

Cuándo los Validation Loops Funcionan Bien

Los validation loops convergen rápido cuando:

  • ✅ Tests claros y específicos con nombres descriptivos (test_password_too_short_raises vs test_validation)
  • ✅ Firmas bien definidas — inputs y outputs explícitos
  • ✅ Scope razonable — una función o un módulo pequeño, no una app completa
  • ✅ Errores informativos — asserts con mensajes, valores esperados vs obtenidos visibles

Tests que ayudan

def test_password_too_short_raises():
    """Password con menos de 8 caracteres debe lanzar ValueError."""
    with pytest.raises(ValueError, match="at least 8"):
        validate_password("short")

El match="at least 8" y el docstring dan contexto. Si falla, Claude ve qué se esperaba.

def test_valid_email_returns_true():
    result = validate_email("user@example.com")
    assert result is True, f"Expected True for valid email, got {result}"

El mensaje en el assert ayuda cuando el valor es inesperado.


Cuándo los Validation Loops Fallan

Fallan o se atascan cuando:

  • ❌ Tests ambiguos — múltiples implementaciones válidas
  • ❌ Demasiados tests a la vez — Claude se pierde en contradicciones
  • ❌ Tests con estado compartido — el orden de ejecución importa
  • ❌ Lógica muy compleja con muchas piezas interactuando

Ejemplo: test ambiguo

def test_format_output():
    assert format_output("hello") == "hello"

format_output podría retornar "hello", "Hello", "HELLO", " hello ". El test no discrimina. Claude puede implementar cualquiera y "pasar", pero no es la que tú querías.

Solución: Especificar más: assert format_output("hello") == "Hello" (capitalizado) o documentar el contrato.

Ejemplo: demasiados tests

Si le das 20 tests que cubren 5 funciones diferentes, Claude puede arreglar uno y romper otro. Reduce el scope: "Implementa solo validate_password. Los tests 1-4 son para esa función."

Ejemplo: tests con estado compartido

# test_bad.py
state = []

def test_a():
    state.append(1)
    assert len(state) == 1

def test_b():
    state.append(1)  # Asume que state está vacío
    assert len(state) == 1  # Falla si test_a corrió antes

Tests que dependen del orden o de estado global confunden a Claude. Cada test debe ser independiente.


Tu Rol Durante los Validation Loops

No eres pasivo. Tu trabajo es:

1. Escribir tests claros

Garbage in, garbage out. Tests vagos producen implementaciones vagas o incorrectas. Tests específicos guían a Claude hacia la solución correcta.

2. Definir scope razonable

No pidas "implementa toda la API". Pide "implementa el endpoint POST /users para que pasen estos 5 tests". Scope pequeño = loops más cortos y convergentes.

3. Revisar el resultado

Pasar tests no garantiza buen código. Revisa:

  • ¿La implementación es correcta o hace trampa?
  • ¿Hay over-engineering?
  • ¿Cumple los constraints?

4. Intervenir cuando Claude está atascado

Si Claude repite el mismo error 3+ veces, el loop no converge. Interviene:

  • Da más contexto: "El error es que X. La causa es Y. Debes hacer Z."
  • Reduce el scope: divide en tests más pequeños
  • Sugiere un enfoque: "Usa regex para el patrón del email"

Ejemplo Real: Validation Loop en Acción

Vamos a seguir un validation loop completo para un módulo de registro de usuarios. Los tests definen la spec; Claude Code itera hasta verde.

Paso 1: Los tests

# tests/test_registration.py
import pytest
from auth.registration import register_user


def test_register_user_returns_user_id():
    """Registro exitoso retorna un ID de usuario."""
    user_id = register_user(email="alice@example.com", password="secure123")
    assert isinstance(user_id, str)
    assert len(user_id) >= 1


def test_register_user_rejects_short_password():
    """Password menor a 8 caracteres debe lanzar ValueError."""
    with pytest.raises(ValueError, match="at least 8"):
        register_user(email="bob@example.com", password="short")


def test_register_user_rejects_invalid_email():
    """Email sin @ debe lanzar ValueError."""
    with pytest.raises(ValueError, match="valid email"):
        register_user(email="notanemail", password="password123")


def test_register_user_rejects_duplicate_email():
    """Email ya registrado debe lanzar ValueError."""
    register_user(email="dup@example.com", password="password123")
    with pytest.raises(ValueError, match="already registered"):
        register_user(email="dup@example.com", password="otherpass456")


def test_register_user_stores_password_hashed():
    """El password no debe almacenarse en texto plano."""
    # Este test requiere acceso a un store interno — por ahora
    # lo dejamos como integración; para unit test mockeamos el store
    user_id = register_user(email="hash@example.com", password="secret123")
    assert user_id  # Verificación básica

Por simplicidad, en el ejemplo trabajamos con 4 tests (excluimos el 5 que requiere store). El módulo auth.registration aún no existe.

Paso 2: El prompt

Implementa register_user en auth/registration.py para que pasen los tests 
en tests/test_registration.py.

Ejecuta: pytest tests/test_registration.py -v

Si algún test falla:
1. Lee el error
2. Identifica la causa
3. Corrige la implementación
4. Re-ejecuta los tests
5. Repite hasta que todos pasen

Constraints:
- Solo stdlib
- Para test_register_user_rejects_duplicate_email necesitas un store en memoria 
  (dict) que persista entre llamadas — usa una variable a nivel de módulo por ahora
- Password hasheado con hashlib.sha256

Paso 3: Primera implementación de Claude (2 tests fallan)

Claude genera:

# auth/registration.py
import hashlib
import re

_registry: dict[str, str] = {}

def register_user(email: str, password: str) -> str:
    if len(password) < 8:
        raise ValueError("Password must be at least 8 characters")
    if "@" not in email:
        raise ValueError("Invalid email: must contain @")
    if email in _registry:
        raise ValueError("Email already registered")
    hashed = hashlib.sha256(password.encode()).hexdigest()
    user_id = hashed[:16]
    _registry[email] = hashed
    return user_id

Al ejecutar pytest tests/test_registration.py -v:

  • test_register_user_returns_user_id — ✅ Pasa
  • test_register_user_rejects_short_password — ❌ Falla: match="at least 8" no coincide con "Password must be at least 8 characters" (el mensaje está bien, pero match es un regex; "at least 8" no está en el mensaje)
  • test_register_user_rejects_invalid_email — ❌ Falla: match="valid email" no coincide con "Invalid email: must contain @"
  • test_register_user_rejects_duplicate_email — ✅ Pasa

En realidad, match en pytest hace un match de substring en el mensaje. "at least 8" no está en "Password must be at least 8" — sí está "8". Y "valid email" no está en "Invalid email: must contain @". Ajustemos: el match "at least 8" podría fallar si el mensaje dice "8 characters"; "valid" sí está en "Invalid". Depende. Supongamos que fallan por el wording.

Paso 4: Claude lee los errores y corrige

Claude ve el output de pytest:

FAILED test_registration.py::test_register_user_rejects_short_password - 
AssertionError: Regex pattern 'at least 8' does not match 'Password must be at least 8 characters'

Ajusta el mensaje para que coincida:

# Corrige a:
raise ValueError("Password must be at least 8 characters")  # ya lo tenía
# El match "at least 8" debería coincidir con "at least 8" en el mensaje
# "Password must be at least 8 characters" contiene "at least 8" — regex busca substring
# En pytest.raises, match hace re.search. "at least 8" en "Password must be at least 8 characters" → match

Si falla, puede ser que el mensaje diga "8 characters" y no "at least 8". Claude podría cambiar a:

raise ValueError("Password must be at least 8 characters")

Y para el email:

raise ValueError("Email must be a valid email address")

Claude actualiza ambos mensajes y re-ejecuta.

Paso 5: Segunda iteración (1 test aún falla)

Tras la corrección, test_register_user_rejects_short_password y test_register_user_rejects_invalid_email pasan. Pero test_register_user_rejects_duplicate_email falla porque _registry se limpia entre tests (cada test en pytest corre en un proceso; si el módulo se reimporta, el dict se reinicia). En un mismo proceso, el orden sería: primero register_user("dup@..."), luego el segundo register_user("dup@...") — ahí debería fallar.

El fallo puede ser por el orden de tests. Si test_register_user_rejects_duplicate_email corre antes de algún otro que usa el mismo email, el estado podría ser distinto. Para aislar, el test usa un email único "dup@example.com". En un run típico, el primer register_user llena _registry["dup@example.com"], y el segundo debe lanzar ValueError. Funcionaría.

Supongamos que el fallo real es otro: por ejemplo, que Claude generó user_id vacío en algún edge case. Claude revisa el error, ajusta la lógica y vuelve a ejecutar.

Paso 6: Verde — todos pasan

Tras una o dos iteraciones más, todos los tests pasan. Claude termina.

Paso 7: Revisión humana

Revisas el código:

  • ¿Valida correctamente?
  • ¿El store en memoria es aceptable para este ejemplo?
  • ¿Hay over-engineering?

Si todo está bien, el validation loop ha cumplido su objetivo: de tests rojos a verdes con intervención mínima.


Optimizar Validation Loops

Mensajes de error más útiles

Mejorar los asserts acelera el loop:

# Poco informativo
assert result == expected

# Mejor
assert result == expected, f"Expected {expected}, got {result}"

# Aún mejor para estructuras
assert result == expected, f"Mismatch: {result!r} != {expected!r}"

Claude interpreta más rápido cuando ve el valor esperado y el obtenido.

Plugins de pytest para mejor salida de fallos

Si los errores de pytest no son suficientemente claros, puedes usar plugins que mejoran el output:

# pyproject.toml o pytest.ini
# [tool.pytest.ini_options]
# addopts = "-v --tb=short"

O instalar pytest-clarity o pytest-verbose-parametrize para ver los valores de parametrize en los nombres de test. Cuando Claude lee el output, nombres como test_foo[a@b.com-longpassword-False] dan más contexto que test_foo[2]. pytest-instafail muestra los fallos inmediatamente en vez de al final, lo que acelera la depuración iterativa.

Usar assert con mensajes en tests parametrizados

@pytest.mark.parametrize("email,password,should_raise", [
    ("a@b.com", "longpassword", False),
    ("bad", "longpassword", True),
])
def test_register_parametrized(email, password, should_raise):
    if should_raise:
        with pytest.raises(ValueError):
            register_user(email, password)
    else:
        uid = register_user(email, password)
        assert uid, f"Expected non-empty user_id for {email}"

Checklist antes de lanzar un validation loop

Antes de enviar el prompt, verifica:

  • ✅ Los tests tienen nombres descriptivos (test_rejects_duplicate_email, no test_1)
  • ✅ Los asserts incluyen mensajes cuando el valor esperado no es obvio
  • ✅ El scope es acotado (una función o un módulo pequeño, no un subsistema completo)
  • ✅ Las firmas de las funciones están definidas en los tests o en el prompt
  • ✅ Los constraints están escritos explícitamente (stdlib, sin deps, etc.)
  • ✅ El comando exacto de pytest está en el prompt

Si falta algo, el loop puede alargarse o divergir.


Práctica: Validation Loop con Validación de Precios

Implementa una función validate_price que valide precios para un e-commerce. Usa el patrón de validation loop.

Tests iniciales

# tests/test_prices.py
import pytest
from shop.validation import validate_price


def test_valid_price_returns_float():
    """Precio válido retorna el valor como float."""
    result = validate_price(19.99)
    assert result == 19.99


def test_string_price_parsed():
    """String numérico debe parsearse a float."""
    assert validate_price("29.50") == 29.50


def test_negative_price_raises():
    """Precio negativo debe lanzar ValueError."""
    with pytest.raises(ValueError, match="negative"):
        validate_price(-5.0)


def test_zero_price_raises():
    """Precio cero debe lanzar ValueError."""
    with pytest.raises(ValueError, match="zero"):
        validate_price(0)


def test_invalid_string_raises():
    """String no numérico debe lanzar ValueError."""
    with pytest.raises(ValueError, match="invalid"):
        validate_price("not a number")

Crea shop/validation.py vacío o con def validate_price(price): raise NotImplementedError. Luego usa este prompt con Claude Code:

Implementa validate_price en shop/validation.py para que pasen los tests 
en tests/test_prices.py.

pytest tests/test_prices.py -v

Si falla algún test, lee el error, corrige y re-ejecuta hasta verde.

Constraints: solo stdlib

Observa cuántas iteraciones necesita Claude para llegar a verde. Si falla, revisa si los tests son ambiguos o si los mensajes de error son claros.


Ejercicios

Ejercicio 1: Escribir el prompt (Fácil)

Tienes tests/test_slugify.py con 4 tests para slugify(s: str) -> str. Escribe un prompt completo para un validation loop que implemente slugify en utils/text.py.

Ver solución
Implementa slugify(s: str) -> str en utils/text.py para que pasen los tests 
en tests/test_slugify.py.

Después de implementar, ejecuta: pytest tests/test_slugify.py -v

Si algún test falla:
1. Lee el error message
2. Identifica la causa
3. Corrige la implementación
4. Re-ejecuta los tests
5. Repite hasta que todos pasen

Constraints: solo stdlib, implementación mínima

Ejercicio 2: Mejorar mensajes de error (Medio)

Este test da poca información cuando falla:

def test_parse_date():
    assert parse_date("2024-01-15") == datetime(2024, 1, 15)

Reescríbelo con un assert que muestre valor esperado y obtenido cuando falle.

Ver solución
def test_parse_date():
    result = parse_date("2024-01-15")
    expected = datetime(2024, 1, 15)
    assert result == expected, f"Expected {expected!r}, got {result!r}"

O, si prefieres una sola línea:

def test_parse_date():
    result = parse_date("2024-01-15")
    assert result == datetime(2024, 1, 15), f"parse_date('2024-01-15') = {result!r}"

Ejercicio 3: Identificar por qué falla un loop (Medio)

Claude Code intenta un validation loop con 15 tests para un módulo de "invoice". Tras 5 iteraciones, sigue fallando 3 tests que parecen contradecirse. ¿Qué harías?

Ver solución

Probable causa: scope demasiado amplio. 15 tests para un módulo pueden implicar muchas funciones y flujos.

Acciones:

  1. Reducir scope: "Por ahora implementa solo calculate_subtotal. Los tests 1-3 son para esa función. Ignora el resto."
  2. Revisar contradicciones: Leer los 3 tests que fallan. ¿Especifican comportamientos incompatibles? Si es así, hay que corregir la spec (los tests).
  3. Dividir en bloques: Implementar función por función: primero calculate_subtotal, luego apply_discount, etc.
  4. Intervenir con contexto: Si Claude repite lo mismo, dar un prompt más directo: "El test X falla porque espera Y pero obtienes Z. La causa es que debes hacer W."

Ejercicio 4: Constraints para evitar over-engineering (Medio)

Claude Code suele generar clases, helpers y validaciones extra cuando implementa is_valid_username(s: str) -> bool. ¿Qué constraints añadirías al prompt para obtener una implementación mínima?

Ver solución

Constraints sugeridos:

  • "Implementación mínima. Una sola función, no clases."
  • "Solo el código necesario para pasar los tests. Sin validaciones que los tests no pidan."
  • "Máximo 15 líneas. Sin helpers privados innecesarios."
  • "Si el test solo verifica longitud 3-20, no agregues checks de caracteres especiales hasta que haya un test para eso."

Ejercicio 5: Validation loop completo (Difícil)

Crea desde cero un validation loop para normalize_phone(phone: str) -> str que:

  • Elimine espacios y guiones
  • Retorne solo dígitos
  • Lance ValueError para strings vacíos o sin dígitos

Escribe los tests, el prompt y ejecuta (o simula) el loop con Claude Code. ¿Cuántas iteraciones necesitas hasta verde?

Ver solución

Tests:

# tests/test_phone.py
import pytest
from utils.phone import normalize_phone


def test_normalize_removes_spaces_and_dashes():
    assert normalize_phone("123-456-7890") == "1234567890"
    assert normalize_phone("123 456 7890") == "1234567890"


def test_normalize_empty_raises():
    with pytest.raises(ValueError, match="empty"):
        normalize_phone("")


def test_normalize_no_digits_raises():
    with pytest.raises(ValueError, match="no digits"):
        normalize_phone("abcdef")

Prompt:

Implementa normalize_phone(phone: str) -> str en utils/phone.py.
Debe pasar los tests en tests/test_phone.py.

pytest tests/test_phone.py -v

Si falla, lee el error, corrige y re-ejecuta hasta verde.

Constraints: solo stdlib

Implementación de referencia:

# utils/phone.py
def normalize_phone(phone: str) -> str:
    cleaned = "".join(c for c in phone if c.isdigit())
    if not phone or not cleaned:
        raise ValueError("empty or no digits")
    return cleaned

(Ajusta el mensaje si usas "empty" y "no digits" por separado en los tests.)

Ejercicio 6: Cuándo intervenir (Medio)

Claude Code está en iteración 4 del mismo validation loop. El mismo test falla con el mismo AssertionError que en la iteración 2. ¿Qué haces?

Ver solución

Claude está atascado. No está incorporando el feedback del error correctamente.

Acciones:

  1. Intervenir con información explícita: Pega el error completo y describe: "El test espera X. Tu código retorna Y. La causa es Z. Para corregir, debes hacer W."
  2. Simplificar el test: Si el test es complejo, divídelo en dos tests más simples y pide implementar uno primero.
  3. Dar un ejemplo: "Para el input 'foo', el output debe ser 'bar'. Aquí tienes un ejemplo de implementación: [snippet]."
  4. Revisar el test: ¿El test es correcto? ¿O hay una ambigüedad que confunde a Claude? Ajusta la spec si hace falta.

Troubleshooting

Problema 1: Claude repite el mismo error varias veces

Causa: El mensaje de error no es suficientemente explícito o el test es complejo.

Solución:

  • Incluye en el prompt el output exacto de pytest y una frase: "El test X falla porque espera A pero obtienes B. Debes hacer C."
  • Simplifica el test: divídelo en casos más pequeños.
  • Da un ejemplo de input/salida esperada.

Problema 2: Los tests pasan pero el código es incorrecto

Causa: Los tests no cubren todos los escenarios o son demasiado permisivos.

Solución:

  • Usa @pytest.mark.parametrize con varios inputs para evitar hardcoding.
  • Revisa la implementación manualmente; si hace trampa, añade tests que lo detecten.
  • Añade el constraint: "No hardcodear. La implementación debe ser genérica."

Problema 3: El loop tarda demasiado (muchas iteraciones)

Causa: Scope demasiado amplio o tests conflictivos.

Solución:

  • Reduce el scope: pide implementar una sola función o un subconjunto de tests.
  • Revisa que los tests no se contradigan.
  • Verifica que el test file sea el correcto y que no haya dependencias ocultas (orden, estado global).

Problema 4: Claude ignora los constraints

Causa: Los constraints están poco visibles o son muchos.

Solución:

  • Pon los constraints al final en lista numerada.
  • Repite el más importante: "IMPORTANTE: solo stdlib, sin dependencias externas."
  • Si sigue ignorando, pide en un mensaje de seguimiento: "La implementación debe usar solo la stdlib. Elimina cualquier import de librerías externas."

Problema 5: Tests que pasan localmente pero fallan en el loop

Causa: Diferencias de entorno (versión de Python, rutas, imports) o estado que se resetea.

Solución:

  • Comprueba que el comando del prompt coincida con lo que usas localmente.
  • Evita estado global compartido entre tests; usa fixtures para setup.
  • Asegura que los imports sean correctos (por ejemplo, from auth.registration import register_user).

Conexión con Proyecto

El proyecto del módulo (Validation pipeline) requiere al menos un validation loop donde Claude Code itera hasta que los tests pasen.

Flujo sugerido:

  1. Escribes tests para un componente (por ejemplo, un validador de datos).
  2. Usas el prompt de validation loop.
  3. Claude implementa e itera hasta verde.
  4. Revisas el código generado.
  5. Repites para otros componentes.

El proyecto final (Módulo 8) se beneficia de lo mismo: specs claras en forma de tests y validation loops para automatizar la implementación de cada pieza.


Resumen

  • ✅ Un validation loop automatiza el ciclo: ejecutar tests → leer errores → corregir → re-ejecutar hasta verde.
  • ✅ En TDD tradicional ese ciclo lo haces tú; con validation loops lo hace Claude Code.
  • ✅ El patrón de prompt incluye: implementar para pasar tests, comando a ejecutar, e instrucción de iterar hasta verde.
  • ✅ Los loops funcionan mejor con tests claros, scope pequeño y errores informativos.
  • ✅ Fallan con tests ambiguos, demasiados tests a la vez o lógica muy compleja.
  • ✅ Tu rol: escribir buenos tests, definir scope, revisar el resultado e intervenir cuando Claude se atasca.
  • ✅ Optimiza con asserts que muestren esperado vs obtenido y, si hace falta, plugins de pytest para mejor salida.

Próxima cápsula: Mocking de servicios externos en la práctica — APIs HTTP, bases de datos, filesystem y variables de entorno.


Recursos Adicionales

  1. Claude Code Documentation - Uso de Claude Code como agente
  2. pytest: Good Integration Practices - Tests bien estructurados
  3. Martin Fowler: Test-Driven Development - Fundamentos de TDD
  4. pytest: Parametrize - Tests parametrizados
  5. Anthropic: Prompt Engineering - Cómo escribir prompts efectivos
  6. Claude Code Best Practices - Prácticas recomendadas para Claude Code

Módulo 6, Cápsula 04 — Testing with Claude Code Guide