Pero antes de hablar de la importancia de hacer un seguimiento de la cobertura de código en el nuevo código y el cambio de paradigma, empecemos por mencionar algo que es probablemente una de las causas fundamentales del desacuerdo con Nicolas: las herramientas de análisis estático y dinámico nunca, jamás, lograrán decir "su código está limpio, bien diseñado, libre de errores y con un alto grado de mantenimiento". Las herramientas de un análisis estático y dinámico sólo son capaces de decir "Por lo que sé, no he encontrado nada malo en el código.". De este modo, esto también se extiende para cualquier métrica o técnica utilizada para entender y analizar el código fuente. Un alto nivel de cobertura no es una garantía de calidad del producto, pero un bajo nivel sí es una clara indicación de testeo insuficiente.
Para Nicolas, el seguimiento de la cobertura de código es inútil porque en algunos casos, las pruebas unitarias que conducen a aumentar la cobertura de código pueden ser muy cutres. Por ejemplo, las pruebas unitarias no pueden contener aserciones, o pueden cubrir todas las ramas pero no todos los parámetros de entrada posibles. Para solucionar estas limitaciones, Nicolas dice que la única solución es hacer algunas pruebas de mutación mientras se mide la cobertura de código (véase, por ejemplo,
pitest.org para Java) y así garanitzar que las pruebas unitarias son sólidas. Ok, pero si realmente quieres tocar el Grial, ¿esto es suficiente? ¡Para nada! Puedes tener un 100% de cobertura de código y algunas muy sólidas pero… pueden ser pruebas unitarias totalmente imposibles de mantener. Las pruebas de mutación no proporcionan, de ninguna manera, el saber cómo de “unitarias” son tus pruebas unitarias, o si hay mucha redundancia entre ellas.
Resumiendo, cuando te preocupas por la capacidad de mantenimiento, fiabilidad y seguridad de tu aplicación, se puede y debe invertir tiempo y esfuerzo para llegar a algunos niveles de madurez más altos. Pero si esperas hasta encontrar la solución definitiva para empezar, nunca sucederá. Además, los niveles de madurez deberían alcanzarse progresivamente:
- No tiene mucho sentido preocuparse por la cobertura de código si no hay un contexto de integración continua.
- No tiene mucho sentido preocuparse por las pruebas de mutación si sólo el 5% del código fuente está cubierto por pruebas unitarias.
- Etc.
Y aquí ni siquiera menciono el esfuerzo extra que conlleva la ejecución de las pruebas de mutación y el análisis de los resultados. Pero no nos vayamos por las ramas: la mutación es una gran técnica y os animo a que lo probéis en: http://pitest.org/ y el
SonarQube Pitest Plugin, creado por
Alexandre Victoor. Simplemente digo que, como punto de partida, las pruebas de mutación son ya una técnica demasiado avanzada.
Los desarrolladores quieren aprender
Hay un segundo punto de desacuerdo con Nicolas: ¿deberíamos confiar en que los desarrolladores quieren progresar? Si la respuesta es que NO, puede que pasemos toda una vida luchando con ellos y siempre haciendo su vida más difícil. Obviamente, siempre encontrarás algunos desarrolladores reacios, retrasando el trabajo y que no se preocupan en absoluto por la calidad y la fiabilidad del código fuente. Pero prefiero dirigirme a la gran mayoría de los desarrolladores deseosos de aprender y progresar. Para esa gran mayoría de los desarrolladores, el objetivo es siempre hacer la vida más divertida en lugar de hacer que sea más difícil. Así que, ¿cómo contagias a esos desarrolladores con ganas de aprender el deseo de hacer pruebas unitarias?
Al iniciar el desarrollo de una aplicación de pruebas de unidad desde cero puede ser bastante fácil. Pero cuando mantienes una aplicación con 100.000 líneas de código y sólo el 5% está cubierto por las pruebas unitarias, puede que te deprimas rápidamente. Y, obviamente, la mayoría de nosotros estamos tratando con código heredado. Cuando estás empezando tan atrás, puede requerir años el llegar a una cobertura total de pruebas unitarias del 90%. Así que para esos primeros años, ¿cómo vas a reforzar la práctica? ¿Cómo te vas a asegurar que en un equipo de 15 desarrolladores todos ellos juegan el mismo juego?
Hemos suspendido durante muchos años
En efecto, estuvimos bloqueados con una cobertura de código del 60% de la plataforma y no fuimos capaces de progresar. Por suerte,
David Gageot se incorporó al equipo por aquél entonces y las cosas fueron bastante sencillas para él: cualquier nueva pieza de código debería tener una cobertura de al menos un 100% :) Así fue y eso es lo que hizo. Desde entonces decidimos establecer un umbral de calidad con un simple pero efectivo criterio: cuando liberamos una nueva versión de cualquier producto, la cobertura de ese código nuevo o actualizado no puede ser menos de un 80%. Sí lo es, entonces la petición de liberación de versión se rechaza. Así fue, eso es lo que hicimos, y empezamos finalmente a volar. Un año y medio después, la cobertura de código de la plataforma SonarQube es 82 – 84% en el conjunto de productos de SonarSource (400.000 líneas de código y 20.000 pruebas unitarias).
La cobertura en código nuevo/modificado cambia las reglas del juego
Y es bastante sencillo entender por qué:
- Sea cual sea tu aplicación, e independientemente de si es heredada o no, el umbral de calidad es siempre el mismo y no evoluciona con el tiempo: sólo haz la cobertura en tus nuevas o cambiadas líneas de código mayores que X%.
- Ya no hay necesidad de mirar la cobertura de código global o la Deuda Técnica heredada. ¡Olvídalo y deja de deprimirte!
- Como cada año el X% de tu cobertura de código total evoluciona (en Google, por ejemplo, supone el 50%), tener cobertura en código modificado significa que, incluso sin prestar atención a la cobertura total, ésta crecerá muy rápido simplemente como un “efecto secundario”.
- Si una parte de la aplicación no está cubierta en absoluto por las pruebas unitarias, pero no ha evolucionado durante los últimos 3 años, ¿por qué debería invertir esfuerzo en aumentar la capacidad de mantenimiento de esa parte del código? No tiene sentido. Con este enfoque, podrás empezar a cuidar de ese código si, y sólo si, un día se tienen que hacer algunos cambios funcionales. En otras palabras, el coste para arrancar este proceso es pequeño. No hay necesidad de parar un proyecto y hacer a todo el equipo trabajar X meses sólo para reembolsar la deuda técnica heredada.
- Los nuevos desarrolladores no tienen otra opción que jugar al juego desde el primer día, porque si comienzan a incluir piezas de código no cubiertas, el feedback llega en cuestión de horas, y en cualquier caso ese nuevo código nunca entrará en producción.
Este nuevo enfoque para hacer frente a la Deuda Técnica es parte de este cambio de paradigma y que se explica en nuestro libro blanco "Continuous Inspection". En otra entrada de blog seguiremos explicando cómo hacer fácilmente un seguimiento de cualquier tipo de deuda técnica con este enfoque, no sólo de la deuda relacionada con la falta de cobertura de código. Y gracias Nicolas Frankel por seguir alimentando este debate abierto.