Ejemplo con pitest

De ChuWiki

Vamos a ver un ejemplo con pitest, un plugin que modifica más o menos aleatoriamente nuestro código java que está pasando pruebas (de Junit por ejemplo), para ver si las pruebas siguen pasando o no después de haber modificado el código. Si las pruebas siguen pasando, es que no son pruebas muy buenas.

El código completo de este ejemplo está en ejemplo de pitest

Concepto básico[editar]

La idea de pitest es la siguiente. Nosotros hacemos nuestro código java y hacemos test de Junit (u otras herramientas) para tener una garantía de que está bien. Pero a veces nuestros test no son tan buenos como nos gustaría. Por ejemplo, imagina que tenemos una clase a la que pasamos un test de Junit de forma correcta. Pero imagina que modificamos la clase un poco al azar (cambiamos un + por un -, o un > por un < en un if, o hacemos que un método que devuelva un boolean devuelva el valor contrario al esperado) y que el test sigue pasando correctamente.

Esto es indicativo de que nuestro test no es muy exhaustivo, que no está probando suficientemente bien nuestro código.

Y esa el idea de pitest. pitest modificad nuestro código y vuelve a pasar los test de Junit. Si los test fallan, los marca como test "buenos". Si los test siguen pasando, los marca como "malos". pitest saca luego un informe indicando la cobertura de nuestros test y si han pasado o no tras haber hecho las mutaciones (los cambios aleatorios por nuestro código).

La siguiente imagen muestra un ejemplo:

  • Líneas verdes claras son que los test de Junit han pasado por ellas (tiene cobertura).
  • Lineas verdes oscuras que pitest ha modificado el código y los test han fallado, eso sería lo correcto.
  • Líneas rojas claras que los test de Junit no han pasado por ellas. Son falta de cobertura en los test, código que los test no prueban.
  • Líneas rojas oscuras que pitest ha modificado el código y el test sigue pasando. Son líneas de código que los test no prueban lo suficientemente bien.

Configuración de Maven[editar]

Pitest puede ejecutarse desde línea de comandos sin más, pero lo suyo es integrarlo en nuestro proceso de compilado con maven o gradle. Aquí vamos a hacerlo con maven. En nuestro tag build de nuestro pom.xml bastaría poner un plugin así

    <build>
        <plugins>
            <plugin>
                <groupId>org.pitest</groupId>
                <artifactId>pitest-maven</artifactId>
                <version>1.5.2</version>
                <executions>
                    <execution>
                        <id>mutationCoverage</id>
                        <goals>
                            <goal>mutationCoverage</goal>
                        </goals>
                        <phase>test</phase>
                    </execution>
                </executions>
                <configuration>
                    <!--        <targetClasses>-->
                    <!--            <param>com.your.package.root.want.to.mutate*</param>-->
                    <!--        </targetClasses>-->
                    <targetTests>
                        <param>com.chuidiang.examples.pitest*</param>
                    </targetTests>
                    <mutationThreshold>90</mutationThreshold>
                    <coverageThreshold>90</coverageThreshold>
                </configuration>
            </plugin>
        </plugins>
    </build>

Hay muchas más opciones, pero vamos a comentar las que aparecen en el fichero.

  • groupId, artifactId y version del plugin.
  • Queremos que se ejecute pitest, su tarea mutationCoverage durante la fase de test. Si no ponemos esto, tendríamos que ejecutarlo a mano cuando quisieramos.
  • En la parte de configuración:
    • targetClasses: paquete con las clases que queramos que se muten. Si lo dejamos en blanco (como en el ejemplo que está comentado), pitest buscará las clases por nuestro proyecto.
    • targetTests: paquete con los test que queremos que pitest vuelva a ejecutar cuando ha hecho los cambios (mutaciones) en nuestro código.
    • mutationTreshold: El porcentaje de tests que queremos que sean correctos desde el punto de vista de pitest, de 0 a 100. Si no se llega a este porcentaje, el compilado de maven dará fallo. Si no ponemos esta opción, no fallará y solo veremos el informe.
    • coverageTreshold: El porcentaje de líneas de código que queremos que quede cubierta por los test de Junit. Si no se llega a ese porcentaje, el compilado falla. Si no ponemos esta opción, no fallará el compilado, únicamente tendremos disponible el informe.

El código[editar]

De código hemos hecho una simple clase calculadora con los métodos de sumar, restar, multiplicar y dividir dos números. Nada interesante. Veamos el test de Junit que hemos hecho, que sí es más interesante

package com.chuidiang.examples.pitest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

/**
 * @author fjabellan
 * @date 02/11/2020
 */
public class CalculatorTest {
    private Calculator calculator;

    @Before
    public void setUp(){
        calculator = new Calculator();
    }

    @Test
    public void simpleTests(){
        double result;

        result=calculator.subtract(10.0,5.0);
        Assert.assertEquals("simple bad subtraction test",5.0,result, 1e-6);

        result = calculator.multiply(1.0,2.0);
        Assert.assertEquals("simple bad divide test",2.0 ,result, 1e-6);

//        result = calculator.divide(1.0,2.0);
//        Assert.assertEquals("simple bad divide test",0.5 ,result, 1e-6);
    }

    @Test
    public void noValidTest(){
        // pitest cambiara dentro del código la suma por una resta, el test seguirá pasando
        // Este test no es muy valido, hay muchas operaciones que dan cero cuando se les
        // pasa dos ceros como parametro: suma, resta y multiplicación.
        double result = calculator.add(0.0,0.0);
        Assert.assertEquals("simple add test", 0.0, result ,1e-6);
    }
}

En simpleTest hacemos los test de restar y multiplicar. Los hacemos más o menos bien, con valores sencillos, pero no triviales.

Hemos comentado a posta el test de dividir. Esto hará que la operación de dividir no tenga cobertura en nuestro código. Es la línea roja clara que aparece en la imagen anterior para la operación de dividir.

En cuanto al test de sumar lo hemos hecho aparte, y lo hemos hecho con valores "triviales", 0+0=0. Cuando pitest cambie el + por un - en el código, seguirá dando un test correcto. El código está bien, suma, pero el test no es muy allá desde el punto de vista de pitest.

Si ejecutamos mvn clean install, se compilará, se pasarán los test, pitest hará de las suyas ... y el compilado fallará:

  • Hay un 25% de líneas de código sin cobertura (de las cuatro operaciones, la de dividir no tiene test).
  • Hay un 50% de fallos en las mutaciones que ha hecho pitest. La de sumar porque nuestro test no es muy allá y la de dividir simplemente porque no tiene test.