Muchos desarrolladores de software piensan que la ventaja más importante de hacer Test driven development (TDD en adelante) es que aseguras que terminas con un conjunto de test unitarios que documentan y verifican el funcionamiento de la aplicación. Sin embargo, esta práctica te permite modificar el diseño del software que estás creando pudiendo tomar las decisiones adecuadas en el momento adecuado, y por tanto, terminar no sólo con el conjunto de test, sino con el mejor diseño de la solución que estás construyendo a partir de un código limpio. Antes de nada, empecemos con un poco de teoría sobre TDD. Se trata de un práctica de ingeniería del software que a su vez incluye otras dos prácticas:

  • Test first development (TFD): Exige escribir las pruebas antes de la implementación.
  • Refactor: Consiste en modificar el código de tu aplicación sin cambiar su comportamiento con objeto de conseguir un diseño mejor y/o un código más limpio.

Y se apoya en dos principios de diseño muy importantes. El primero, Keep it simple, stupid ( KISS ), que aboga por evitar cualquier complejidad innecesaria. Y el segundo, You aren’t gonna need it ( YAGNI ), que supone escribir el código más sencillo que implementa cada uno de los casos de prueba de nuestro conjunto de pruebas.

Esta práctica se basa en realizar ciclos de desarrollo cortos donde los requisitos se transforman en casos de prueba y a continuación se implementa el software que cumple esas pruebas. A medida que se van implementando casos de prueba será necesario refactorizar el código para mantenerlo limpio eliminando la duplicación y aplicando principios de programación y diseño como SOLID o “Clean code” con el objetivo de mejorar la legibilidad y mantenibilidad del mismo.

dqe

Para mostraros un ejemplo, voy a construir un módulo que verifiquen los documentos con los que se puede identificar un cliente, en este caso el DNI y el NIE. Empezaremos con el DNI y nos creamos los siguientes casos de prueba.

Para empezar creamos tanto clase Dni y como la Excepción InvalidDni para poder implementar el fallo de nuestro test:

Con este código la aplicación compila y podemos ejecutar la prueba comenzando un ciclo de TDD (red/green/refactor) puesto que vemos que tenemos un caso de prueba que nuestro código no cumple ( red / green /refactor)). Ahora el objetivo es escribir el código más sencillo posible que implemente el requisito, que en este caso, comprobar la validez del DNI con número 7.983.231-T ( red / green /refactor).

Yo he llegado hasta el siguiente:

Tenemos nuestra prueba funcionando por lo que podemos plantearnos refactorizar el código para hacerlo más mantenible y legible ( red/ green/ refactor ). Yo he decidido hacerlo porque veo que estamos mezclando muchas responsabilidades en una misma clase lo que la hace poco mantenible. Además, me gusta que las clases sean muy pequeñas para que sean fácilmente entendibles 😉

Por un lado tenemos la responsabilidad de filtrar la cadena para soportar números de Dni con guiones, puntos y espacios en blanco y por otro la verificación de la letra de control. Todo ese código lo hemos extraído a clases con el propósito de filtrado (DniFilter) y verificación (DniChecker) respectivamente y hemos dejado el código de nuestra clase Dni de la siguiente forma:

Además, también hemos decidido proteger el constructor y proporcionar el método estático parse para hacer el código aún más legible a la hora de usar nuestra clase Dni.

La clase DniFilter tendrá como responsabilidad separar la parte numérica de la letra del DNI además de permitirnos DNI con espacios, guiones y puntos.

Respecto a la clase DniChecker, tendrá como responsabilidad comprobar si la letra es la correcta. Pueden ocurrir dos casos. Por un lado, que la letra no sea válida o que no sea la adecuada según la fórmula de control.

Hemos ido también añadiendo algunos casos a nuestro test puesto que a la par que incluímos requisitos adicionales relativos al filtrado.

Mucho mejor, ¿no? Poco a poco, emerge el mejor diseño para los requisitos que hemos decidido implementar y además podremos comprobar en todo momento el correcto funcionamiento de nuestra aplicación. Con este código, podemos estar bastante seguros que nuestra aplicación funciona. De hecho, tenemos un 100% de cobertura, aunque creedme que esto no es lo importante. Lo más importante es que nuestros tests sean los adecuados para representan los requisitos que vamos implementar.

En este punto, con el código ya mucho más legible y mantenible, nos tocaría coger otro requisito para repetir el ciclo. En este caso, el NIE. Pero eso os lo dejo a vosotros. Aquí tenéis el repositorio git con el código para que podáis continuar. ¿Quien se atreve a hacer un fork y contribuir con un pull request? Os doy alguna pista de cómo lo haría yo. En la siguiente refactorización, una vez implementado el NIE, quizás podamos extender las clases Dni, DniFilter y DniChecker para dar cabida a las clase Nie.

Muchas gracias por leerme,
José San Román