Módulo 4: TDD Workflow Completo

Proyecto del Módulo: Feature Completa con TDD

Proyecto del Módulo: Feature Completa con TDD

Descripción del proyecto

Has aprendido el ciclo TDD completo con Claude Code: escribir tests rojos, dejar que Claude implemente, evaluar, refactorizar. Ahora vas a experimentar el workflow real construyendo una feature completa de principio a fin: un sistema de autenticación con registro, login, y validación de tokens.

Cada aspecto del sistema empieza como un test que falla. Claude Code implementa. Evalúas. Refactorizas. Pasas al siguiente test. Al final, tendrás un sistema funcional construido ciclo por ciclo — y habrás experimentado spec refinement, errores de Claude Code, y el juicio de cuándo intervenir.

La clave no es el código final — es el proceso. Vas a documentar cada ciclo TDD para internalizar el ritmo.


Objetivo del Proyecto

Construir un sistema de autenticación completo usando TDD con Claude Code, experimentando múltiples ciclos red-green-refactor consecutivos.

Al completar este proyecto:

  • ✅ Habrás ejecutado 8+ ciclos TDD completos
  • ✅ Habrás experimentado spec refinement (ajustar tests al descubrir gaps)
  • ✅ Habrás manejado situaciones donde Claude Code falla y necesita más contexto
  • ✅ Habrás refactorizado código con tests como safety net
  • ✅ Tendrás un sistema de autenticación funcional construido test-first

Especificaciones Técnicas

Stack Tecnológico

  • Lenguaje: Python 3.10+
  • Testing: pytest
  • Hashing: hashlib (stdlib) — no dependencias externas para simplificar
  • Tokens: secrets + json (simulación simple de JWT sin dependencias)
  • AI: Claude Code

Setup Inicial

mkdir auth-tdd-project
cd auth-tdd-project
python -m venv venv
source venv/bin/activate
pip install pytest

Estructura del Proyecto

auth-tdd-project/
├── auth/
│   ├── __init__.py
│   ├── validators.py     ← Validación de email y password (tú creas con TDD)
│   ├── hasher.py          ← Hashing de passwords (tú creas con TDD)
│   ├── token_manager.py   ← Generación y validación de tokens (tú creas con TDD)
│   └── auth_service.py    ← Servicio de autenticación (tú creas con TDD)
├── tests/
│   ├── __init__.py
│   ├── test_validators.py
│   ├── test_hasher.py
│   ├── test_token_manager.py
│   └── test_auth_service.py
└── requirements.txt

Los Ciclos TDD

Fase 1: Validadores (Ciclos 1-3)

Ciclo 1: Validación de email

# tests/test_validators.py — Escribe PRIMERO

def test_valid_email_passes():
    assert validate_email("user@example.com") is True

def test_email_without_at_fails():
    assert validate_email("userexample.com") is False

def test_email_without_domain_fails():
    assert validate_email("user@") is False

def test_empty_email_fails():
    assert validate_email("") is False

Tu turno:

  1. Escribe estos tests
  2. Ejecuta pytest tests/test_validators.py -v — todo rojo
  3. Prompt a Claude Code: "Implementa validate_email en auth/validators.py para que pasen estos tests."
  4. Evalúa la implementación
  5. Refactoriza si es necesario

Ciclo 2: Validación de password

def test_password_too_short_fails():
    assert validate_password("Ab1!") is False

def test_password_without_uppercase_fails():
    assert validate_password("abcdef1!") is False

def test_password_without_number_fails():
    assert validate_password("Abcdef!!") is False

def test_valid_password_passes():
    assert validate_password("Abcdef1!") is True

Ciclo 3: Mensajes de error específicos

def test_validate_password_returns_errors():
    result = validate_password_detailed("abc")
    assert not result["is_valid"]
    assert "At least 8 characters" in result["errors"]
    assert "At least one uppercase" in result["errors"]
    assert "At least one number" in result["errors"]
    assert "At least one special character" in result["errors"]

Spec refinement: En el Ciclo 2, validate_password retorna bool. Ahora en el Ciclo 3, necesitas una función diferente que retorne detalles. Esto es spec refinement — descubres durante el proceso que necesitas más granularidad.

Fase 2: Password Hashing (Ciclos 4-5)

Ciclo 4: Hash básico

# tests/test_hasher.py

