Cobertura de código con Surefire y JaCoCo

Cobertura de código con Surefire y JaCoCo

Este breve artículo explica cómo se puede lograr la cobertura del código utilizando el complemento JaCoCo para Maven. La idea detrás de las herramientas de cobertura de código, como JaCoCo, es averiguar qué partes del código fuente se han probado realizando un seguimiento de todas las líneas de código ejecutadas durante una prueba determinada.

Introducción

JaCoCo (Cobertura de código Java) ofrece cobertura tanto de línea como de sucursal. A diferencia de otras herramientas de cobertura de código (como Cobertura, por ejemplo), JaCoCo admite instrumentación de código sobre la marcha. Esto, lo hace ejecutando un agente Java que instrumenta y monitorea la ejecución del código. También se puede configurar para almacenar los datos recopilados de forma local en un archivo o de forma remota a través de TCP. Además, los archivos de datos de informes de varias ejecuciones se pueden combinar fácilmente. La última versión (> 0.6.4.201312101107) del complemento JaCoCo para Maven que es compatible con Java 7, proporciona dos nuevos objetivos, destinados a proporcionar cobertura de código para pruebas de integración también. Con estos objetivos de Maven, se pueden generar fácilmente dos informes de cobertura de código separados, uno para pruebas unitarias y otro para pruebas de integración. Es claramente deseable separar la cobertura del código de prueba de integración y unidad, principalmente porque ambas pruebas abordan diferentes aspectos de las pruebas del sistema. Mientras que las pruebas unitarias simples y simples abordan el problema de la corrección del código, las pruebas de integración abordan el problema de si una prueba del sistema de principio a fin es exitosa o no. Como tal, las pruebas de integración no se enfocan exclusivamente en los detalles de implementación de la clase, como por ejemplo, asegurarse de que se cubran todas las rutas de ejecución de las bifurcaciones, sino que se enfocan en el comportamiento del sistema como un todo. Por el contrario, las pruebas unitarias se enfocan solo en los detalles de implementación de la clase, donde generalmente se emplean afirmaciones o simulaciones para asegurarse de que se cubra cada ruta de ejecución de ramificación. Por lo tanto, se deduce que si se separan los informes de cobertura de prueba de integración y unidad, el código no probado se puede detectar fácilmente, ya que ahora se puede saber exactamente qué se está probando. Más importante aún, no hay riesgo de que una prueba de integración afecte inadvertidamente el resultado de cobertura general de una prueba unitaria, ya que los agujeros en una prueba unitaria son más fáciles de detectar. Si ese es el caso, la prueba unitaria en cuestión se puede reforzar en consecuencia, para cubrir aquellas secciones de código que solo estaban siendo objetivo de las pruebas de integración.

Cobertura de código con Jenkins y Sonar

Jenkins es un servidor de integración continua que somete el código fuente de manera continua y repetida a ciclos de compilación en función de algunos criterios, como, por ejemplo, cuando detecta cambios en el SCM después de que se ha producido una confirmación. Sonar, por otro lado, es una plataforma de inspección continua, generalmente configurada de tal manera que monitorea constantemente la calidad del código procesando los resultados emitidos por una compilación de Jenkins para proporcionar métricas de código. Esto incluye cobertura de código, infracciones e informes de instancia de código duplicado. Por lo general, Sonar se configura como una acción posterior a la compilación de Jenkins, lo que significa que se ejecuta solo después de que Jenkins completa el proceso de compilación. Sin embargo, si lo desea, también se puede configurar para que se ejecute como un complemento de Maven, dentro del proceso de compilación en sí. Cuando se configura como una acción posterior a la compilación de Jenkins, los informes de cobertura de código se pueden enviar a Sonar de dos formas diferentes:
  • Usando su motor integrado para permitir que Sonar lance directamente la ejecución de la cobertura del código de prueba unitaria.
  • Reutilizando informes existentes, previamente generados por herramientas externas como Maven y Ant.
