Al evolucionar productos software no es raro tener que enfrentarnos a código legacy. Es decir, código heredado que no está cubierto por tests y que incluso puede ser difícil de descifrar. Incluso los equipos más disciplinados y con mejores conocimientos cometen errores de vez en cuando. En esta situación nuestro primer impulso podría ser el de rehacer esa parte de nuevo, pero es algo que deberíamos evitar. No debemos ignorar el riesgo de introducir nuevos errores o pasar por alto piezas de código que no entendemos, pero que seguramente estén ahí por algo. 

Así que en esta ocasión quería compartir una herramienta que siempre me ha resultado muy útil a la hora de hacer frente a estas situaciones: los tests de caracterización.

Qué es un tests de caracterización

Los tests automatizados son una herramienta muy importante, pero no solo para localizar bugs (al menos no directamente). En general, estos tests especifican una meta u objetivo que nos gustaría cumplir o un comportamiento que queremos preservar. En un flujo natural de desarrollo, los tests que especifican se convierten en tests que preservan.

En el caso del código legacy podríamos no tener ningún test que nos apoye a la hora de realizar cambios, por lo que no tenemos forma de verificar que, al tocar algo, estamos preservando los comportamientos que deberíamos

Por eso, la mejor aproximación es crear primero una red de seguridad alrededor de la pieza de código que vamos a tocar para caracterizar el comportamiento actual. Llamaremos a esto un test de caracterización porque caracteriza el comportamiento actual, no el comportamiento ideal que se esperaría del código.

Paper, rules and pen
Benjamin Smith on Unsplash

Cómo escribir tests de caracterización

Escribir tests de caracterización no es especialmente complicado (más allá de las dificultad añadida de testear código legacy). Un posible algoritmo para escribir este tipo de tests podría ser:

  • Toma la pieza de código que vas a tocar y úsala en un entorno de test
  • Escribe una aserción que falle
  • Deja que el propio fallo te diga cuál es el comportamiento real del código
  • Cambia el test para que la aserción corresponda al comportamiento actual del código
  • Repite hasta conseguir una batería de pruebas que te haga sentir más seguro

Lo que intentamos es poner en marcha un mecanismo que nos ayude a localizar bugs más tarde. Es decir, diferencias entre el funcionamiento actual del sistema y el que debería tener. Estos tests no pretenden documentar los comportamientos esperados, sino cómo está funcionando el sistema en realidad.

Si encontramos algo inesperado o extraño al escribir este tipo de tests, es importante dejarlo reflejado. Podría tratarse de un bug. Podemos marcar ese test como sospechoso y trabajar en entender si es un comportamiento esperado o no, y arreglarlo. Pero lo haremos ya con la cobertura y seguridad de los tests automatizados.

Otro punto importante a tener en cuenta es que no estamos escribiendo tests de caja negra. Podemos mirar el código para intentar entender qué comportamientos tiene y cómo caracterizarlos a través de los tests.

Consejos a la hora de usar tests de caracterización

Aquí os dejo también algunos consejos que os pueden ayudar a la hora de decidir qué y cómo testearlo:

  • Buscad piezas de código enmarañadas. Si no entendéis lo que hace un fragmento de código, considerad introducir variables que os ayuden a caracterizarlo. Usadlas para asegurar que determinadas partes del código se ejecutan o qué resultados producen.
  • A medida que descubráis las responsabilidades de una clase o método, buscad formas de plantear tests que fuercen situaciones en las que podría fallar.
  • Pensad en los valores extremos de las entradas.
  • Si encontráis condiciones que deberían ser ciertas a lo largo de todo el ciclo de vida de una clase, se llaman invariantes. Intentad escribir tests para verificarlas.
  • Escribid tests para los fragmentos donde vais a hacer los cambios y plantead tantos casos como sean necesarios hasta que sintáis que entendéis el comportamiento del código.
  • Después del paso anterior, echad un vistazo a los cambios específicos que vais a introducir y escribid tests para ellos.
  • Si estáis intentando extraer o mover alguna funcionalidad, escribid tests para verificar la existencia y conexión de esos comportamientos.

Unas palabras finales

Tocar código legacy siempre es delicado. Y, evidentemente, este tipo de tests solo proporcionan una red de seguridad pero no son infalibles. Aún así, los tests de caracterización pueden proporcionarnos una ayuda muy grande y evitar que nos lancemos a ciegas. Recordemos también que el objetivo al tratar con código legacy debería ser dejarlo un poco mejor, más manejable y más protegido, pero no rehacerlo todo de nuevo.

No he planteado un ejemplo completo para este caso pero, si creéis que puede ser interesante, por favor, pedidlo en los comentarios y estaré encantado de prepararlo. Lo que más me interesaba era contaros la idea de testear lo que existe y el comportamiento actual (no el esperado). Y por mal que planteemos esos tests, siempre son mejor que nada. En mi caso, me costó mucho ponerlos en práctica pero una vez que empecéis a coger soltura veréis la gran ayuda y buenos resultados que ofrecen.

Antes de terminar, quería dar el reconocimiento de todo este conocimiento a Michael Feathers y su libro Working Effectively with Legacy Code. He aprendido mucho de ese libro y os recomiendo echarle un vistazo.

Como siempre, ¡encantado de escuchar vuestras opiniones y conocer vuestras propias experiencias y consejos!