Como testear código legado: primer paso

Estoy empezando a leer “Working effectively with Legacy code” de Michael Feather:

Este libro explica muchas técnicas de cómo trabajar con código legado. Si estas teniendo problemas al respecto te recomiendo que lo compres.

Uno de los primeros pasos para poder testear tu código legado sería el romper la dependencia de nuestra código para que podemos añadir test. Porque uno de los mayores problemas con el código legado es que no podemos añadir test.

Ten en cuenta que la definición que tiene el autor de código legado es código sin test.

Uno de los métodos principales para romper la dependencia que tiene el código se llama crear una subclase y sobrescribir  (subclass and override method)

Este método hace que podamos añadir test en código que no es posible. Convierte código en testable.

 

Vamos a verlo con un ejemplo. Tenemos el siguiente código:

Queremos testear el método getUser de la clase UserService pero si creamos un test unitario como el siguiente:

va a fallar porque lanzará una excepción de tipo RuntimeException.

Ten en cuanto que he añadido esta excepción para simplificar el código. Imaginad que en vez de ser esta excepción accede a una base datos que no podemos acceder desde nuestro ordenador o a internet o al sistema de fichero.

 

Si vemos atentamente el código el origen del problema es que tenemos una dependencia con UserConfigurationService que en vez de estar inyectada (ser parte de los atributos de UserService) se crea por una clase que se llama Factory.

Lo que vamos a hacer es crear un método que contenga la instanciación de UserConfigurationService que tendrá el “scope” protected para que las subclases puedan sobreescribirla.

y en el test vamos a crear una instancia hija de UserService que sobreescriba ese método y que devuelva un mock:

Al poder inyectar el mock podemos hacer que el código cambie su comportamiento sin que toquemos su código. Es lo que Michael Feather llama seam. Lo que estamos haciendo con este método es añadir un seam a nuestro código.

Como has podido ver este método es simple pero muy potente porque en muchas casos vamos a poder utilizarlo para poder añadir dobles de test.

No siempre es bueno o posible el poder utilizar este método de romper las dependencias de tu código pero mi experiencia me dice que en la mayoría de los casos es la mejor opción.

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.

 

Python y Google Code Jam

Imagen de https://i.ytimg.com/vi/Gaj54O90Yak/maxresdefault.jpg

Este es el segundo artículo que hablo sobre Python. Si quieres leer el primer artículo está aquí.

Terminé el curso de Python en CodeAcademy:

https://www.codecademy.com/courses/introduction-to-python-6WeG3

Lo recomiendo. Es muy práctico, fácil de seguir y con buen contenido. El único pero que le pongo es que dentro de poco lo van a cerrar. No recuerdo cuando pero leí que en unos meses van a quitar el curso. Una pena.

Durante el curso estuve pensando lo bueno que sería python para el Google Code Jam porque su forma de manejar cadenas, caracteres, números binarios, listas y arrays es muy intuitiva. Estos elementos se utilizan mucho en el code jam.

Google Code Jam es un competición de programación. Todos los ejercicios se basan en generar cierta lógica que cree un fichero de salida a partir de un fichero de entrada.
Llevo años compitiendo utilizando Java y creo que ya era hora de empezar a usar otro lenguaje.

La competición comienza el 7 de Marzo pero como tengo poca experiencia con Python decidí el hacer algunos ejercicios de prácticas.
En CodeAcademy no necesitaba tener configurado nada en mi ordenador porque el código se ejecutaba en el propio navegador. Pero para la competición me iba a ser útil el tener un entorno de trabajo de Python.
Así que me he configurado un entorno que no es el óptimo pero que es mas que suficiente para lo que quiero. Que es el poder crear y ejecutar pequeños programas de python en mi ordenador.
Tengo instalado python 2.7 y Atom en mi portátil. Dentro de Atom tengo cuatro plugins: atom-python-run, linter, linter-pylama y python-tools. El primer plugin es para poder arrancar ficheros de python en Atom, los de linter para el formateo de código (estilo lint en Javascript) y el último para todo lo demás relativo al lenguaje.

Después me puse a resolver problemas de años pasados de la competición y está siendo muy divertido. He terminado cuatro y estoy con el quinto.
Por si a alguien está interesado a continuación aparece una lista de los problemas y mis soluciones:

Ahora estoy con Revenge of the Pancakes que se me está atascando porque aunque tengo una solución esta no coincide con el esperado.