El valor de TDD (2)

Published on Author lopez

Anterior Post
Próximo Post

En el anterior post comentaba que una de las ventajas de TDD (Test-Driven Development) es que facilita cambiar nuestro código, ya sea para mejorarlo o para adaptarlo a nuevos casos de uso. Quería comentar hoy algunas experiencias (me temo que sobre proyectos no públicos, así que no tengo un repo para mostrar) donde descubrí ese valor de TDD.

Por ejemplo, en un proyecto ya terminado y entregado, el cliente pide despues de un tiempo un cambio: un nuevo caso de uso. El proyecto contenía un “parser” de expresiones de consulta, tipo SQL, sobre un modelo de dominio en memoria, y en en lenguaje consulta que se había implementado había filtros como

where Year = 2013

Donde se admiten otros operadores que el igual, pero donde la expresión de la derecha era siempre una constante. Para el nuevo caso de uso el cliente pedía extender este lenguaje de consulta (tanto el “parser” como la ejecución de la consulta, para tener expresiones como

where Year = @today – 10

donde @today es el año actual, donde la expresión de arriba es “dame los datos de hace diez años”. Bien, como tanto el “parser” como el ejecutor de comandos estuvieron hechos con TDD, estaba todo preparado para probarlos, y se escribieron los nuevos tests a pasar. Al principio no compilaba (en C#), y luego comenzó a compilar, pero dando las pruebas nuevas en rojo. Se agregó el código necesario para que pasen a verde, se corrieron todas las pruebas para ver si había algún “side effect”, efecto colateral, que rompiera algo, pero todo anduvo bien. Se entregó el resultado al cliente, lo probó, y hasta el día de hoy no ha reportado un solo “bug” sobre el tema. Lo está usando sin problema. ¿Cuánto se tardó en modificar el código? Pues:

UNA HORA

Tardé otra hora explicando lo que había hecho (en inglés, que no es un tema que domino mucho ;-).

Pero no es solamente el tiempo, sino que la forma de trabajo (TDD antes, TDD ahora), permitió:

– Apenas gastar tiempo mínimo en depuración
– No romper nada de lo anterior (porque TODO LO IMPORTANTE que anda de lo ANTERIOR, se implementó siguiendo el flujo de trabajo de TDD, no con simple “test-after”; entonces si los tests de ahora dan en verde, es ALTAMENTE PROBLABLE que el sistema en conjunto siga funcionando)
– Dejar todo preparado para que otro equipo pueda hacer lo mismo (adaptar el sistema a un nuevo caso de uso) más adelante.

Pongamos un ejemplo negativo. En un proyecto, había una lógica que, dado un usuario y un tema, devolvía una lista de documentos de ese tema y accesibles por el usuario (no era exactamente así, pero basta en este post esa descripción para el ejemplo). Obtener esa lista no era simple: había una gran cantidad de lógica, no siempre directa (un documento podría heredar de otro, y heredar entonces su accesibilidad o innaccesibilidad por un usuario; había distintas accesibilidades (para ver, para modificar, etc)…). Y además, en la implementación del anterior equipo de desarrollo, alguien había decidido algo como “si lo ponemos en un Stored Procedure va a andar más rápido” (sin hacer ninguna prueba que corrobere esa afirmación). Conclusión: todo estaba en un stored procedure de decenas de líneas.

Llegó el momento de, ante un caso de uso nuevo, hubo que refactorizar esa lógica: en el caso de uso nuevo, en algunas ocasiones, obtener esa lista llevaba un par de decenas de minutos (jeje… si, la “magia” de poner todo en el stored procedure ;-). Eso en sí no es problema: no siempre la primera implementación es “la mejor” (en parte, porque no hay “la mejor”, sino que quizás hay “la mejor para los actuales casos de uso, pero tal vez hay que cambiarla cuando aparezcan nuevos casos”), y la original implementación que ya estaba en producción había cumplido con su cometido, y el sistema estaba funcionando sin mayor problema. Pero al hacerlo sin TDD, una consecuencia es: FUE DIFICIL agregar un caso de uso, y reimplementar la lógica, porque no había forma de saber, por pruebas automáticas, qué casos eran cumplidos por la nueva implementación (un modelo en memoria fue la nueva implementación, mucho más rápido), y cuáles no. Conclusión: hubo que implementar pruebas con casos de uso más frecuentes, y como no estaba claro qué tenía que devolver en cada caso, la prueba comparaba el resultado DE LA NUEVA implementación, con el resultado que daba la VIEJA implementación. No es la mejor solución.

¿Lección aprendida? Hacer TDD, paga, nos da beneficios, en especial al cliente final, y a todo equipo que venga a mantener el sistema. Sin TDD: horas o días para reproducir y arreglar un bug, interminables sesiones de depuración, costo mayor para el cliente final, desgaste y poco avance diario. Con TDD: todos felices, y dedicando el tiempo a agregar valor y no a remar en un río de mermelada 😉

Tengo más casos para comentar, pero espero que lo de arriba sirva para aportar agua para el molino de TDD.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez