Mutation Testing con PIT

0
9554

En este tutorial vamos a ver una técnica que nos permite comprobar la calidad de nuestras pruebas unitarias: mutation testing.

Ya sabemos que las pruebas unitarias, y los test en general, son una parte esencial en el ciclo de vida del software, nos ayudan a comprobar que el código fuente que tenemos entre manos es correcto, además de ofrecernos otras ventajas como por ejemplo documentación, simplificación de la integración, fomentan el cambio, etc.

Por desgracia, no siempre se entiende bien el concepto y la idea se pervierte, este es el caso de aquellos que introducen test sin una lógica consistente, sin verificaciones (verify) o sin aseveraciones (asserts), buscando el 100% de cobertura del código.
Aquí es donde aparece el concepto de Mutation testing, que nos ayuda ha diseñar nuevas pruebas y a evaluar la calidad de las pruebas ya existentes.

Índice de contenidos

1. Introducción

El concepto Mutation testing fue acuñado por primera vez en 1978, un artículo de Richard J. Lipton[1], donde detallaba como, en un grupo formado por integrantes de la Universidad de Yale y del Instituto Tecnológico de Georgia, habían construido un sistema mediante el cuál determinar el grado en el que un determinado conjunto de pruebas había probado un programa Fortran. Al método que utilizaban en el sistema para realizar estas mediciones lo denominaba «Programa mutado».

A lo largo de este tutorial veremos en que consiste el Mutation testing, cuales son las mutaciones más relevantes y un ejemplo de aplicación con el plugin PIT para IntelliJ IDEA y Maven.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.5 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS Yosemite 10.10.5.
  • Entorno de desarrollo: IntelliJ IDEA 14 CE.
  • Apache Maven 3.3.0.
  • JDK 1.7.0_79

3. Mutation testing

Básicamente, el Mutation testing consiste en introducir pequeñas modificaciones en el código fuente de la aplicación, a las que denominaremos mutaciones o mutantes.

  • Si las pruebas pasan al ejecutarse sobre el mutante, el mutante sobrevive.

  • Si las pruebas no pasan al ejecutarse sobre el mutante, el mutante muere

El objetivo es que todos los mutantes mueran, así podremos decir que el test responde a la definición concreta del código y que lo prueba correctamente.

Este concepto se basa en dos hipótesis:

  • Hipótesis del programador competente: La mayoría de los errores introducidos por programadores Senior consisten en pequeños errores sintácticos.
  • Hipótesis del efecto de acoplamiento: Pequeños fallos acoplados pueden dar lugar a otros problemas mayores.

Problemas de mayor orden serán revelados por mutantes de mayor orden, que se crean mediante la unión de multiples mutaciones.

4. Mutaciones

A continuación listamos algunas de las principales mutaciones:

  • Reemplazo de valores lógicos: Cambiar true por false, y viceversa.
  • Reemplazo de operadores aritméticos: Cambiar + por -, o * por /.
  • Reemplazo de condiciones lógicas: Cambiar > por ≥ o por <.
  • Eliminar líneas de código.
  • Modificar contadores.

Este es solo un pequeño listado de todas las posibles, un listado m´s extenso puede visualizarse en el siguiente enlace: PIT mutation operators.

5. PIT

5.1. Instalación en IntelliJ IDEA 14 CE

Para proceder con la instalación del plugin acceder a Preferencias > Plugins > Browse Repositories… , en la ventana emergente buscamos «PIT», lo seleccionamos entre las opciones que se encuentran y pulsamos sobre el botón «Install plugin».
Cuando haya finalizado el proceso de instalación, reiniciar el IDE.

5.2. El proyecto de ejemplo

Para ver el funcionamiento del plugin, vamos a implementar una calculadora muy básica, que implementará 4 operaciones: Suma, resta, multiplicación y división.

Generamos un proyecto maven:

El código de nuestra clase será el siguiente:

Calculadora.java
package com.ejemplos;

public class Calculadora
{
    public int suma(int operando1, int operando2){
        return operando1 + operando2;
    }

