Desarrollo Guiado por Pruebas
Filosofía
Principio fundamental: Las pruebas deben verificar el comportamiento a través de interfaces públicas, no los detalles de implementación. El código puede cambiar por completo; las pruebas no deberían.
Las pruebas buenas son de estilo integración: ejercitan rutas de código reales a través de APIs públicas. Describen qué hace el sistema, no cómo lo hace. Una buena prueba se lee como una especificación — "el usuario puede finalizar la compra con un carrito válido" te dice exactamente qué capacidad existe. Estas pruebas sobreviven a refactorizaciones porque no les importa la estructura interna.
Las pruebas malas están acopladas a la implementación. Simulan colaboradores internos, prueban métodos privados o verifican a través de medios externos (como consultar una base de datos directamente en lugar de usar la interfaz). La señal de alerta: tu prueba se rompe al refactorizar, pero el comportamiento no ha cambiado. Si renombras una función interna y las pruebas fallan, esas pruebas estaban probando la implementación, no el comportamiento.
Consulta tests.md para ejemplos y mocking.md para directrices sobre simulación.
Anti-patrón: Rebanadas Horizontales
NO escribas todas las pruebas primero, luego toda la implementación. Esto es "rebanadas horizontales" — tratar ROJO como "escribe todas las pruebas" y VERDE como "escribe todo el código."
Esto produce pruebas deficientes:
- Las pruebas escritas en masa prueban un comportamiento imaginado, no real
- Terminas probando la forma de las cosas (estructuras de datos, firmas de funciones) en lugar del comportamiento orientado al usuario
- Las pruebas se vuelven insensibles a cambios reales: pasan cuando el comportamiento se rompe, fallan cuando el comportamiento está bien
- Te adelantas a tu propia comprensión, comprometiéndote con la estructura de pruebas antes de entender la implementación
Enfoque correcto: Rebanadas verticales mediante balas trazadoras. Una prueba → una implementación → repetir. Cada prueba responde a lo que aprendiste del ciclo anterior. Como acabas de escribir el código, sabes exactamente qué comportamiento importa y cómo verificarlo.
INCORRECTO (horizontal):
ROJO: prueba1, prueba2, prueba3, prueba4, prueba5
VERDE: impl1, impl2, impl3, impl4, impl5
CORRECTO (vertical):
ROJO→VERDE: prueba1→impl1
ROJO→VERDE: prueba2→impl2
ROJO→VERDE: prueba3→impl3
...
Flujo de Trabajo
1. Planificación
Al explorar el código base, utiliza el glosario de dominio del proyecto para que los nombres de las pruebas y el vocabulario de la interfaz coincidan con el lenguaje del proyecto, y respeta las ADR en el área que estás tocando.
Antes de escribir cualquier código:
- Confirma con el usuario qué cambios de interfaz son necesarios
- Confirma con el usuario qué comportamientos probar (prioriza)
- Identifica oportunidades para módulos profundos (interfaz pequeña, implementación profunda)
- Diseña interfaces para probar
- Enumera los comportamientos a probar (no pasos de implementación)
- Obtén la aprobación del usuario sobre el plan
Pregunta: "¿Cómo debería ser la interfaz pública? ¿Qué comportamientos son más importantes de probar?"
No puedes probarlo todo. Confirma con el usuario exactamente qué comportamientos importan más. Enfoca el esfuerzo de las pruebas en rutas críticas y lógica compleja, no en cada posible caso límite.
2. Bala Trazadora
Escribe UNA prueba que confirme UNA cosa sobre el sistema:
ROJO: Escribe prueba para el primer comportamiento → la prueba falla
VERDE: Escribe el código mínimo para aprobarla → la prueba pasa
Esta es tu bala trazadora: demuestra que la ruta funciona de extremo a extremo.
3. Bucle Incremental
Para cada comportamiento restante:
ROJO: Escribe la siguiente prueba → falla
VERDE: Código mínimo para aprobarla → pasa
Reglas:
- Una prueba a la vez
- Solo el código suficiente para aprobar la prueba actual
- No anticipes pruebas futuras
- Mantén las pruebas enfocadas en el comportamiento observable
4. Refactorización
Después de que todas las pruebas pasen, busca candidatos de refactorización:
- Extrae duplicación
- Profundiza módulos (mueve complejidad detrás de interfaces simples)
- Aplica principios SOLID donde sea natural
- Considera lo que el nuevo código revela sobre el código existente
- Ejecuta pruebas después de cada paso de refactorización
Nunca refactorices mientras estés en ROJO. Llega a VERDE primero.
Lista de Verificación Por Ciclo
[ ] La prueba describe comportamiento, no implementación
[ ] La prueba usa solo la interfaz pública
[ ] La prueba sobreviviría a una refactorización interna
[ ] El código es mínimo para esta prueba
[ ] No se agregaron características especulativas