TDD is dead? … o yo y el TDD. Primera Parte

Test-driven development (TDD)”, en la lengua de Cervantes “Desarrollo guiado por pruebas”… Palabras malditas para algunos… Últimamente se dice que está muerto… “TDD is dead?”.


Actualización:
La segunda parte de este artículo es «Mocks… o el TDD y yo»


¿Qué es?

A grandes rasgos, consiste en primero diseñar y realizar los test de una determinada funcionalidad (lo usual es una clase de negocio). En este punto los test no pasan => Test ‘en rojo‘.

[Esquema TDD]A continuación se codifica, centrándonos en que pasen todos los test. Una vez que todos están ‘ok’ (Test en ‘verde‘) se refactoriza el código. Se da un repaso final para que quede todo correcto y pulir los flecos que quedan.

Estos test serán siempre independientes. No debe importar el orden en que se lancen. Se añaden a la herramienta de integración continua para que se pasen en cada compilación/build y asegurarse así que no ha habido ‘efectos colaterales’ en cada modificación de código fuente.

«TDD is dead?«.. el debate.

Si partimos de este enfoque ¿es todo ‘testeable‘? ¿Se ve condicionado tu diseño del software poder hacer este tipo de pruebas? ¿Cuanto tardan tus test? ¿Es asumible este tiempo?

Todo esto y más argumentos se han puesto encima de la mesa. Hay quien opina que que en el TDD puro sólo es para entornos académicos y blogs y que en el mundo real no se puede llevar al extremo. Hay pruebas que no se pueden replicar tan fácilmente y que dependen de un entorno desplegado (terceros sistemas).

Tampoco es recomendable que se condicione el diseño y la arquitectura para poder realizar los test. Debe ser al contrario los test se deben adaptar al diseño.

El TDD se basa en pasar continuamente los test, por lo tanto deben ser lo más rápidos posible. En algunos casos el tiempo de lanzar toda la batería de test no es despreciable. En este punto los Frameworks de ‘mock‘ basados en generación de clases por reflexión están en el punto de mira.

Y a pesar de todo esto, esta forma de desarrollar me ha hecho la vida más sencilla. Lo intento explicar:

Mi Kung-fu, Mi TDD

He llegado a esta metodología tarde. No fue hasta que empezando con un proyecto desde cero, que me puse en serio a seguir este paradigma. Mi conversión ha sido tal, que confieso que ahora ya casi no sé prescindir de los test al enfrentarme a tirar código. Primero me planteo los tests y después implemento. Aunque esto no es siempre así, tengo bastante flexibilidad.

Si tengo claro ‘lo que hay que hacer‘ hago todos del tirón. Repaso todos los casos de uso, las posibles entradas, salidas y los errores y cada caso lo reflejo en un test.  En estos casos lo más normal es que tarde más en diseñar los test que en codificar. El proceso es, de manera iterativa, terminar una parte de la codificación e ir pasando los test, hasta que no quede ninguno con error y la funcionalidad esté terminada.

En los casos que no tengo tan claro lo que tiene que hacer la clase, quizás porque, por ejemplo, tenga que ‘repartir’ la responsabilidad en otra clase, empiezo realizando los test que si tengo claro, validando entradas y resultados parciales. Codifico y logró que se pasen los test. Me replanteo o no la  estructura y vuelvo a codificar los nuevos test y terminar de implementar la parte que queda. Todo también de manera iterativa.

En cualquier caso siempre habrá uno o varios test con las entradas y la salida correctas. Los pongo al principio de la clase de test y me sirve de documentación. De un vistazo se puede saber el comportamiento y el control de errores realizado.

Sobre los ‘mock’

Para poder realizar los test ‘mokeo’ las clases que necesito para probar la funcionalidad. Empecé realizando mis propios objetos Mock porque no quería que mi foco se perdiera en el aprendizaje de un nuevo Framework de ‘mokeo‘.

En la arquitectura en la que suelo trabajar Spring y Spring-Data para acceso a base de datos, en lo que todo es un interfaz, es supersencillo crear el Mock que genere la salida que necesitas. Estos objetos son reaprovechables, ya que en un proyecto normalmente las clases están relacionadas, y utilizas el mismo Mock para varios test.

Esto en realidad no va en contra de ‘la máxima‘ de que los test no puede ser un desarrollo en sí, ya que ‘sólo codifico la parte que necesito‘. Si en un segundo test necesito más funcionalidad completo el comportamiento del Mock.

Resulta que después, leyendo post como este  («When to mock«) en el Blog de Uncle Bob Martin me di cuenta que no iba tan mal encaminado.

Estoy preparando un segundo artículo donde intentaré explicar con ejemplos como hago los ‘mock‘.

Qué he ganado

Sobre todo una nueva visión de enfrentarme al desarrollo, para mi más enriquecedora. Ahora lo primero que pienso son los parámetros que voy a pasar a la clase y que salida quiero obtener. Valoro que entradas podrían causar error y como quiero realizar este control de errores. Esto hace que tenga una idea mucho más clara de lo que va hacer la clase y de que parte de la funcionalidad va a ser responsable.

Para simular la base de datos utilizo mis propios ‘mock‘ implementando los interfaces de Spring-Data y no uso una BBDD en memoria. Esto también me ha ayudado a entender mejor que datos necesito y tener mucho más claro el modelo.

He ganado en seguridad. Casi consigo que el 100% del código sea testeable. Gracias a los test estoy seguro de que se ha pasado por cada uno de los ‘if’ del  código.

Es significativo que ahora uso mucho menos el ‘debug‘ para saber que está fallando y corregir errores. Puede resultar paradójico pero en muchos casos, con los test consigo comportamientos que es difícil de obtener para obtener el mismo resultado en una prueba de integración. Normalmente hago pocos ajustes en el código cuando lo pruebo desplegado en un servidor de aplicaciones, contra una base de datos de verdad, …

Tengo menos bug y más control de la interrelaciones que tiene el código. Si hay alguna modificación de uan parte del código que afecta a otra, los ‘test’ pueden evitar muchos de estos problemas de interrelación. Problemas que suelen surgir, ‘como siempre pasa’ (ley de Murphy), en producción.

Como contrapartida se tarda más en el desarrollo y el proceso de generación de los test no es ‘tan divertido‘ como puede parecer. De todas maneras las ventajas que he obtenido me salen a cuenta y doy el tiempo por bien invertido.

Reflexiones

Realmente en mis proyectos, y a pesar de integrarse con más de un sistema de terceros no he tenido muchos problemas en generar los test. No he sentido que el diseño del software se haya visto condicionado.

Al hilo de esto, estoy acostumbrado a trabajar con Spring y su inversión de control. Hay quienes le ‘chirría’ tener que definir un interfaz aunque sólo haya una implementación.  A mí definirlo me parece más limpio indicando así el api pública de la clase.

Esto para los test viene muy bien. En muchos casos por cada interfaz existirá la implementación de la funcionalidad y otra ‘mock‘ para simular el comportamiento para los test de otra clase.  Insisto no lo veo como un condicionante de diseño para facilitar los test. Hubiera definido el interfaz de todas maneras incluso en el caso de que no hubiera hecho los test.  Sobre esto me parece interesante este post How tdd affect my designs«)

El hecho de implementar mis propios ‘mock‘ y que estos no sean creados utilizando reflexión y sólo haya que instanciarlos, hace que la lentitud no sea un problema.

Así que, en la práctica, no me he encontrado muchos problemas técnico/filosóficos a la hora de utilizar el TDD. Queda pendiente la segunda parte de este artículo de ejemplos de ‘mock’ que estoy utilizado.

Espero que os sirva.

M.E.