Test driven development, en su definición de manual, es un ciclo de tres pasos: escribes un test que falla, escribes el mínimo código para que pase, refactorizas, repites. Rojo, verde, refactor. En un pizarrón, con un ejemplo de calculadora, el ciclo se ve limpio y convincente. El problema aparece en cuanto lo llevas a un sprint real con una fecha de entrega.
El libro de Kent Beck describe una práctica específica y disciplinada: escribes un test antes de que exista una sola línea de código de producción, lo ves fallar por la razón correcta, escribes la mínima cantidad de código que lo hace pasar, y solo después limpias el diseño. Hecho tal como está escrito, es tanto una técnica de diseño como una técnica de testing. Los tests son un efecto secundario. El producto real es un código que se va formando de manera incremental a partir de un feedback constante y ajustado.
Donde el dogma se aplica mal es cuando se trata ese ciclo como una ley universal en lugar de una herramienta con un trabajo específico. El test driven development estricto asume que estás partiendo de un archivo vacío, que los requisitos son lo bastante estables como para codificarlos en una aserción, y que tienes el tiempo para dejar que el diseño emerja barra roja tras barra roja. La mayoría de los ingenieros senior pasa muy poco de su carrera en esa situación.
Dónde se rompe el test driven development estricto en equipos de producción reales
Tres cosas matan la versión de manual en contacto con un trabajo real.
La primera es el código legado. No arrancas de un archivo vacío. Te dan una clase de servicio de 4.000 líneas sin ninguna costura, escrita por alguien que dejó la empresa hace tres años, y un ticket que dice "arreglar el null pointer en el checkout". Michael Feathers escribió un libro entero sobre este problema porque el libro de Beck no lo aborda: no puedes escribir un test primero para un código que ya existe y que no tiene inyección de dependencias, ni interfaces, ni efectos secundarios separados de la llamada a la base de datos. El primer paso real aquí es escribir tests de caracterización, tests que fijan lo que el código hace actualmente, no lo que debería hacer. Eso es test driven en espíritu, pero no es el ciclo rojo-verde-refactor. Se parece más a ingeniería inversa con una red de seguridad.
La segunda es la presión de entrega. El TDD estricto te pide escribir un test para una feature cuya forma puede cambiar dos veces antes del almuerzo. Cuando el equipo de producto todavía está decidiendo si un campo es obligatorio u opcional, escribir una suite de tests exhaustiva desde el primer momento es una apuesta que vas a perder. Los ingenieros senior que insisten en la disciplina completa de test-first en esa situación suelen terminar tirando la mitad de sus tests cuando los requisitos cambian, que es exactamente el tipo de desperdicio que el TDD debería evitar.
La tercera es el volumen enorme de código que no tiene ninguna lógica relevante. Un getter. Un mapeador de DTO. Un componente de React que recibe un prop user y renderiza un nombre y un avatar. Escribir primero un test que falle para <UserBadge user={user} /> antes de escribir el componente no te da nada a cambio. No hay una rama, ni un cálculo, ni una transición de estado que puedas arruinar. Estás probando que el JSX renderiza lo que le dijiste que renderice. Eso no es rigor, es ceremonia.
El subconjunto de test driven development que sí sobrevive
Aquí está la parte de la que no se habla lo suficiente: los ingenieros que entregan sistemas confiables bajo deadlines reales no son los que abandonaron el TDD. Son los que lo acotaron.
Lo que sobrevive es escribir el test primero para todo lo que tiene lógica de decisión real: ramificaciones, cálculos, máquinas de estado, cualquier cosa donde un output equivocado es difícil de notar a simple vista y caro de detectar tarde. Lo que se deja de lado es el test-first para el código estructural: cableado, pegamento, componentes de paso, objetos de configuración. La versión senior del TDD no es "testear todo antes de escribirlo". Es "saber en qué diez por ciento de tu código se esconden realmente los bugs, y escribir ese diez por ciento con el test primero".
Esto no es una versión diluida de la idea de Beck. Se parece más a lo que los equipos con experiencia ya venían haciendo antes de que "TDD" se convirtiera en una palabra de moda apta para certificaciones: escribir el test primero cuando el código es genuinamente difícil de acertar al primer intento, y escribirlo después (o directamente no escribirlo, para los casos verdaderamente triviales) cuando no lo es.
Una línea concreta: lógica de precios versus un pass-through de props en React
Un ejemplo concreto, porque "lógica de negocio" es algo vago hasta que lo ves al lado de algo que no lo es.
Supongamos que estás construyendo una calculadora de precios para un producto de suscripción: precio base, una regla de prorrateo para upgrades a mitad de ciclo, un multiplicador de impuestos regional, y un stack de descuentos que tiene que aplicarse en un orden específico porque así lo pide el equipo de finanzas. Escribe el test primero aquí. Escribe it("aplica el descuento porcentual antes del descuento fijo cuando ambos están presentes") antes de escribir una sola línea del cálculo. Vas a equivocarte en el orden de los descuentos en el primer intento. Le pasa a todo el mundo. El test lo detecta en tres segundos en lugar de en un ticket de soporte de un cliente al que le cobraron mal cuatro meses después. Este es exactamente el caso para el que se escribió el libro de Beck: una regla lo bastante compleja como para que sostenerla entera en tu cabeza mientras escribes código sea una apuesta perdida.
Ahora toma el componente <UserBadge user={user} /> de antes. Recibe un objeto user y renderiza un nombre, un avatar y un punto de estado en línea. No hay ninguna regla que puedas arruinar. Un snapshot test o una revisión visual rápida después del hecho te dice todo lo que un ciclo test-first te hubiera dicho, con una fracción de la ceremonia. Forzar un ciclo rojo-verde-refactor sobre ese componente no hace que el código sea más seguro. Hace que el pull request sea más largo y que a quien lo revisa se le nublen los ojos.
La línea no es "backend versus frontend" ni "difícil versus fácil de escribir". Es "si equivocarte en esto te cuesta algo que no se ve a simple vista". Un cálculo de precios, los casos límite de un rate limiter, una máquina de estado para el ciclo de vida de una orden, un cálculo de fechas que cruza zonas horarias: todos estos se ganan un enfoque test-first. Un componente que solo pasa props a un markup, no.
Cómo se ve esto semana a semana
En la práctica, esto significa que tu suite de tests está desbalanceada a propósito. El módulo de precios, la lógica de renovación del token de auth, el código de reintentos con backoff, el parser de CSV que tiene que manejar los formatos ligeramente distintos de tres proveedores: esos tienen tests escritos antes o junto con la implementación, y son específicos sobre los casos límite, no solo sobre el camino feliz. El controller que llama a tres servicios y devuelve el resultado combinado, el config loader, la librería de componentes: esos también tienen tests, pero escritos después, sobre todo para fijar el comportamiento una vez que ya es estable, y a nadie le quita el sueño el orden.
Esto también cambia cómo revisas código. Un pull request que agrega una regla de descuento sin ningún test adjunto es un hallazgo real, que vale la pena bloquear. Un pull request que agrega un prop nuevo a un componente de visualización sin test adjunto no es algo que deberías estar comentando, y si el proceso de tu equipo trata ambos casos igual, ese proceso está optimizando para una métrica (el porcentaje de cobertura) en lugar de para lo que la cobertura debería proteger (la confianza en el código que realmente es riesgoso).
La disciplina que importa no es "escribe los tests primero, siempre". Es saber, antes de escribir una sola línea, si lo que estás a punto de construir es del tipo de cosa en la que te puedes equivocar sin darte cuenta. Si lo es, frena y escribe el test primero. Si no lo es, escribe el código y sigue adelante. Ese criterio es la habilidad real. El ritual del rojo-verde-refactor es solo una forma de ejercitarlo.
Vuelve a esa reunión del pizarrón, la del ciclo rojo-verde-refactor que dos semanas después nadie vuelve a mencionar. No falló porque el equipo careciera de disciplina. Falló porque nadie distinguió entre el módulo de precios y el componente que solo pasa props. El test driven development estricto, aplicado parejo a los dos, es la razón por la que el diagrama terminó borrado.
La versión que sobrevive a un deadline real es más acotada y, honestamente, más útil que la del libro: test-first para los cálculos, las transiciones de estado y los casos límite que te quitan el sueño, y criterio de ingeniería normal para todo lo demás. Esa es la reunión que sí vale la pena repetir cada sprint.



