¿Qué hace Desarrollo Guiado por Pruebas (TDD)?
Principio fundamental
Un cambio de comportamiento solo genera confianza cuando una prueba falló primero por la razón correcta y luego pasó debido a la implementación.
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 buenas pruebas 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 las refactorizaciones porque no les importa la estructura interna.
Las malas pruebas están acopladas a la implementación. Simulan colaboradores internos, prueban métodos privados, afirman conteos de llamadas o verifican a través de medios externos en lugar de la interfaz pública. Señal de advertencia: la prueba se rompe después de una refactorización aunque el comportamiento se haya mantenido igual.
Ver tests.md, mocking.md y testing-anti-patterns.md.
Regla de hierro
Ningún cambio de comportamiento de producción propio sin una prueba fallida primero.
Significado:
- Escribe una prueba para un comportamiento.
- Ejecútala y obserbala fallar.
- Confirma que el fallo demuestra el comportamiento faltante, no un error tipográfico o de configuración.
- Escribe el cambio de producción más pequeño que haga que pase.
- Refactoriza solo después de que esté en verde.
Si el código de implementación ya existe antes de una prueba:
- No elimines el trabajo del usuario sin permiso.
- Trátalo como un prototipo/referencia, no como una implementación validada.
- Prefiere dejarlo a un lado, escribir la prueba fallida y luego implementar desde la prueba.
- Si es necesario conservar el código existente, agrega primero una prueba de regresión/caracterización, luego cambia el comportamiento con una nueva prueba fallida.
Antipatrón: rebanadas horizontales
No escribas todas las pruebas primero y luego toda la implementación. Eso es una rebanada horizontal.
Incorrecto:
RED: test1, test2, test3, test4
GREEN: impl1, impl2, impl3, impl4
Correcto:
ROJO -> VERDE -> REFACTORIZAR: comportamiento 1
ROJO -> VERDE -> REFACTORIZAR: comportamiento 2
ROJO -> VERDE -> REFACTORIZAR: comportamiento 3
Por qué: las pruebas en lote codifican un diseño imaginado, adelantan la retroalimentación y a menudo prueban formas en lugar de comportamientos.
Flujo de trabajo
1. Planificar el comportamiento
Antes de escribir código:
- Confirma los cambios en la interfaz pública con el usuario cuando no sean obvios.
- Enumera los comportamientos a probar, no los pasos de implementación.
- Prioriza las rutas críticas y la lógica compleja; no persigas una integridad falsa.
- Identifica oportunidades para módulos profundos.
- Diseña interfaces para capacidad de prueba.
- Elige el primer comportamiento de "bala trazadora".
Pregunta: "¿Qué comportamiento público debe existir? ¿Qué comportamiento importa primero?"
2. ROJO: una prueba fallida
Escribe una prueba mínima para un comportamiento observable.
Requisitos:
- Utiliza solo la interfaz pública.
- El nombre describe el comportamiento.
- Prueba código real cuando sea práctico.
- Simula solo en los límites del sistema o en costuras inevitables.
- Falla antes de la implementación.
Ejecuta el comando útil más específico, por ejemplo:
npm test ruta/a/la/prueba.test.ts
Verifica el fallo:
- La prueba falla, no hay errores de configuración o tipográficos.
- La razón del fallo coincide con el comportamiento faltante.
- Si pasa inmediatamente, la prueba es incorrecta o el comportamiento ya existe. Corrige la prueba o elige el siguiente comportamiento faltante.
3. VERDE: implementación mínima
Escribe solo el código suficiente para que pase la prueba actual.
No hagas:
- Agregar características especulativas.
- Refactorizar código no relacionado.
- Agregar opciones futuras.
- Ampliar el alcance porque "es fácil mientras estás aquí".
Ejecuta:
npm test ruta/a/la/prueba.test.ts
Luego ejecuta las pruebas circundantes relevantes. La salida debe estar limpia: sin nuevas advertencias, falls intermitentes o errores ocultos.
4. REFACTORIZAR: limpiar mientras está en verde
Refactoriza solo después de que las pruebas pasen. Si no hay ninguna refactorización útil, dilo brevemente y continúa.
Busca candidatos a refactorización:
- duplicación
- nombres pobres
- módulos superficiales
- límites confusos
- complejidad que se puede mover detrás de una interfaz pequeña
Reglas:
- El comportamiento y las expectativas no cambian.
- Las pruebas permanecen en verde después de cada paso de refactorización.
- Las refactorizaciones de pruebas pueden renombrar, deduplicar, extraer fixtures/constructores o mejorar los mensajes de error.
- Si una aserción cambia el comportamiento esperado, detente: ese es un nuevo ciclo ROJO.
5. Repetir verticalmente
Elige el siguiente comportamiento basado en lo que te enseñó el último ciclo. Un comportamiento a la vez.
Lista de verificación por ciclo
[ ] La prueba describe el comportamiento observable, no la implementación
[ ] La prueba usa solo la interfaz pública
[ ] La prueba falló antes de la implementación
[ ] La razón del fallo fue correcta
[ ] El código es mínimo para esta prueba
[ ] No se agregó comportamiento especulativo
[ ] Las pruebas relevantes pasan limpiamente
[ ] La refactorización se consideró solo mientras estaba en verde
Puertas de confirmación (commit gates)
Para trabajo auditable, confirma las fases por separado cuando sea práctico:
- Confirmación ROJA: solo prueba o andamio mínimo más aserción fallida. El cuerpo registra el comando fallido y la razón.
- Confirmación VERDE: implementación mínima. El cuerpo registra la prueba objetivo que pasa y la refactorización considerada.
- Confirmación de REFACTORIZACIÓN: sin cambios de comportamiento o expectativas. El cuerpo indica qué cambió y qué permaneció igual.
No incluyas atribución de IA en las confirmaciones.
Pruebas de contrato de CLI
Para herramientas CLI, la superficie de comandos es el contrato del producto. Prefiere pruebas de afuera hacia adentro en el nivel confiable más bajo:
- Pruebas de contrato CLI: ejecuta comandos raíz/subcomandos con proveedores falsos o fixtures; afirma stdout, stderr, comportamiento de salida, forma JSON.
- Pruebas unitarias: análisis, comparación, clasificación, selección de contexto, clasificación de errores.
- Pruebas de integración de proveedores: fixtures del sistema de archivos y servicios externos falsos; sin red/autenticación.
- Pruebas de hum humo en vivo: solo opt-in; afirma forma/invariantes, no salida exacta volátil.
Regla de regresión de Control de Calidad (QA) manual
El QA manual es descubrimiento, no columna vertebral de validación. Cuando encuentra una brecha de comportamiento:
- Escribe la prueba de regresión ROJA de nivel más bajo que la reproduzca.
- Verifica la evidencia del fallo.
- Implementa VERDE.
- Vuelve a ejecutar las pruebas automatizadas.
- Vuelve a ejecutar el QA manual solo después de que las pruebas pasen.
Si la salida/copia/forma de la CLI cambió, agrega o actualiza una prueba de contrato CLI.
Trampas de racionalización
| Afirmación | Respuesta |
|---|---|
| "Demasiado simple para probar" | El comportamiento simple aún puede regresionar. Agrega la pequeña prueba. |
| "Probaré después" | Las pruebas que pasan después no demuestran que la prueba puede fallar. |
| "Lo verifiqué manualmente" | Las comprobaciones manuales no son pruebas de regresión repetibles. |
| "Necesito explorar primero" | Está bien. Haz un prototipo, luego déjalo a un lado y comienza TDD. |
| "La prueba es difícil" | La interfaz puede ser difícil de usar. Simplifica la costura/diseño. |
| "Simular lo hace fácil" | Quizás estás probando el comportamiento simulado. Lee los antipatrones. |
| "Una gran prueba lo cubre" | Prefiere un comportamiento por prueba a menos que un contrato de extremo a extremo necesite más. |
| "La refactorización necesita una prueba fallida" | La refactorización pura comienza en verde y permanece en verde; el cambio de comportamiento necesita ROJO. |
Señales de alerta: detente y corrige
- El comportamiento de producción cambió antes de una prueba fallida.
- La prueba pasa inmediatamente pero se afirma como ROJO.
- La razón del fallo no se inspeccionó.
- El nombre de la prueba describe la implementación.
- La prueba afirma llamadas privadas, orden de llamadas o colaboradores internos.
- La configuración de simulación domina la lógica de la prueba.
- Existe un nuevo método solo para pruebas.
- Corregir la prueba durante VERDE porque la implementación no está de acuerdo.
- Lote horizontal de pruebas antes de cualquier implementación.
Cuando estás atascado
| Problema | Acción |
|---|---|
| No sabes cómo probar | Escribe la API pública deseada y la primera aserción. |
| La configuración de la prueba es enorme | Extrae un fixture/helper; si sigue siendo enorme, simplifica el diseño. |
| Debes simular todo | El código probablemente está demasiado acoplado. Introduce inyección de dependencias/costura. |
| El código existente no tiene pruebas | Agrega una prueba de caracterización para el comportamiento actual, luego ROJO para el cambio. |
| El error no está claro | Reproduce manualmente/con registros primero, luego codifica la regresión fallida más pequeña. |
Verificación antes de terminar
- El comportamiento nuevo/cambiado tiene pruebas.
- Se observó que cada prueba de comportamiento nuevo fallaba por la razón correcta.
- Todas las pruebas relevantes pasan.
- Las pruebas verifican el comportamiento público.
- Las simulaciones son solo en los límites o están justificadas.
- No se agregaron APIs de producción solo para pruebas.
- Los descubrimientos de QA manual se convirtieron en pruebas de regresión.