Introducción
El ruido actual alrededor del hype de la IA en la ingeniería de software es ensordecedor. Con nuevos modelos y herramientas agénticas emergiendo cada día, es fácil caer en la trampa de creer que las prácticas tradicionales del oficio disciplinado han quedado obsoletas. Sin embargo, creemos que hoy más que nunca necesitamos volver a nuestros orígenes. En este periodo cambriano de rápida evolución, la industria está dejando de lado el conocimiento detrás de las prácticas fundamentales de la ingeniería de software, aquellas que en el pasado evitaron ser arrastradas por el caos del desarrollo rápido y no verificado.
Nuestro camino personal para convertirnos en mejores ingenieros comenzó cuando nos sumergimos en las obras fundamentales de Kent Beck y Martin Fowler. Sus libros sobre Extreme Programming (XP), Test-Driven Development (TDD) y refactorización, junto con los principios atemporales de The Pragmatic Programmer, cambiaron nuestra perspectiva de simplemente “escribir código” a construir software. Al adoptar estas disciplinas, aprendimos que la calidad no es algo adicional, es el núcleo. Las joyas ocultas de TDD, en particular, no son solo técnicas heredadas; son los pilares esenciales que necesitamos para construir el futuro de la tecnología de manera efectiva. Estos recursos nos enseñaron que ser un gran ingeniero no se trata de complejidad o velocidad, sino de confiabilidad y simplicidad.
La disciplina de la evidencia
En esencia, TDD es un proceso disciplinado en el que escribes una prueba antes del código. Esto asegura que los requisitos sean claros y que el software sea testeable desde el primer día. El proceso sigue el ciclo rojo-verde-refactor. Esto implica:
- Escribir una pequeña prueba que falle, definiendo el comportamiento deseado (Rojo).
- Escribir el código mínimo necesario para que la prueba pase (Verde).
- Limpiar y mejorar el diseño del código asegurando que la prueba siga siendo exitosa (Refactor).
Sin embargo, la comprensión de este proceso evoluciona con la experiencia. Al inicio de nuestras carreras, veíamos escribir pruebas simplemente como una práctica de verificación, o una forma de asegurar que el código hace lo que se supone que debe hacer. A medida que maduramos, empezamos a obsesionarnos con métricas como la cobertura de pruebas o técnicas avanzadas como mutation testing.
Pero la comprensión final es más profunda. Las pruebas son en realidad una forma de documentación viva. Demuestran que el código es válido al crear estructuras que representan la hipótesis que el código sostiene. Edsger Wybe Dijkstra descubrió este principio en 1960, al darse cuenta de que los programadores podían usar una jerarquía de postulados similar a la de los matemáticos. Empezar la implementación con pruebas ofrece todo esto desde el inicio, pero aún hay algo más valioso por descubrir.
Apóyate en el feedback
"Una prueba es el primer usuario de tu código" es una de las lecciones clave del libro The Pragmatic Programmer de Andy Hunt y Dave Thomas. Es simple, directa y clara: cuando escribimos una prueba antes de la implementación, se convierte en la primera entidad que verifica la funcionalidad a implementar.
Este cambio de perspectiva es invaluable. Si una prueba es difícil de escribir, es una señal clara de que el código será difícil de usar. Ya sea diseñando una API REST o definiendo métodos públicos en una clase, estamos creando una arquitectura que define cómo otros desarrolladores interactúan con nuestro código. Al tratar la prueba como el primer cliente de nuestro código, diseñamos interfaces más intuitivas y aseguramos que el software nazca con la usabilidad en mente. El poder de este mecanismo va más allá de la ingeniería de software. Está profundamente arraigado en la psicología del comportamiento, particularmente en el trabajo de Daniel Kahneman y Richard H. Thaler. Como humanos, dependemos del feedback para mejorar nuestro desempeño, incluso en tareas de programación.
La mejor forma de ayudar a las personas a mejorar su desempeño es proporcionar feedback. Los sistemas bien diseñados le dicen a las personas cuándo lo están haciendo bien y cuándo están cometiendo errores
Evitar la sobreingeniería
Uno de los beneficios más inmediatos de TDD es que obliga a implementar la cantidad mínima de código necesaria para que una prueba pase, ya que es fácil dejarse llevar y escribir código “por si acaso”. Este es un punto que Kent Beck menciona en Extreme Programming Explained. Sin una prueba fallida que satisfacer, los desarrolladores suelen caer en la trampa de la sobreingeniería.
Al seguir el ciclo de TDD, el código resultante es significativamente más simple. Se evita la acumulación de métodos no utilizados y lógica especulativa. El resultado es un código más limpio que hace exactamente lo necesario, ni más ni menos, reduciendo la superficie de posibles errores.
Reducir la superficie de posibles errores no es solo un lujo técnico, sino una estrategia de negocio crítica. Cuanto más tarde se detecta un defecto, exponencialmente más costoso es corregirlo. Esto se debe a que el trabajo construido sobre componentes defectuosos inevitablemente tendrá que ser descartado o rehecho. Corregir un error después del lanzamiento puede ser hasta 150 veces más costoso que abordarlo durante el diseño inicial. Por lo tanto, evitar la sobreingeniería no es una preferencia de programación, es una necesidad financiera.
Refactorización: diseñar con propósito
La etapa final del ciclo de TDD —refactorización— es donde ocurre lo más importante. Al contar con pruebas que ya pasan como red de seguridad, puedes reorganizar tu código sin miedo para alinearlo con los principios fundamentales de la ingeniería de software. Este es el momento de aplicar principios SOLID, eliminar duplicaciones (DRY) y asegurar que no estás construyendo cosas que aún no necesitas (YAGNI). Y menos tokens que consumir.
Refactorizar con TDD no es solo limpiar, es diseñar con intención. Permite usar patrones como strategy o factory solo cuando el código realmente lo requiere. Convierte el desarrollo en un proceso disciplinado de refinamiento de la arquitectura.
Diseño evolutivo
TDD permite un diseño evolutivo, que nos da la posibilidad de posponer decisiones de implementación hasta el último momento responsable. En lugar de depender de un diseño completo desde el inicio (BUFD), que suele generar sistemas rígidos y sobreingenierizados difíciles de cambiar cuando la realidad del negocio evoluciona. TDD permite que el diseño emerja de forma orgánica, impulsado por requisitos reales y no por suposiciones. Este enfoque mantiene el sistema flexible. Al tomar decisiones cuando se tiene más información, evitamos la falacia del costo hundido en arquitecturas que no se adaptan a las necesidades del proyecto.
Sin embargo, la evolución no aplica solo a nuevas funcionalidades. También aplica a sistemas existentes. Cuando necesitamos evolucionar código legado (código sin pruebas), crear una red de seguridad antes de cualquier cambio es imprescindible. Es la única forma de distinguir qué funciona y qué no. En la práctica, hay dos opciones: Editar y rezar, o cubrir y modificar (trabajar de forma segura).
No creo que nadie elegiría a un profesional cirujano que opere con un cuchillo de mantequilla solo porque trabaja con cuidado
Desarrollo basado en hipótesis
En arquitectura de software, a menudo enfrentamos decisiones donde el camino correcto no es evidente. En lugar de debatir durante horas, podemos usar TDD como un laboratorio de desarrollo basado en hipótesis. En este contexto, la prueba no solo valida errores o funcionalidades, sino que actúa como experimento para comprobar si una decisión estructural es viable. Tratamos nuestras suposiciones arquitectónicas como hipótesis que deben ser probadas mediante implementación.
Por ejemplo, si un equipo debate si una lógica compleja debe ser una librería interna o un microservicio, se puede escribir una suite de pruebas que simule el comportamiento. Al implementar el mínimo código para pasar esas pruebas, puede descubrirse que el contrato de datos es tan complejo que el servicio se vuelve difícil de mantener. La prueba invalida la hipótesis antes de invertir semanas en infraestructura. El proceso también permite decidir en el último momento responsable.
Reducir la complejidad accidental
En su ensayo No Silver Bullet, Fred Brooks distingue entre complejidad esencial, la inherente al problema, y complejidad accidental, la que generamos por mal diseño o herramientas inadecuadas. Argumenta que ninguna tecnología puede multiplicar la productividad por diez, y que el software debe crecer de forma incremental.
TDD es una de las herramientas más efectivas para esto. Obliga a escribir solo el código necesario para pasar una prueba, filtrando la complejidad innecesaria antes de que entre al sistema. Evita crear abstracciones innecesarias y mantiene el software lo más simple posible.
Los agentes de IA funcionan mejor con TDD
En la era del código asistido por IA, TDD es más relevante que nunca. Herramientas y agentes funcionan mejor cuando tienen criterios claros. Sin pruebas, los agentes pueden generar grandes cantidades de código que parecen correctas, pero contienen errores o complejidad innecesaria.
Al usar pruebas como guía, se establecen límites claros. Esto obliga a la IA a generar implementaciones mínimas y funcionales, evitando complejidad innecesaria. TDD convierte a la IA en un colaborador más predecible y disciplinado.
Conclusión: ATDD y el futuro de la orquestación
El software surgió como producto hace unos 75 años y evolucionó como disciplina hace unos 55, tras la crisis del software y la conferencia de la OTAN de 1968. Desde entonces, se han desarrollado métodos para enfrentar estos desafíos. Extreme Programming (XP) reúne muchas de estas soluciones, y TDD es uno de sus pilares.
TDD equilibra disciplina y flexibilidad. Permite usar pruebas como experimentos y como filtro de complejidad, con el objetivo de construir software de forma orgánica e intencional.
Mirando al futuro, con agentes de IA generando grandes volúmenes de código, surgen problemas que ya hemos vivido antes. Y probablemente ya conocemos las soluciones. Acceptance Test-Driven Development (ATDD) es la evolución natural, especialmente al trabajar con múltiples agentes. Mientras las pruebas unitarias definen el cómo, ATDD define el qué y el por qué desde el negocio. Al establecer criterios de aceptación desde el inicio, es posible orquestar sistemas complejos con confianza. ATDD probablemente será la interfaz principal, mientras los equipos trabajan para hacer que esas pruebas pasen usando TDD.
Aviso legal: Las declaraciones y opiniones expresadas en este artículo son las del autor/a o autores y no reflejan necesariamente las posiciones de Thoughtworks.