    public int resta(int operando1, int operando2){
        return operando1 - operando2;
    }

    public int multiplica(int operando1, int operando2){
        return operando1 / operando2;
    }

    public int divide(int operando1, int operando2){
        return operando1 / operando2;
    }
}

Como véis, hemos introducido un bug en la implementación del método suma, introduciendo la multiplicación, para ver como reaccionan las mutaciones al test.

El código de nuestra clase de test quedará de la siguiente manera:

Calculadora.java
package com.ejemplos;

import org.hamcrest.MatcherAssert;
import org.junit.Before;
import org.junit.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

public class CalculadoraTest {

    private Calculadora sut;

    @Before
    public void inicializa(){
        this.sut = new Calculadora();
    }

    @Test
    public void suma_debe_devolver_la_suma_de_los_operandos(){
        assertThat(4,is(this.sut.suma(2, 2)));
    }

    @Test
    public void resta_debe_devolver_la_resta_de_los_operandos(){
        assertThat(6,is(this.sut.resta(8, 2)));
    }

    @Test
    public void multiplica_debe_devolver_la_multiplicacion_de_los_operandos(){
        assertThat(1,is(this.sut.multiplica(1, 1)));
    }

    @Test
    public void divide_debe_devolver_la_division_de_los_operandos(){
        assertThat(5,is(this.sut.divide(40, 8)));
    }
}

Al ejecutar nuestro conjunto de test, obtenemos que todo ha sido correcto aunque, como hemos visto, nuestro código es erróneo y nuestro código de test lo secunda….

mutation_imagen3

5.3. Uso de PIT a través de IntelliJ

Para poder utilizar el plugin PIT, basta con que lo activemos mediante Run > Edit Configurations…, pulsamos sobre el símbolo ‘+’ y añadimos un PIT runner, que mantendrá una configuración por defecto a la que debemos añadir la dirección en la que se generan las clases con el parámetro adicional «–targetClasses».

mutation_imagen4

A continuación, procederemos a ejecutar el conjunto de test con el PIT runner que hemos definido:

Esta ejecución nos generará un reporte, que al abrirlo nos dará el análisis sobre las mutaciones que se han realizado…

mutation_imagen7

mutation_imagen8

mutation_imagen9

Como vemos, una de las mutaciones ha sobrevivido, lo que implica que nuestro test no es consistente, y es posible que nuestro código no este haciendo lo que esperamos que haga.

5.3. Uso de PIT a través de Maven

Existe también la posibilidad de hacer uso de esta herramienta través de su plugin de maven, para ello tan solo hace falta agregar las siguientes líneas en el fichero pom.xml, en la sección de plugins.

<plugin>
  <groupId>org.pitest</groupId>
  <artifactId>pitest-maven</artifactId>
  <version>LATEST</version>
  <configuration>
    <mutators>
      <mutator>ALL</mutator>
    </mutators>
    <threads>10</threads>
    <targetClasses>
      <param>com.ejemplos*</param>
    </targetClasses>
  </configuration>
</plugin>

Para ejecutarlo, tendremos que dirigirnos a un terminal e introducir el comando:

mvn org.pitest:pitest-maven:mutationCoverage

La ejecución del anterior comando ofrecerá los mismos resultados, un informe sobre el análisis de las mutaciones realizadas.

Éste tipo de ejecución permite una configuración mayor de los parámetros como las mutaciones que se van a aplicar, el código sobre el que se va a ejecutar o que clases excluir.

Para más información al respecto de la configuración podéis visitar el siguiente enlace: PIT maven configuration.

6. Conclusiones

En el plugin de PIT podemos encontrar un buen aliado a la hora de auditar código de testing, ya sea propio o ajeno.
A través de las diferentes mutaciones, podremos analizar si nuestras pruebas son consistentes y prueban nuestro código «como es debido».

Sin embargo, hay que tener en cuenta el conjunto de pruebas sobre el que aplicamos el plugin, así como el código que prueba, puesto que la complejidad y la cantidad del código hace que los tiempos de ejecución de las mutaciones y sus aplicaciones puede dispararse.

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