def test_hash_password_returns_string():
    hashed = hash_password("MyPassword1!")
    assert isinstance(hashed, str)
    assert hashed != "MyPassword1!"

def test_hash_password_is_deterministic_with_same_salt():
    hashed1 = hash_password("MyPassword1!", salt="fixed-salt")
    hashed2 = hash_password("MyPassword1!", salt="fixed-salt")
    assert hashed1 == hashed2

def test_different_passwords_produce_different_hashes():
    hash1 = hash_password("Password1!")
    hash2 = hash_password("Password2!")
    assert hash1 != hash2

Ciclo 5: Verificación de password

def test_verify_password_correct():
    hashed = hash_password("MyPassword1!")
    assert verify_password("MyPassword1!", hashed) is True

def test_verify_password_incorrect():
    hashed = hash_password("MyPassword1!")
    assert verify_password("WrongPassword", hashed) is False

Fase 3: Token Manager (Ciclos 6-7)

Ciclo 6: Generación de tokens

# tests/test_token_manager.py
import json

def test_generate_token_returns_string():
    token = generate_token(user_id=1, email="user@test.com")
    assert isinstance(token, str)
    assert len(token) > 0

def test_generate_token_contains_user_data():
    token = generate_token(user_id=42, email="test@example.com")
    decoded = decode_token(token)
    assert decoded["user_id"] == 42
    assert decoded["email"] == "test@example.com"

def test_generate_token_has_expiration():
    token = generate_token(user_id=1, email="test@test.com")
    decoded = decode_token(token)
    assert "exp" in decoded

Ciclo 7: Validación de tokens

def test_validate_valid_token():
    token = generate_token(user_id=1, email="user@test.com")
    assert validate_token(token) is True

def test_validate_tampered_token():
    token = generate_token(user_id=1, email="user@test.com")
    tampered = token[:-5] + "xxxxx"
    assert validate_token(tampered) is False

def test_validate_expired_token():
    token = generate_token(user_id=1, email="user@test.com", expires_in=-1)
    assert validate_token(token) is False

Fase 4: Auth Service (Ciclos 8-10)

Ciclo 8: Registro

# tests/test_auth_service.py

def test_register_creates_user():
    service = AuthService()
    user = service.register("user@test.com", "ValidPass1!")
    assert user["email"] == "user@test.com"
    assert "id" in user
    assert "password" not in user  # Never return password

def test_register_duplicate_email_raises():
    service = AuthService()
    service.register("user@test.com", "ValidPass1!")
    with pytest.raises(ValueError, match="already registered"):
        service.register("user@test.com", "AnotherPass1!")

def test_register_invalid_email_raises():
    service = AuthService()
    with pytest.raises(ValueError, match="Invalid email"):
        service.register("not-an-email", "ValidPass1!")

Ciclo 9: Login

def test_login_returns_token():
    service = AuthService()
    service.register("user@test.com", "ValidPass1!")
    result = service.login("user@test.com", "ValidPass1!")
    assert "token" in result
    assert isinstance(result["token"], str)

def test_login_wrong_password_raises():
    service = AuthService()
    service.register("user@test.com", "ValidPass1!")
    with pytest.raises(ValueError, match="Invalid credentials"):
        service.login("user@test.com", "WrongPass1!")

def test_login_nonexistent_user_raises():
    service = AuthService()
    with pytest.raises(ValueError, match="Invalid credentials"):
        service.login("noone@test.com", "SomePass1!")

Ciclo 10: Validación de acceso

def test_validate_access_with_valid_token():
    service = AuthService()
    service.register("user@test.com", "ValidPass1!")
    result = service.login("user@test.com", "ValidPass1!")
    user = service.validate_access(result["token"])
    assert user["email"] == "user@test.com"

def test_validate_access_with_invalid_token_raises():
    service = AuthService()
    with pytest.raises(ValueError, match="Invalid token"):
        service.validate_access("fake-token")

Proceso Paso a Paso

Para cada ciclo:

  1. Escribe los tests (RED)

    pytest tests/test_[module].py -v
    # → FAILED (rojo ✅ — esto es lo esperado)
  2. Prompt a Claude Code (GREEN)

    Implementa [función/clase] en auth/[module].py para que pasen 
    estos tests: [pega los tests]. 
    No uses dependencias externas. 
    Usa solo la stdlib de Python.
    
  3. Evalúa la implementación

    pytest tests/test_[module].py -v
    # → ¿Todo verde? Evalúa la calidad del código
    # → ¿Algo rojo? Aplica el framework de decisión
  4. Refactoriza (REFACTOR)

    Refactoriza [función] para mejorar legibilidad.
    Los tests deben seguir pasando.
    
  5. Documenta el ciclo — Anota:

    • ¿Claude Code pasó los tests al primer intento?
    • ¿Necesitaste dar más contexto?
    • ¿Encontraste spec refinement?
    • ¿Refactorizaste algo?

