Seguro estuviste en esta reunión. Alguien del equipo propone hacer test driven development como corresponde en este sprint: escribir el test que falla, escribir el código mínimo para que pase, refactorizar, repetir. Rojo, verde, refactor. Todos asienten. Dos semanas después el diagrama del pizarrón desapareció y nadie vuelve a mencionarlo.
Ese ciclo no es un mito. 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.
Dónde queda el argumento
El test driven development, como práctica estricta, no es algo que hagan la mayoría de los equipos de producción, y no es algo que la mayoría de los equipos de producción deberían hacer en todo el código. Eso no es un fracaso de la idea. Es un desajuste entre una técnica diseñada para un tipo de problema (código cuya corrección es difícil de verificar a simple vista) y la realidad de que la mayor parte de un código no es ese tipo de problema. Aplicarlo de manera uniforme es la razón por la que los equipos lo abandonan dos semanas después de la sesión en el pizarrón: estaban pagando el costo completo de la disciplina en código que nunca la necesitó.
La versión que sobrevive al contacto con 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. Eso no es TDD liviano. Es TDD apuntado a la parte del trabajo donde realmente vale la pena.