Este artículo opta por utilizar el segundo método y proporciona informes de cobertura de código de Sonar generados previamente a través de una compilación de Maven-Jenkins. La ventaja de este método sobre el primero es que permite a los desarrolladores ejecutar pruebas de cobertura de código manualmente, sin invocar Sonar o requerir una instalación de Sonar localmente. En cambio, el complemento JaCoCo Maven se utiliza para generar informes de cobertura de código durante una compilación. Esto acelera considerablemente el proceso de prueba, ya que no se requiere interacción con Sonar. Además, los informes de cobertura de código generados por JaCoCo durante una compilación de Maven se pueden importar fácilmente a Eclipse a través del complemento EclEmma.

Pruebas unitarias frente a pruebas de integración

Ya mencionamos que la última versión de JaCoCo proporciona objetivos especializados para cubrir las pruebas de integración, por separado de las pruebas unitarias. Del mismo modo, Maven distingue entre los dos y proporciona dos complementos diferentes para ejecutar pruebas de unidad e integración a través de los complementos Surefire y Failsafe, respectivamente. El complemento Surefire está diseñado para ejecutar pruebas unitarias y, de forma predeterminada, está vinculado a la fase de prueba del ciclo de vida de compilación de Maven. El complemento Failsafe, por otro lado, está diseñado para ejecutar pruebas de integración y debe usarse durante la prueba de integración y verificar las fases del ciclo de vida de la compilación de Maven. Ambos complementos generan informes de prueba, que detallan qué pruebas fueron exitosas, cuáles fallaron, el tiempo que tomó ejecutar un conjunto de pruebas y similares.
"Lo que hace que estos dos complementos aparentemente similares sean diferentes es lo siguiente. Si se utiliza el complemento Surefire para ejecutar pruebas de integración y se produce una falla en la prueba, la compilación se detendrá en la fase de prueba de integración, lo que provocará que la compilación falle por completo. Esto es claramente una desventaja, ya que durante una prueba de integración, los sistemas de los que depende una prueba determinada pueden quedar fuera de servicio, lo que hace que la compilación falle innecesariamente. El complemento Failsafe resuelve este problema al no fallar durante la fase de prueba de integración, lo que permite la publicación -Fase de prueba de integración a ejecutar.
Con esto en mente, podemos decidir fácilmente cuándo usar uno y no el otro. Puede encontrar información más detallada sobre estos dos complementos en Maven Proyecto Surefire Testing Framework. En este artículo, se prefiere una configuración de complemento predeterminada estándar a una personalizada, ya que el POM resultante es más pequeño y menos desordenado. Esto significa que para Surefire, las pruebas que coincidan con estos patrones de comodines se incluirán automáticamente:
**/Test*.java
Incluye todos sus subdirectorios y todos los nombres de archivos java que comienzan con "Prueba".
**/*Test.java
Incluye todos sus subdirectorios y todos los nombres de archivos java que terminan con "Prueba".
**/*TestCase.java
Incluye todos sus subdirectorios y todos los nombres de archivos java que terminan con "TestCase". Para Failsafe, se aplica lo siguiente:
**/IT*.java
Incluye todos sus subdirectorios y todos los nombres de archivos java que comienzan con "IT".
**/*IT.java
Incluye todos sus subdirectorios y todos los nombres de archivos java que terminan con "IT".
**/*ITCase.java
Incluye todos sus subdirectorios y todos los nombres de archivos java que terminan con "ITCase".

Surefire contra JaCoCo

