Definición de code coverage
La cobertura de código, en inglés llamada “code coverage” o también “test coverage”, es una medida usada en el desarrollo de software para describir el grado al cual el código fuente de un software ha sido probado.
Los usos de la cobertura de software son por ejemplo:
- Encontrar secciones de código fuente que no han sido ejecutadas por un conjunto de pruebas de software, y así escribir pruebas (test cases) adicionales para incrementar el porcentaje de cobertura
- Identificación de pruebas redundantes, es decir, pruebas que ejercitan las mismas secciones de código fuente de un programa, con el objetivo de optimizar las pruebas que se le realizan al mismo
- Determinar una medida cuantitativa de cobertura de código, la cual es indirectamente una medida de la calidad de un programa
Aun cuando podría resultar evidente, se debe enfatizar que:
- El porcentaje de cobertura mide la cobertura de lo que ha sido escrito (programado), es decir, la cobertura de software no puede decir absolutamente nada acerca del código que aun no ha sido escrito
- Una prueba de cobertura no puede ser usada para identificar si una función especificada del software no ha sido implementada o ha sido omitida. Un porcentaje de cobertura tan solo “habla” del código ya ha sido escrito
- Tener una un porcentaje alto de cobertura no significa que la aplicación es confiable
¿Porqué code coverage?
Uno de los retos en el desarrollo de software es saber cuando el producto tiene la suficiente calidad para ser publicado. La cobertura de código es una medida de calidad, que al indicar que porcentaje del código del software ha sido ejecutado al correr un conjunto de pruebas, da una idea de que tan intensamente ha sido probado el software.
Hewlett-Packard condujo una investigación en la cual se concluyó que probar un software sin el código fuente lleva a que solo alrededor del 55% del código sea ejecutado. [1] Es decir, tener un ingeniero de pruebas que use el software como lo haría un usuario final, con el objetivo de verificar que este se comporte de la forma esperada, llevaría a que solo 55% del código fuente del programa fuera ejercitado con las pruebas hechas por el ingeniero.
¿Qué porcentaje de cobertura debería de ser la meta para el lanzamiento de un software?
Esta es una pregunta muy complicada, ya que la respuesta depende de muchos factores, como por ejemplo:
- La cantidad de código del proyecto
- La existencia de unit tests
- La posibilidad de sumar la cobertura generada con los diferentes tipos de pruebas (unit tests + integration tests)
- El porcentaje que código que está cubierto con las pruebas existentes
- La existencia de un sistema automatizado que corra las pruebas y genere información acerca del porcentaje de cobertura de código
- La disciplina, formación y profesionalismo de los desarrolladores de software e ingenieros de pruebas
- Presupuesto
- Tiempo disponible hasta el lanzamiento
- El costo que un error en el código pudiese tener una vez que el software ya ha sido lanzado
- La etapa en la cual se encuentra el software (desarrollo, mantenimiento, etc)
Dependiendo de esos y otros factores se debe seleccionar un porcentaje de cobertura que tendrá que ser el objetivo a alcanzar antes de que el software pase a un estado productivo.
Cuando un proyecto ha existido por años, la cantidad de código es grande, muchos desarrolladores han ido y venido, y mucho código ha quedado huérfano y nadie se siente responsable de él, cuando no existen unit tests, si no que únicamente se ha probado las funcionalidades del software conforme han sido implementadas, cuando no hay experiencia en la obtención de medidas de calidad del software, cuando la automatización de los procesos durante el desarrollo del software es deficiente, entre muchos otros inconvenientes, es entonces el imaginarse establecer de la noche a la mañana un porcentaje de cobertura de código mínimo, aun siendo bajo (digamos 25%) es una ilusión, y lo mejor es entenderlo, aceptarlo y mirar hacia el futuro.
Antes de establecer un porcentaje de cobertura mínimo, sería más importante tomar medidas para poco a poco:
- hacer posible la implementación de unit tests
- hacer posible la obtención del porcentaje de cobertura de los unit tests
- desarrollar un sistema que automáticamente corra los unit tests cada vez que se introduce código nuevo, y que automáticamente genere estadísticas de la cobertura
- entrenar a los desarrolladores en la implementación de unit tests
- hacer conciencia entre los desarrolladores de la importancia de tener unit tests para todo el código nuevo que vaya surgiendo
- implementar guardas de seguridad que no permitan que el porcentaje de cobertura baje cuando se introduce código nuevo
- dedicar sprints/increments enteros al desarrollo de unit tests con el objetivo de subir el porcentaje de cobertura
- subir gradualmente el porcentaje mínimo permitido para introducir código nuevo
Cuando en cambio, el proyecto es nuevo, y la cantidad de código es pequeña, cuando aun no hay una fecha establecida de lanzamiento, cuando las características del software aun no estan plenamente definidas, cuando los desarrolladores estan motivados, cuando existe experiencia en la obtención de cobertura de código, cuando hay experiencia entre los programadores en la implementación y desarrollo de unit tests, cuando se cuenta con personal con conocimientos de “integración continua” empleando gitlab o Jenkins, entonces vale la pena desde los primeros días del proyecto establecer como mínimo un porcentaje alto de cobertura de código (>75%) a través de los unit tests, y dependiendo de la importancia de que el software no falle (por ejemplo software que se va a usar en la industria, donde un fallo sería muy costoso e incluso peligroso), ir subiendo el porcentaje de cobertura mínimo hasta alcanzar el 100%.
Reflexión
Aun cuando un porcentaje alto de cobertura de código da una muy buena sensación acerca de la buena funcionalidad del software, esto no es del todo cierto y no se debe tomar como el único parámetro para decidir si un software está listo para ser lanzado. Se debe entender que incluso tener 100% de cobertura no implica que la arquitectura del software sea buena.
Los unit tests tienen muchas ventajas, entre otras:
- son relativamente fáciles de desarrollar
- se pueden desarrollar en paralelo mientras se desarrolla el producto (Test-driven development)
- se pueden ejecutar gran cantidad de test cases (miles) en cuestión de segundos
- es muy fácil probar condiciones de frontera
- se puede inyectar código o emular condiciones a través del uso de mocks
- es posible probar condiciones de fallo, lo que muchas veces es imposible o sería muy costoso realizar en el producto final
Pero los unit tests no son suficientes para garantizar que todos las funcionalidades necesarias de un software han sido implementadas. Además los unit tests tampoco sirven para garantizar que la interacción entre las clases del proyecto sea la adecuada. Para eso sirven las pruebas de integración (integration tests), donde se prueba la funcionalidad del software como un producto total, y estas pruebas son mucho más efectivas para darse cuenta de las limitaciones del software y para encontrar molestas fallas que el usuario enfrentaría si durante el desarrollo solo se pusiera enfoque a lograr 100% de cobertura a través de unit tests.
La cobertura de código puede usarse de manera muy efectiva para:
- identificar código para el cual no existe una prueba que lo ejercite, y entonces escribir una prueba (un test case) para que ese código sea ejercitado.
- identificar código muerto, es decir, código que no es posible bajo ninguna circunstancia ejecutarlo, y que solo es una fuente de distracción a la hora de implementar nuevos features, buscar un defecto, o hacer cualquier tipo de cambio en el código. Cuando se identifica plenamente algún fragmento de código muerto lo mejor es borrarlo.
- motivar y/o forzar a los desarrolladores a escribir unit tests suficientes para alcanzar un porcentaje de cobertura mínimo cuando se introducen nuevas clases al proyecto
Finalmente vale la pena mencionar que existen múltiples niveles de cobertura, algunos de ellos, de acuerdo al nivel de exhaustividad de prueba reflejado, y ordenados de menor a mayor, son:
- porcentaje de cobertura del proyecto (project coverage)
- porcentaje de cobertura por archivo (file coverage)
- porcentaje de cobertura de funciones (function coverage)
- porcentaje de cobertura de lineas de código (line coveage/statement coverage)
- porcentaje de cobertura de ramas (branch coverage)
En este artículo cuando se habla de cobertura de código se piensa principalmente en “line coverage”, pero tener 100% de cobertura de ramas implicaría que el código fuente ha sido probado en más condiciones que si se tuviese 100% de cobertura de lineas de código.