Cobertura en un proyecto maven multimódulo con JaCoCo

0
3434

En este tutorial vamos a explorar como tener la cobertura de nuestro código en un proyecto multimódulo usando Jacoco y maven. Iremos desde un único proyecto con un módulo a un proyecto multimódulo.

Índice de contenidos

1. Introducción

Tener la cobertura de un proyecto es una métrica más que podemos usar para ver que partes de nuestro código están cubiertas por tests. Según se puede ver a continuación, los microservicios están dejando de ser moda y puede que nos demos cuenta de que no siempre está bien usarlos.

Gartner Hype Cycle for Software Engineering 2022
Gartner Hype Cycle for Software Engineering 2022

Lo que aún no he visto es que los conceptos de encapsulación, modularidad y abstracción se dejen de usar. Con un monolito modular, en el black friday de 2021 Shopify alcanzó unos números espectaculares.

Hace mucho tiempo, Martin Fowler nos dijo que MonolithFirst, pero a lo mejor no le hicimos mucho caso.
En este tutorial vamos a intentar ver la evolución de un proyecto de un solo módulo, a separarlo en varios de manera que si algún día nos pasa y tenemos que crecer en distintos módulos sepamos como hacerlo y siempre tengamos métricas.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Lenovo t480 (1.80 GHz intel i7-8550U, 32GB DDR4).
  • Sistema Operativo: Sistema Operativo: Zorin OS 16.1
  • Entorno de desarrollo: IntelliJ IDEA 2022.2.2 (Ultimate Edition)
  • Apache Maven 3.8.6
  • Java version: 17.0.4, vendor: Eclipse Adoptium

3. ¿Que es la cobertura?

La cobertura del código es una métrica utilizada para ver las líneas de código que han sido cubiertas por los tests. Esta métrica también nos dice si se han cubierto todas las posibles bifurcaciones en el código, por lo que nos puede ayudar a detectar casos de prueba que no hemos implementado. La métrica tiene que ser interpretada como una métrica más, ya que puede ser que no tengamos los casos de prueba más adecuados. Para otro tipo de métricas como pueden ser los tests de mutación podemos verlos aquí, aquí o aquí. Robert C. Martin, en su libro Clean Code ya nos decía que cuanta más cobertura tuviéramos, menos miedo.

The higher your test coverage, the less your fear.

Robert C. Martin, Clean Code (2009), 124.

4. JaCoCo en un único proyecto

Vamos a empezar desde lo más básico hasta conseguir la cobertura en un proyecto multimódulo. El ejemplo va a ser una calculadora básica donde veremos como tener cobertura.

4.1. La aplicación

Nuestro ejemplo va a ser una calculadora muy sencilla, donde la clase calculadora es:

Calculator.java
package es.dionisiocortes;

public class Calculator {
    public int addition(final int firstNumber, final int secondNumber) {
        return firstNumber + secondNumber;
    }

    public int subtraction(final int firstNumber, final int secondNumber) {
        return firstNumber - secondNumber;
    }

    public int multiplication(final int firstNumber, final int secondNumber) {
        return firstNumber * secondNumber;
    }

    public int division(final int firstNumber, final int secondNumber) {
        return firstNumber / secondNumber;
    }

}

Tenemos nuestros tests:

CalculatorTest.java
package es.dionisiocortes;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

public class CalculatorTest {
    private final Calculator sut = new Calculator();

    @Test
    public void addition_should_add_two_numbers() {
        assertEquals(4, sut.addition(2, 2));
    }

    @Test
    public void subtraction_should_subtract_second_number_from_first_number() {
        assertEquals(6, sut.subtraction(8, 2));
    }

    @Test
    public void multiplication_should_multiply_two_numbers() {
        assertEquals(32, sut.multiplication(8, 4));
    }

    @Test
    public void division_should_divide_first_number_with_the_second() {
        assertEquals(5,sut.division(40, 8));
    }
}

Y el proyecto configurado de la siguiente manera:

pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>es.dionisiocortes</groupId>
  <artifactId>jacoco</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>jacoco</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <junit-jupiter.version>5.9.1</junit-jupiter.version>
    <maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>${junit-jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>${junit-jupiter.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>${maven-surefire-plugin.version}</version>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

Si ejecutamos:

mvn clean test

Tenemos:

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running es.dionisiocortes.CalculatorTest
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.044 s - in es.dionisiocortes.Calculator
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.794 s
[INFO] Finished at: 2022-10-01T11:37:00+02:00
[INFO] ------------------------------------------------------------------------

Bien, 4 tests ejecutados, sin fallos ni nada, pero ¿Cómo sabemos que estamos cubriendo todo el código? Ahora vemos como añadirle cobertura.

4.2. Cambios en el pom.xml

Añadir JaCoCo es tan sencillo como añadirlo al pom.xml de esta forma:

pom.xml
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>prepare-agent</id>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
          </execution>
          <execution>
            <id>report</id>
            <phase>test</phase>
            <goals>
              <goal>report</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

Estamos diciéndole al plug-in que en la fase de test nos haga los informes. Con esto ya lo tendríamos, el pom.xml entero se quedaría así:

pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>es.dionisiocortes</groupId>
  <artifactId>jacoco</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>jacoco</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <junit-jupiter.version>5.9.1</junit-jupiter.version>
    <jacoco-maven-plugin.version>0.8.8</jacoco-maven-plugin.version>
    <maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>${junit-jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>${junit-jupiter.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.jacoco</groupId>
          <artifactId>jacoco-maven-plugin</artifactId>
          <version>${jacoco-maven-plugin.version}</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>${maven-surefire-plugin.version}</version>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>prepare-agent</id>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
          </execution>
          <execution>
            <id>report</id>
            <phase>test</phase>
            <goals>
              <goal>report</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Ahora si ejecutamos

mvn clean test
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running es.dionisiocortes.CalculatorTest
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.095 s - in es.dionisiocortes.Calculator
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- jacoco-maven-plugin:0.8.8:report (report) @ jacoco ---
[INFO] Loading execution data file /home/dio/dev/jacocomultimodulo/jacoco/target/jacoco.exec
[INFO] Analyzed bundle 'jacoco' with 1 classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.543 s
[INFO] Finished at: 2022-10-01T13:03:22+02:00
[INFO] ------------------------------------------------------------------------

Vemos la sutil diferencia que el plug-in de JaCoCo que hemos añadido se ha ejecutado y nos ha generado un informe. Este informe está en la carpeta target/site/index.html y se ve así:

Primer ejemplo de informe de JaCoCo
Primer ejemplo de informe de JaCoCo

5. JaCoCo multimódulo

Ahora que ya hemos visto como tener la cobertura en un único módulo, vamos al siguiente nivel, vamos a ver como tener la cobertura en varios módulos. Vamos a imaginar que la calculadora es un éxito tan grande que vamos a partirlo en módulos, uno que hace sumas y restas y otro que hace multiplicaciones y divisiones. Esto nos permitirá el día de mañana poder hacer despliegues separados, tener equipos trabajando en módulos distintos sin que se molesten demasiado etc.

5.1. Cambios en la aplicación

Primero vamos a tener dos módulos, uno para las sumas y las restas, otro para las multiplicaciones y divisiones. El código es el mismo pero separado. El pom.xml de la aplicación que contiene los módulos quedaría así:

pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>es.dionisiocortes</groupId>
  <artifactId>jacoco</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>
  <modules>
    <module>AdditionAndSubtraction</module>
    <module>MultiplicationAndDivision</module>
  </modules>

  <name>jacoco</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <junit-jupiter.version>5.9.1</junit-jupiter.version>
    <jacoco-maven-plugin.version>0.8.8</jacoco-maven-plugin.version>
    <maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
  </properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>${junit-jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>${junit-jupiter.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.jacoco</groupId>
          <artifactId>jacoco-maven-plugin</artifactId>
          <version>${jacoco-maven-plugin.version}</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>${maven-surefire-plugin.version}</version>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>prepare-agent</id>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Vemos que se le ha quitado la parte que generaba los informes porque no es relevante tener los HTML por módulo ya que se van a agregar, lo que importa es que JaCoCo se lance y deje los resultados.

El módulo de las sumas y restas quedaría así:

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jacoco</artifactId>
        <groupId>es.dionisiocortes</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>

    <artifactId>AdditionAndSubtraction</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit-jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit-jupiter.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
AdditionAndSubtraction.java
package es.dionisiocortes;

public class AdditionAndSubtraction {
    public int addition(final int firstNumber, final int secondNumber) {
        return firstNumber + secondNumber;
    }

    public int subtraction(final int firstNumber, final int secondNumber) {
        return firstNumber - secondNumber;
    }

}

Los tests:

AdditionAndSubtractionTest.java
import es.dionisiocortes.AdditionAndSubtraction;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class AdditionAndSubtractionTest {
    private final AdditionAndSubtraction sut = new AdditionAndSubtraction();

    @Test
    public void addition_should_add_two_numbers() {
        assertEquals(4, sut.addition(2, 2));
    }

    @Test
    public void subtraction_should_subtract_second_number_from_first_number() {
        assertEquals(6, sut.subtraction(8, 2));
    }
}

El módulo de las multiplicaciones y divisiones quedaría así:

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jacoco</artifactId>
        <groupId>es.dionisiocortes</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>

    <artifactId>MultiplicationAndDivision</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
MultiplicationAndDivision.java
package es.dionisiocortes;

public class MultiplicationAndDivision {
    public int multiplication(final int firstNumber, final int secondNumber) {
        return firstNumber * secondNumber;
    }

    public int division(final int firstNumber, final int secondNumber) {
        return firstNumber / secondNumber;
    }

}

Los tests:

MultiplicationAndDivisionTest.java
import es.dionisiocortes.MultiplicationAndDivision;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class MultiplicationAndDivisionTest {
    private final MultiplicationAndDivision sut = new MultiplicationAndDivision();

    @Test
    public void multiplication_should_multiply_two_numbers() {
        assertEquals(32, sut.multiplication(8, 4));
    }

    @Test
    public void division_should_divide_first_number_with_the_second() {
        assertEquals(5,sut.division(40, 8));
    }
}

Ahora si ejecutamos

mvn clean test
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running MultiplicationAndDivisionTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.09 s - in MultiplicationAndDivisionTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- jacoco-maven-plugin:0.8.8:report (report) @ MultiplicationAndDivision ---
[INFO] Loading execution data file /home/dio/dev/jacocomultimodulo/jacoco/MultiplicationAndDivision/target/jacoco.exec
[INFO] Analyzed bundle 'MultiplicationAndDivision' with 1 classes
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for jacoco 1.0-SNAPSHOT:
[INFO]
[INFO] jacoco ............................................. SUCCESS [  0.248 s]
[INFO] AdditionAndSubtraction ............................. SUCCESS [  2.304 s]
[INFO] MultiplicationAndDivision .......................... SUCCESS [  1.032 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.659 s
[INFO] Finished at: 2022-10-01T17:27:48+02:00
[INFO] ------------------------------------------------------------------------

Todo ha ido correcto, hemos podido ver que se construyen todos los módulos y pasan los tests. Pero … ¿Donde están los informes?

5.2. Que ha pasado con la cobertura

Ahora que tenemos distintos módulos, no tenemos un único informe, tenemos uno por módulo (aunque quitamos del plug-in su generación en HTML). Esto es algo que no suele ser lo deseado, ya que no es viable ir recorriendo todos los módulos viendo que tal esta cada uno, queremos un único sitio donde podamos ver el estado de la aplicación. Esto lo conseguiremos haciendo un módulo que agregue todos los informes de los módulos en uno solo. Desde la versión 0.7.7 de JaCoCo esto es posible, ya que nos ofrece el goal report-aggregate. De hecho como habéis visto lo anterior era la parte fácil del tutorial y esta será la interesante, pero siempre está bien ponernos en situación.

5.3. Nuevo módulo de agregación

Este nuevo módulo se llamará jacoco-report-aggregate. El módulo tiene que:

  • Tener el plug-in de JaCoCo con el goal report-aggregate.
  • Tener los módulos de los que queremos agregar los informes como dependencias.
  • Poner la propiedad maven.deploy.skip a true (o a releases, snapshots o false según sea conveniente).
  • Dejar el packaging a jar aunque se vea un warning como este: [WARNING] JAR will be empty – no content was marked for inclusion!. Si no se deja así no generara el informe.

El nuevo modulo tiene el siguiente pom.xml:

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jacoco</artifactId>
        <groupId>es.dionisiocortes</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>

    <artifactId>jacoco-report-aggregate</artifactId>

    <properties>
        <maven.deploy.skip>true</maven.deploy.skip>
    </properties>

    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>AdditionAndSubtraction</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>MultiplicationAndDivision</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>report-aggregate</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report-aggregate</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Ahora si ejecutamos

mvn clean test
[INFO] --- jacoco-maven-plugin:0.8.8:report-aggregate (report-aggregate) @ jacoco-report-aggregate ---
[INFO] Loading execution data file /home/dio/dev/jacocomultimodulo/jacoco/AdditionAndSubtraction/target/jacoco.exec
[INFO] Loading execution data file /home/dio/dev/jacocomultimodulo/jacoco/MultiplicationAndDivision/target/jacoco.exec
[INFO] Analyzed bundle 'AdditionAndSubtraction' with 1 classes
[INFO] Analyzed bundle 'MultiplicationAndDivision' with 1 classes
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for jacoco 1.0-SNAPSHOT:
[INFO]
[INFO] jacoco ............................................. SUCCESS [  0.216 s]
[INFO] AdditionAndSubtraction ............................. SUCCESS [  2.301 s]
[INFO] MultiplicationAndDivision .......................... SUCCESS [  1.106 s]
[INFO] jacoco-report-aggregate ............................ SUCCESS [  0.054 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.777 s
[INFO] Finished at: 2022-10-01T18:19:38+02:00
[INFO] ------------------------------------------------------------------------

Vemos que el módulo de agregación va a por los resultados de los otros módulos, los agrega y dentro de su carpeta target/site/index.html lo tenemos todo agregado.

Primer ejemplo de informe de JaCoCo agregado
Primer ejemplo de informe de JaCoCo agregado

5.4. Los tests de integración

Si tenemos tests de integración la cosa se complica un poco. Imaginemos un nuevo módulo Calculator que hace uso de los otros servicios para hacer una operación sumAndMultiply y hacemos un test de integración para ver que todo es correcto. Añadimos el nuevo módulo y cambiamos a verify la agregación. También añadimos el plug-in de failsafe para poder ejecutar los tests en el pom principal. Con esto ya quedaría configurado.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>es.dionisiocortes</groupId>
  <artifactId>jacoco</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>
  <modules>
    <module>AdditionAndSubtraction</module>
    <module>MultiplicationAndDivision</module>
    <module>Calculator</module>
    <module>jacoco-report-aggregate</module>
  </modules>

  <name>jacoco</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <junit-jupiter.version>5.9.1</junit-jupiter.version>
    <jacoco-maven-plugin.version>0.8.8</jacoco-maven-plugin.version>
    <maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
    <maven-failsafe-plugin.version>3.0.0-M7</maven-failsafe-plugin.version>
  </properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>${junit-jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>${junit-jupiter.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</dependencyManagement>


  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.jacoco</groupId>
          <artifactId>jacoco-maven-plugin</artifactId>
          <version>${jacoco-maven-plugin.version}</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>${maven-surefire-plugin.version}</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-failsafe-plugin</artifactId>
          <version>${maven-failsafe-plugin.version}</version>
        </plugin>

      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>integration-test</goal>
              <goal>verify</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>prepare-agent</id>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

En el módulo que agrega:

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jacoco</artifactId>
        <groupId>es.dionisiocortes</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>

    <artifactId>jacoco-report-aggregate</artifactId>

    <properties>
        <maven.deploy.skip>true</maven.deploy.skip>
    </properties>

    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>AdditionAndSubtraction</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>MultiplicationAndDivision</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>Calculator</artifactId>
            <version>${project.version}</version>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>report-aggregate</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>report-aggregate</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

La nueva clase

Calculator.java
package es.dionisiocortes;

public class Calculator {

    public Calculator() {
    }

    public int sumAndMultiply() {
        return 25;
    }
}

Su test de integración

CalculatorIT.java
import es.dionisiocortes.Calculator;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class CalculatorIT {

    private final Calculator sut = new Calculator();

    @Test
    public void sum_and_multiply_should_return_the_correct_value() {
        assertEquals(25, sut.sumAndMultiply());
    }

}

Ahora si ejecutamos

mvn clean test
[INFO] --- jacoco-maven-plugin:0.8.8:report-aggregate (report-aggregate) @ jacoco-report-aggregate ---
[INFO] Loading execution data file /home/dio/dev/jacocomultimodulo/jacoco/AdditionAndSubtraction/target/jacoco.exec
[INFO] Loading execution data file /home/dio/dev/jacocomultimodulo/jacoco/MultiplicationAndDivision/target/jacoco.exec
[INFO] Loading execution data file /home/dio/dev/jacocomultimodulo/jacoco/Calculator/target/jacoco.exec
[INFO] Analyzed bundle 'AdditionAndSubtraction' with 1 classes
[INFO] Analyzed bundle 'MultiplicationAndDivision' with 1 classes
[INFO] Analyzed bundle 'Calculator' with 1 classes
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for jacoco 1.0-SNAPSHOT:
[INFO]
[INFO] jacoco ............................................. SUCCESS [  0.666 s]
[INFO] AdditionAndSubtraction ............................. SUCCESS [  2.726 s]
[INFO] MultiplicationAndDivision .......................... SUCCESS [  1.200 s]
[INFO] Calculator ......................................... SUCCESS [  1.237 s]
[INFO] jacoco-report-aggregate ............................ SUCCESS [  0.184 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  6.114 s
[INFO] Finished at: 2022-10-02T13:49:40+02:00
[INFO] ------------------------------------------------------------------------
Ejemplo de informe de JaCoCo agregado con test de integración
Ejemplo de informe de JaCoCo agregado con test de integración

Aquí hay que tener en cuenta varias cosas. La primera seria ver que al poner los tests de integración se están ejecutando, es decir, tenemos el plugin de failsafe bien configurado. Luego, tenemos que ver que el agente de jacoco está bien configurado.

[INFO] --- jacoco-maven-plugin:0.8.8:prepare-agent (prepare-agent) @ Calculator ---
[INFO] argLine set to -javaagent:/home/dio/.m2/repository/org/jacoco/org.jacoco.agent/0.8.8/org.jacoco.agent-0.8.8-runtime.jar=destfile=/home/dio/dev/jacocomultimodulo/jacoco/Calculator/target/jacoco.exec

Si tenemos alguna configuración que no es compatible, puede darse el caso de que no se esté ejecutando bien JaCoCo. Para solucionar esto tenemos que configurar JaCoCo y failsafe. Al plug-in de JaCoCo le tenemos que decir que prepare el agente en la fase de integración con el parámetro propertyName que puede ser cualquier cosa, pero tiene que estar igual en argLine de failsafe

pom.xml configuración extra de JaCoC0
....
  <execution>
    <id>initialize-coverage-before-integration-test-execution</id>
    <goals>
      <goal>prepare-agent</goal>
    </goals>
    <phase>pre-integration-test</phase>
    <configuration>
      <propertyName>integrationTestCoverageAgent</propertyName>
    </configuration>
  </execution>
....
pom.xml configuración extra de failsafe
....
        <executions>
          <execution>
            <configuration>
              <argLine>${integrationTestCoverageAgent}</argLine>
            </configuration>
          </execution>
        </executions>
....

6. Conclusiones

Hemos hecho un breve repaso por como obtener la cobertura de nuestro código. Es importante tener algún tipo de métrica (o varias) que nos digan que partes del código están cubiertas por test e idealmente si esa cobertura es buena o mala. Hemos empezado en un proyecto con un solo módulo que hemos ido partiendo en distintos módulos y hemos visto como agregar los informes en un solo sitio para que de un golpe de vista podamos ver el estado del proyecto. También hemos incluido tests de integración que son un poco más especiales.

7. Referencias

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad