Testing unitario y el factor tiempo

No hace mucho que estuve involucrado en un proyecto donde la importancia del testing unitario fue cayendo hasta pasar de ser un requisito a un extra. Cada semana la exigencia por parte del cliente aumentaba, y el factor tiempo acabó imponiéndose de tal manera que el equipo terminamos por sentir que cualquier cosa que no fuese desarrollo puro era una pérdida de tiempo. Y esto nos encaminó sin remedio a un proyecto inestable (casi intocable en algunos puntos), impregnado de esa magia que tienen las cosas que funcionan o se rompen sin saber muy bien por qué.

Parece bastante probable que a corto plazo se seguirá apostando en muchos casos más por la velocidad que por la calidad (sin duda, en gran parte, porque se desconocen los riesgos). Pero esto no significa que nosotros, como desarrolladores, abandonemos por completo la responsabilidad de probar nuestro código. Esto es más fácil decirlo que hacerlo, lo sé. Pero debemos partir de la base de que un test es mejor que ningún test, y poco a poco ir añadiendo tests hasta cubrir al menos aquel código que gestiona la lógica fundamental de nuestro proyecto.

No hay que asustarse con el reto. No tenemos que alcanzar siempre el 100% de cobertura de tests. El equipo de desarrollo conoce perfectamente cuáles son los bloques clave del proyecto (que normalmente suelen coincidir con aquellas partes del código que consideramos más delicadas y menos comprensibles para los miembros nuevos del equipo). Son estas partes las que primero deberíamos testear, repasando para ello las reglas que nos llevaron a escribir ese código, y al mismo tiempo refactorizando hasta llegar a un punto de lectura óptimo.

Aunque también es cierto que no se debe pasar por alto que nuestras pruebas unitarias forman un conjunto de clases con vida propia, que se debe mantener y cuidar, y que requiere su tiempo. Esto no es algo que se deba ocultar a nuestros clientes, pero también habrá que resaltarles que este tiempo será muy inferior al que en caso contrario habrá que invertir en el futuro, cuando el proyecto se convierta en algo extremadamente frágil, que no puede crecer, ni refactorizarse con seguridad, y tengamos que reescribir ciertas partes del proyecto por completo (si no el proyecto entero).

Escribir pruebas unitarias es importante porque reduce el miedo a refactorizar el código de producción, aumentando con ello la durabilidad del producto. Además, el testing unitario a menudo nos encamina a un código de más calidad:

  • Es muy difícil (y a veces incluso imposible) testear una clase sin proveer adecuadamente sus dependencias. Si un método oculta la instanciación de un objeto, el test dará resultados inesperados debido a que dicha instancia es incontrolable. Cumplir con el patrón de inyección de dependencias nos ayudará a modularizar nuestro sistema y a invertir la dependencia (depender de abstracciones, no de objetos concretos). En un artículo anterior ya he hablado sobre el uso de Dagger y sus beneficios.
  • Por otro lado, el testing unitario nos ayuda a analizar de forma más concreta el objetivo de una función. Cuando añadamos un test, pongámosle un título que hable el lenguaje de negocio, que detalle con claridad el requisito del cliente. A menudo este segundo análisis es el que nos ayuda a colocar cada cosa en su sitio, a revelar el acoplamiento de responsabilidades y, en resumen, a cumplir el principio de responsabilidad única.

En definitiva, un proyecto que no puede mejorar, es un proyecto destinado a morir. El código que escribimos hace un año, hoy nos parece obsoleto, en el mejor de los casos requiere una limpieza. El precio a pagar por un proyecto que no se puede refactorizar es demasiado alto. El testing unitario nos facilita precisamente esto, ejerciendo como malla de seguridad de nuestro código de producción, y asegurándonos que tras cada cambio realizado, nuestro código sigue haciendo lo que tiene que hacer.