¿Dónde entra JaCoCo y en qué se diferencia de Surefire? Lo que debe entenderse es que estos complementos de Maven interactúan con las clases de prueba de manera diferente. Si bien el trabajo de Surefire (o Failsafe) es ejecutar pruebas en una JVM, JaCoCo se engancha a esta JVM y, a través de su agente Java, puede instrumentar y monitorear. Esto significa que JaCoCo no puede funcionar por sí solo, sino que necesita instrumentar el código que ya está siendo ejecutado por un complemento como Surefire. Además, dado que JaCoCo está instrumentando código sobre la marcha, como se mencionó anteriormente, se dirige a las clases de Java y al código de bytes que contiene, donde la cobertura del código se calcula en términos de instrucciones de código de bytes de JVM, y no en líneas fuente. Solo cuando se genera el informe final de JaCoCo se consultan los archivos fuente reales de Java, de modo que se calculan las estadísticas de cobertura de línea adecuadas. Además de eso, para que el informe final de JaCoCo incluya números de línea y muestre el código fuente resaltado, la compilación debe realizarse con los símbolos de depuración habilitados; de lo contrario, la información de línea no estará disponible para el agente de JaCoCo. Cuando se ejecuta como un complemento de Maven, la configuración del agente JaCoCo se prepara invocando los objetivos de preparar-agente o preparar-agente-integración, antes de que se ejecuten las pruebas reales. Esto establece una propiedad denominada argLine que apunta al agente JaCoCo, que luego se pasa como un argumento JVM al ejecutor de pruebas. Una vez que el ejecutor de pruebas crea e inicia la JVM con argLine, la configuración del agente JaCoCo ya está en su lugar y la instrumentación del código se realiza de forma transparente. Tan pronto como finaliza el proceso de JVM, el agente de JaCoCo escribe las estadísticas de cobertura de código en un archivo. Según el objetivo de preparación del agente que se invocó, el archivo creado se denomina jacoco.exec para las pruebas unitarias y jacoco-it.exec para las pruebas de integración de forma predeterminada. Ambos archivos binarios se escriben en el directorio de destino. Los archivos de datos de cobertura de código de JaCoCo se pueden utilizar para generar un informe final, incluidas todas las estadísticas de ejecución, como la cobertura de instrucciones (código de bytes), la cobertura de rama y la cobertura de línea, junto con las líneas de código fuente real resaltadas en consecuencia. Estos informes fáciles de usar se pueden generar utilizando el informe y los objetivos de integración de informes, dependiendo de si se requieren informes de prueba de unidad o de prueba de integración. Los informes están disponibles en archivos HTML, XML y CSV.

Preparación del POM para la cobertura del código

Ahora que se han introducido los complementos básicos de Maven, veremos cómo se puede configurar un POM típico para que ejecute pruebas unitarias y de integración y, al hacerlo, generar informes de cobertura de código separados para cada uno. En primer lugar, es necesario configurar el complemento JaCoCo.

Configuración de JaCoCo

Como se explicó anteriormente, para que JaCoCo pueda instrumentar el código, el agente debe estar preparado antes de la ejecución de las pruebas. La configuración prepara dos agentes, uno para instrumentar pruebas unitarias y el otro para instrumentar pruebas de integración. Los objetivos de JaCoCo relacionados con la prueba de integración solo están disponibles en las versiones 0.6.4.201312101107 y superiores. Además de preparar estos agentes, también configuraremos el complemento JaCoCo para que los informes de cobertura de código se produzcan automáticamente durante la compilación de Maven. Estos informes se crean de forma predeterminada en el directorio target / site / jacoco. La configuración de este complemento se realiza de la siguiente manera: donde maven.jacoco.plugin.version es igual a 0.6.4.201312101107. El objetivo de preparar el agente utilizado para las pruebas unitarias está vinculado de forma predeterminada a la fase de inicialización del ciclo de vida, mientras que el objetivo de preparar el agente de integración está vinculado a la fase del ciclo de vida de la prueba previa a la integración. Ambos objetivos de generación de informes están vinculados a la fase de verificación del ciclo de vida.

Configuración de Surefire y Failsafe

