Solucionar el problema y buen diseño

Imagen de http://www.nydailynews.com

 

Cuando tenemos que añadir una nueva funcionalidad en nuestra aplicación tenemos que pensar desde el principio en como vamos añadirla y en el diseño que vamos a utilizar para implementarlo. Con diseño quiero decir los nuevos objetos de tipo servicio que vamos a utilizar, los nuevos objetos de dominio, como vamos a conectar todo esto con el código actual y un largo etcétera.

Normalmente tenemos que pensar en el diseño desde el principio porque posteriormente nos va a resultar trabajo el hacerlo después. Por ejemplo, si nos damos cuenta al final que un patrón encaja perfectamente con la solución que hemos implementado tendremos que testear que la nueva funcionalidad sigue funcionando de la manera adecuada. Y si hay algún error tendremos que depurar el código para ver donde está el error. Ocasionando una gran pérdida de tiempo.

Si estamos utilizando lenguajes estáticos como Java los IDE como IntelliJ o Eclipse nos permiten hacer cambios de diseño de forma automática (como renombrado de clases) lo que hace que el cambio no provoque casi nunca que el código no funcione como deseemos. Pero posiblemente queramos asegurarnos que el cambio no ha provocado un problema, lo que hace que tengamos que testear la aplicación de forma manual.

En resumen, cuando estamos empezando a codificar una nueva funcionalidad tenemos que pensar en la solución y en un diseño porque los cambios en el diseño posteriormente son costosos.

 

Cuando empecé a practicar TDD lo hice porque estaba cansado de que cada vez que arreglaba un problema había añadido dos más o que en un futuro apareciera de nuevo el mismo problema.

Pero cuanto más he ido practicando TDD una de las ventajas que más me gustan es que puedo enfocarme en una sola cosa a la vez porque tengo fases en las que me enfoco solo en diseñar y hay fases en las que me enfoco solo en solucionar el problema / añadir la nueva funcionalidad.

Las fases de TDD son tres:

  • escribir un test que falle
  • añadir código para que el test pase
  • refactorizar

En la primera y segunda fase nos dedicamos principalmente a solucionar el problema porque cada test que añadimos es un paso adelante hacia la solución y porque el código que añadimos es el mínimo para que pase el test. No nos tenemos que preocupar en el diseño de nuestro aplicación. Hay una excepción que es cuando escribimos el primer test ya que en el primer test decidimos el nombre de la clase, su método y los parámetros de entrada y salida.

En la tercera fase hacemos pequeños cambios en el código que hacen que el diseño general vaya mejorando y evolucionando en el tiempo.

Así que tenemos una parte en la que nos enfocamos en solucionar el problema y otra en la que nos enfocamos en el diseño. Enfocándonos en una cosa a la vez. Las técnicas de productividad que conozco como pomodoro o GTD se basan en hacer una cosa a la vez para mejorar la productividad. Así que tiene sentido el hacer diseño o solución del problema pero no las dos a la vez.

Además, si vemos que después de terminar tenemos dudas del diseño el hacer cambios en el código nos va a resultar más seguro y fácil porque tenemos un conjunto de tests que nos van a dar seguridad que no estamos rompiendo nada.

 

Resumiendo, que podamos preocuparnos solo por solucionar el problema entre manos o por el diseño pero no los dos a la vez es una de las grandes ventajas de TDD. Además, debido a que tenemos un buen número de test unitarios podemos hacer cambios posteriores en el código con seguridad.

 

Pequeño ejemplo en Python y TDD

Imagen de http://code.tutsplus.com/tutorials/beginning-test-driven-development-in-python–net-30137

Hace un tiempo leí el libro Test-Driven Development by Example de Ken Beck y me pareció un gran libro. Es una gran introducción a TDD.

Una parte del libro no entendí bien. La relacionada con un ejemplo de xUnit.
En esa parte del libro, implementa una librería para poder testear código y el ejemplo que va desarrollando está escrito en Python y no conocía el lenguaje. Creo que el lenguaje fuera Python hizo que no lo entendiera.

Así que para poder entenderlo y aprender un poco Python he escrito el código resultado de cada capítulo en un fichero mientras iba leyendo el libro. He subido los ficheros a:

https://github.com/kikers25/tddByExamplePython

Se puede comparar un fichero (con Winmerge) con otro para ver cual han sido los cambios en cada capítulo. Por ejemplo, los cambios entre el capítulos 18 y 19 serían:

Python

Espero que te resulte útil.

Ciclo TDD: versión extendida

Imagen de http://www.agilenutshell.com/test_driven_development

Cuando a alguien se le explica por primera vez test driven development (TDD) lo primero que se le cuenta es su ciclo de desarrollo, que consiste en:

  1. Escribir un test que falle
  2. Escribir código de producción que haga que el test pase
  3. Refactoriza el código

Estos pasos son un gran resumen de las principales tareas que hay que hacer en TDD pero para alguien nuevo en TDD no es suficiente. Voy a extender el mismo ciclo de desarrollo añadiendo más detalles. La mayoría de los pasos vienen de la siguiente página:

http://c2.com/cgi/wiki?TestDrivenDevelopment

Versión extendida

  1. Selecciona un test. Busca un test que sea fácil de implementar pero que permita progresar lo máximo posible en la implementación
  2. Piensa cómo quieres comprobar que el test pasa
  3. Piensa qué debe ocurrir cuando todo va bien y cuando ocurre algo inesperado (excepciones). Por ejemplo, cuando hay problemas en la base de datos.
  4. Escribe el test teniendo en cuanta cómo quieres que sea la API. Con la API me refiero al nombre del método, a sus parámetros y al valor a devolver
  5. Haz que compile el código creando las clases y métodos que has escrito en el test. Los métodos deben devolver un valor, el que consideres que es el de por defecto
  6. Comprueba que el test no pasa
  7. Escribe el mínimo código de producción que hace que todos los test pasen. Si, el mínimo.
  8. Si es necesario, mejora el código sin cambiar su comportamiento. O lo que es lo mismo, refactoriza el código
  9. Comprueba que todos los test pasan

 

Notas

Teniendo en cuenta los pasos de la versión extendida, cuando escribimos el test ya hemos decidido qué vamos a hacer, cómo vamos a testearlo y cómo será su API. Luego dirán que con TDD no se piensa 🙂

Para gente con cero experiencia con TDD seguramente lo que les resulte extraño es que al escribir el
primer test, el código no compila y tendremos que crear todas las clases y métodos necesarios para que el código compile. Es una forma diferente de pensar, por lo que es necesario tiempo para asimilarla.

Consejo

Un muy buen consejo de Kent Beck en su libro TDD by example es el mantener una lista de los tests que quieres implementar. Así, cada test o prueba se convierte en una tarea a realizar y cada vez que terminas una es un ciclo TDD terminado y te acerca a la nueva funcionalidad que estás buscando.