Criterios de Éxito

Tu proyecto está completo cuando:

  • ✅ pytest tests/ -v → todos green
  • ✅ 25+ tests en total (validadores + hasher + tokens + auth service)
  • ✅ 8+ ciclos TDD documentados
  • ✅ Al menos 1 caso de spec refinement documentado
  • ✅ Al menos 1 caso donde Claude Code necesitó más contexto
  • ✅ Al menos 1 refactoring documentado

Rúbrica de Evaluación (100 puntos)

Tests y Funcionalidad (40 puntos)

  • (15 pts) Todos los tests pasan
  • (10 pts) Cobertura de happy path, edge cases, y error handling
  • (10 pts) Tests independientes y descriptivos
  • (5 pts) 25+ tests en total

Proceso TDD (35 puntos)

  • (15 pts) Evidencia de ciclos red→green→refactor (documentación)
  • (10 pts) Al menos 1 spec refinement documentado
  • (10 pts) Al menos 1 caso de intervención/iteración con Claude Code

Calidad del Código (15 puntos)

  • (5 pts) Código limpio y legible
  • (5 pts) Type hints en las funciones principales
  • (5 pts) Funciones pequeñas y enfocadas (resultado de refactoring)

Organización (10 puntos)

  • (5 pts) Estructura de archivos correcta
  • (5 pts) Imports correctos y sin dependencias externas

Extra Credit (hasta +10 puntos)

  • (+5 pts) E2E test que valida register → login → validate_access
  • (+5 pts) Test parametrize que valide múltiples formatos de email

Errores Comunes

Error 1: Escribir todos los tests de una vez

Causa: Escribir 30 tests antes de cualquier implementación.

Solución: Trabajo por fases. Implementa una fase completa antes de empezar la siguiente. Dentro de cada fase, 2-4 tests por ciclo es el sweet spot.

Error 2: No documentar los ciclos

Causa: Solo escribir código sin registrar el proceso.

Solución: Después de cada ciclo, escribe una línea: "Ciclo N: [test] → Claude pasó/falló → [acción] → verde." La documentación es parte del entregable.

Error 3: Tests que verifican implementación, no comportamiento

Causa: assert hasher.algorithm == "sha256" verifica implementación interna.

Solución: assert verify_password("pass", hashed) is True verifica comportamiento. Testea QUÉ hace, no CÓMO lo hace.

Error 4: Saltarse el refactoring

Causa: "Los tests pasan, terminé."

Solución: El refactoring es parte del ciclo. Después de verde, siempre pregúntate: "¿Puedo mejorar este código sin romper tests?"

Error 5: Confundir spec refinement con test incorrecto

Causa: Un test falla y lo borras en vez de analizar por qué.

Solución: Si un test falla, pregúntate: "¿Mi spec está incompleta (agregar más tests) o incorrecta (modificar el test)?" Borrar tests sin reflexión pierde información.


Recursos para el Proyecto

  1. hashlib Documentation - Hashing con stdlib
  2. secrets Module - Generación de tokens seguros
  3. pytest Documentation - Referencia de pytest
  4. Kent Beck: TDD by Example - El libro de referencia
  5. Martin Fowler: Refactoring - Catálogo de refactorings

Conexión con Siguiente Módulo

Lo que construiste hoy se expande en los siguientes módulos:

  • Módulo 5 (Coverage): Medirás la cobertura de tu sistema de auth y descubrirás los tests que no escribiste
  • Módulo 6 (Mocking): Aprenderás a mockear servicios externos si tu auth necesita verificar email via API
  • Módulo 8 (Proyecto Final): Cada feature de la app final se construye con este mismo workflow, pero a mayor escala

Completaste tu primera feature real con TDD. El workflow que experimentaste aquí es el mismo que usarás profesionalmente — solo cambia la escala.


Módulo 4, Cápsula 06 — Testing with Claude Code Guide Tu primera feature completa construida con TDD agentic