Una vez que la configuración de JaCoCo está en su lugar, los corredores de prueba se pueden configurar a continuación como se muestra a continuación: donde tanto maven.surefire.plugin.version como maven.failsafe.plugin.version son iguales a 2.8.1. El objetivo de prueba del complemento Surefire está vinculado de forma predeterminada a la fase del ciclo de vida de prueba, mientras que los objetivos de prueba de integración y verificación de los complementos Failsafe están vinculados a las fases de prueba de integración y verificación del ciclo de vida, respectivamente. Para resumir, enumeraremos las fases relevantes del ciclo de vida de Maven y lo que realmente ocurre en cada fase a medida que se ejecuta.
  1. initialize: se establece la configuración del agente JaCoCo para las pruebas unitarias, con la propiedad argLine establecida en una configuración de agente similar a:
    -javaagent:/path/to/local-maven-repo/org/jacoco/org.jacoco.agent/0.6.4.201312101107/org.jacoco.agent-0.6.4.201312101107-runtime.jar=destfile=/Workspace/path/to/maven-project/target/jacoco.exec
  2. compilar: ocurre la compilación del código fuente.
  3. prueba: el complemento Surefire lanza pruebas unitarias en JVM separadas, de acuerdo con los patrones de inclusión de prueba definidos "** / Test * .java", "** / * Test.java" y "** / * TestCase.java". Surefire aplica la propiedad argLine a cada JVM, que fue convenientemente configurada durante la fase de inicialización del ciclo de vida por JaCoCo. Tan pronto como la JVM termina, se escribe un archivo de datos JaCoCo jacoco.exec correspondiente, uno para cada módulo Maven.
  4. paquete: ocurre el empaquetado del código fuente compilado y los recursos relacionados.
  5. prueba previa a la integración: la configuración del agente JaCoCo para las pruebas de integración está configurada, con la propiedad argLine establecida en una configuración de agente similar a:
    -javaagent:/path/to/local-maven-repo/org/jacoco/org.jacoco.agent/0.6.4.201312101107/org.jacoco.agent-0.6.4.201312101107-runtime.jar=destfile=/Workspace/path/to/maven-project/target/jacoco-it.exec
  6. prueba de integración: el complemento Failsafe lanza pruebas de integración en JVM separadas, de acuerdo con los patrones de inclusión de prueba definidos "** / IT * .java", "** / * IT.java" y "** / * ITCase.java" . Failsafe aplica la propiedad argLine a cada JVM, que fue convenientemente configurada durante la fase de ciclo de vida de prueba previa a la integración por JaCoCo. Tan pronto como la JVM termina, se escribe un archivo de datos JaCoCo jacoco-it.exec correspondiente, uno para cada módulo Maven.
  7. verificar: Se ejecutan el informe de JaCoCo y los objetivos de integración de informes, procesando los archivos de datos jacoco.exec y jacoco-it.exec respectivamente. El informe de cobertura de código para ambos se genera en el directorio de destino del proyecto.
Los informes de cobertura de código se pueden generar fácilmente ejecutando una compilación completa de Maven (es decir, mvn clean install) o bien ejecutando manualmente el informe de JaCoCo y los objetivos de integración de informes, siempre que estén disponibles jacoco.exec y jacoco-it.exec. De lo contrario, se debe emitir una compilación completa de Maven. Antes de concluir esta sección, conviene señalar una cosa muy importante. Recuerde que los complementos Surefire y Failsafe utilizan la propiedad argLine como un medio para permitir que otros complementos o incluso el usuario inicien la JVM de prueba con argumentos adicionales. El complemento JaCoCo utiliza este hecho a su favor y establece silenciosamente esta propiedad argLine con la configuración de su agente. Solo a través de esta propiedad JaCoCo puede comenzar. Si de alguna manera se sobrescribiera esta propiedad, Surefire y Failsafe comenzarían a ejecutar pruebas sin ejecutar el agente JaCoCo. Sin embargo, puede haber ciertos casos en los que se deban agregar argumentos de línea de comandos adicionales a la JVM iniciada por Surefire o Failsafe. Si este es el caso, el parámetro argLine Surefire / Failsafe debe usarse junto con la propiedad $ {argLine}; de lo contrario, el agente no se iniciará. Esto se muestra a continuación y se aplica tanto a Surefire como a Failsafe. Tenga en cuenta que en el momento de escribir este artículo, este esquema solo funciona con versiones inferiores a 0.6.4.201312101107. Finalmente, si se van a omitir las pruebas, el proyecto Maven debe ejecutarse con -DskipUnitTests o -DskipIntegrationTests. Para omitir ambos, se puede usar la marca -DskipTests. Como se muestra en la configuración de fragmentos de POM anterior, tendemos a utilizar los valores predeterminados proporcionados por todos los complementos. Esto da como resultado un POM más ágil y limpio, basado en el comportamiento estándar documentado en cada uno de los sitios web de los complementos.

Configuración de Sonar como acción posterior a la compilación de Jenkins

Anteriormente, vimos cómo el informe de JaCoCo y los objetivos de integración de informes se utilizan junto con jacoco.exec, jacoco-it.exec y los archivos fuente de la aplicación para producir un informe legible que detalla las estadísticas de cobertura del código. Los informes de JaCoCo no son más que una herramienta que puede procesar estos archivos de datos. Sonar es otra herramienta que puede analizar dichos archivos para generar un informe propio, siempre que esté configurado correctamente. En este artículo, Sonar está configurado en Jenkins como una acción posterior a la compilación. Por lo tanto, se asume que tanto Jenkins como su complemento Sonar están instalados correctamente. También se supone que el complemento Sonar Jenkins está configurado correctamente con una base de datos como MySQL, y que un servidor Sonar activo se refiere a esta base de datos.  Configuración de la base de datos del complemento de Sonar Jenkins
Para configurar el complemento Sonar Jenkins para procesar archivos de datos JaCoCo ya generados, se deben especificar las siguientes propiedades en el cuadro de texto "Propiedades adicionales", debajo de la instalación de Sonar que se está configurando:
  • sonar.dynamicAnalysis=reuseReports
    Configura Sonar para que se base en informes previamente generados externamente. En nuestro caso, estos informes (jacoco.exec y jacoco-it.exec) se generan durante la compilación de Maven que precede a la acción posterior a la compilación de Sonar.
  • sonar.skipDesign=true
    Omite el cálculo de métricas de diseño y dependencias.
  • sonar.java.coveragePlugin=jacoco
    Establece el motor de cobertura de código que se utiliza para JaCoCo.
  • sonar.jacoco.reportPath=target/jacoco.exec
    Establece la ubicación del archivo de informe JaCoCo de prueba unitaria.
  • sonar.jacoco.itReportPath=target/jacoco-it.exec
    Establece la ubicación del archivo de informe JaCoCo de la prueba de integración.
  • sonar.surefire.reportsPath=target/surefire-reports
    Establece la ubicación del directorio de informes de Surefire. Failsafe no es compatible todavía, ya que estos no se presionan ni se muestran en Sonar.
Estas propiedades también se pueden especificar en un POM de aplicación, o un super POM común si se desea. Sin embargo, la razón por la que se especifican en la configuración del complemento Sonar Jenkins es doble. En primer lugar, presumiblemente se normalizarían y adoptarían en toda la organización. En segundo lugar, eliminar dichas propiedades de un POM facilitará la vida del desarrollador, ya que estos detalles están ocultos, sin mencionar que un desarrollador típico no debería estar interesado en si se está utilizando Sonar o no. Además, el POM se desacoplaría del mecanismo de informes, ya que las propiedades relacionadas con Sonar se configuran en otros lugares. Esto no significa que el desarrollador no pueda anular estas propiedades, pero al hacerlo corre el riesgo de romper la generación del informe. Dicho esto, las propiedades de Sonar se pueden manipular, anular y también agregar al POM de la aplicación. Estas propiedades solo deben agregarse cuando realmente se necesiten o cuando sean específicas de la aplicación en cuestión. Dos de esas propiedades son:
  • sonar.exclusions: excluye uno o más archivos o directorios del análisis de Sonar. Se admiten varias entradas separadas por comas.
  • sonar.branch: establece una rama SCM con nombre. Si se nombran de manera diferente, dos ramas del mismo proyecto todavía se consideran proyectos de Sonar diferentes.
Estas propiedades pueden especificarse en un perfil de Maven o simplemente incluirse en la lista de propiedades del proyecto de POM. Para obtener una lista completa de las propiedades de un análisis de sonda (también conocido como parámetros), consulte la Parámetros de análisis de sonda wiki. Finalmente, Sonar también se puede invocar manualmente desde la línea de comando a través de su complemento Maven. Esto evitará por completo el complemento Jenkins Sonar y, en su lugar, interactuará directamente con el servidor Sonar y su base de datos:
mvn clean install sonar:sonar -Plocalhost -Dsonar.host.url=http://localhost:9000 -Dsonar.jdbc.url=jdbc:mysql://localhost:3306/sonar -Dsonar.jdbc.username=root -Dsonar.jdbc.password=abcdefgh -Dsonar.dynamicAnalysis=reuseReports -Dsonar.skipDesign=true -Dsonar.java.coveragePlugin=jacoco -Dsonar.jacoco.reportPath=target/jacoco.exec -Dsonar.jacoco.itReportPath=target/jacoco-it.exec -Dsonar.surefire.reportsPath=target/surefire-reports

Configuración de la sonda

Una vez que todos los datos de cobertura del código de prueba de integración y unidad se interpretan y almacenan en la base de datos de Sonar, las estadísticas del proyecto pueden estar disponibles agregando el widget "Cubierta de prueba de integración" a su tablero. Como de costumbre, el desglose está disponible en este widget. También a nivel de archivo, se puede acceder a la información de cobertura de código detallada dividida por unidad y prueba de integración desde la pestaña "Cobertura", como se muestra a continuación. Ficha Cobertura de código de sonda
Si tuviéramos que comparar las estadísticas de los informes de cobertura de código generados por el Sonar con los generados por el objetivo jacoco: report, inmediatamente notaríamos que los porcentajes varían. Esta diferencia se debe al hecho de que, a pesar de que ambas herramientas consumen el mismo archivo jacoco.exec / jacoco-it.exec, las estadísticas de cobertura se calculan de manera diferente. El complemento jacoco: report basa sus porcentajes en cálculos proporcionales simples, mientras que Sonar emplea una combinación sutil de cobertura de línea y rama. Sin embargo, en una mirada más cercana, tanto JaCoCo como Sonar reportan las mismas cifras en términos de recuento de cobertura de líneas y sucursales (compare las marcas verdes y azules en las figuras a continuación); son sólo los cálculos porcentuales los que varían. Informe de cobertura del código de sonda Informe de cobertura del código JacocoPara obtener una descripción detallada de las fórmulas reales que utiliza Sonar para calcular las cifras de cobertura de código, consulte la Definiciones de métricas de sonda wiki. Las versiones recientes de Sonar incluso combinan los resultados de cobertura de código de las pruebas unitarias y de integración mediante el uso de las propiedades sonar.jacoco.reportPath y sonar.jacoco.itReportPath, con el fin de obtener una cifra de cobertura de código general.

Conclusión

Este artículo presentó la noción de cobertura de código y cómo se puede lograr en Maven utilizando los complementos Surefire y JaCoCo. Luego pasó a describir las diferencias entre las pruebas unitarias y de integración, y propuso un esquema simple con el que los informes de cobertura del código de la prueba unitaria y de integración pueden ser separados por JaCoCo cuando se generan y luego por Sonar cuando se consumen.