Todos estamos escribiendo tests y tenemos controlado el code coverage, pero 100% code coverage solo significa que todas las líneas se han ejecutado al menos una vez por nuestros tests, pero no dice nada sobre la precisión de los tests o la integridad de los casos de uso, y es por eso que el mutation testing es muy importante.
Mutation testing se utiliza para diseñar nuevos tests y evaluar la calidad de los tests existentes. La idea es modificar el código cubierto por tests de forma sencilla, comprobando si el conjunto de tests existente para este código detectará y rechazará las modificaciones.
Cada cambio en el código se llama mutante y da como resultado una versión alterada del programa, llamada mutación. La calidad de los tests se mide en función del porcentaje de mutación eliminada.
2. 2
Mutation Testing
Mis puntos fuertes:
❖ Tengo un rendimiento muy alto
❖ Trabajo muy bien bajo presión
❖ No tengo miedo de subir a producción
Mis puntos débiles:
❖ Tengo un rendimiento muy alto
❖ Trabajo muy bien bajo presión
❖ No tengo miedo de subir a producción
Sobre mí.
❖ Me llamo Ismail
❖ Bulgaro
❖ Adicto a los deploys
3. 3
Mutation Testing
Test-driven development.
❖ Los tests nos ayudan a entregar con seguridad.
❖ Nos ayuda a corregir los bugs y garantiza que el mismo
error no volverá a aparecer.
❖ A mi me resulta más fácil desarrollar.
4. 4
Mutation Testing
Así empieza esta historia...
❖ Debíamos añadir una nueva funcionalidad en un sistema
existente.
❖ Era algo que ya había hecho en otros dos proyectos y no
podía fallar.
❖ Así que hemos empezado…
7. 7
Mutation Testing
Tests con mala calidad.
❖ Tests que no comprueban el resultado completo esperado.
❖ Tests sin assert.
❖ No tener claro que se está testeando.
❖ Los tests dependen de otros tests.
❖ Testear más de una cosa en el mismo test.
❖ Saltar los tests de código difícil de testear.
❖ A veces fallan…
8. 8
Mutation Testing
Tests con mala calidad.
❖ Tests que no comprueban el resultado completo esperado.
@Test
void return_products_list () throws Exception {
Product product1 = new Product(3L, "Logitech Wireless Mouse M185" , "10,78 €");
Product product2 = new Product(4L, "Fellowes Mouse Pad Black" , "1,34 €");
when(productService .getAllProducts()).thenReturn( List.of(product1, product2));
this.mockMvc
.perform(get("/products" ))
.andExpect( status().isOk());
}
9. 9
Mutation Testing
Tests con mala calidad.
❖ Tests sin assert.
@Test
void create_basket_if_not_exists () {
Basket expectedBasket = new Basket(null, USER_ID, new Items(List.of(PRODUCT)));
when(basketRepository .getByUserId( USER_ID)).thenReturn( Optional.empty());
when(basketRepository .save(expectedBasket )).thenReturn( expectedBasket );
Basket actualBasket = basketService .addProductToBasket( USER_ID, PRODUCT);
}
En frameworks de testing como Spock por ejemplo, si falta When o Then sale error:
10. 10
Mutation Testing
¿Por qué hacemos tests?
❖ Cumplir con los criterios de aceptación
❖ Tener una red de seguridad
11. 11
Mutation Testing
¿Por qué hacemos TDD?
❖ Para reducir los bugs
❖ Incrementar la velocidad
❖ Cumplir los requisitos que ha pedido el
stakeholder
❖ Menos estrés / dormir más tranquilo
12. 12
Mutation Testing
¿Cómo podemos medir si tenemos suficientes tests?
“In computer science, test coverage is a percentage measure of the
degree to which the source code of a program is executed when a
particular test suite is run.”
- - Wikipedia - -
14. 14
Mutation Testing
Tests con mala calidad.
❖ Tests que no comprueban el resultado completo esperado.
❖ Tests sin assert.
❖ No tener claro que se está testeando.
❖ Los tests dependen de otros tests.
❖ Testear más de una cosa en el mismo test.
❖ Saltar los tests de código difícil de testear.
❖ A veces fallan…
15. 15
Mutation Testing
Mutation Testing.
❖ Asume que el software funciona correctamente.
❖ Se centra en comprobar si nuestros tests son estrictos.
❖ Nos ayuda encontrar las brechas que permiten que nuestro
código se salga con la suya con un comportamiento no deseado.
17. 17
Mutation Testing
¿Cómo funciona Mutation Testing?
❖ Comienza ejecutando sus tests para asegurarse de que todos pasen antes de crear cualquier
mutación.
❖ Luego cambia el código compilado y vuelve a ejecutar los tests.
21. 21
Mutation Testing
¿Qué hacer con los mutantes supervivientes?
❖ Los mutantes sobrevivientes le muestran dónde buscar problemas potenciales en su código.
❖ Algunos mutantes sobrevivientes son ruidosos.
❖ Algunas son 'mutaciones equivalentes'
❖ Algunos revelan datos reales y/o problemas significativos en su código o pruebas
23. 23
Mutation Testing
Mutantes que no se pueden matar.
❖ Son oportunidades para refactorizar:
➢ código muerto o inútil que nunca se llama
➢ código que afecta solo al rendimiento
➢ código que afecta solo al estado interno
➢ lógica en otra parte del código
25. 25
Mutation Testing
void mutation operator.
class A {
int field = 3;
public void method(int inc) {
field += 3;
}
}
class A {
int field = 3;
public void method(int inc) { }
}
26. 26
Mutation Testing
null mutation operator.
class A {
public A clone() {
return new A();
}
}
class A {
public A clone() {
return null ;
}
}
27. 27
Mutation Testing
empty mutation operator.
class A {
public int[] getRange(int count) {
int[] result = new int[count];
for(int i=0; i < count; i++) {
result[i] = i;
}
return result;
}
}
class A {
public int[] getRange(int count)
{
return new int [0];
}
}
28. 28
Mutation Testing
constant mutation operator.
class A {
int field;
public int getAbsField () {
if(field >= 0)
return field;
return -field;
}
}
class A {
int field;
public int getAbsField () {
return 3;
}
}
29. 29
Mutation Testing
new mutation operator.
class A {
int field;
public ArrayList range(int end) {
ArrayList l = new ArrayList();
for(int i = 0; i < size; i++) {
A a = new A();
a.field = i;
l.add(a);
}
return l;
}
}
class A {
int field;
public List range(int end) {
return new ArrayList();
}
}
30. 30
Mutation Testing
optional mutation operator.
class A {
int field;
public Optional<Integer> getOptional () {
return Optional.of( field);
}
}
class A {
int field;
public Optional<Integer> getOptional () {
return Optional.empty();
}
}
31. 31
Mutation Testing
argument mutation operator.
class A {
public int m(int x, int y) {
return x + 2 * y;
}
}
class A {
public int m(int x) {
return x;
}
}
32. 32
Mutation Testing
this mutation operator.
class A {
int value = 0;
public A addOne() {
value += 1;
return this ;
}
}
class A {
int value = 0;
public A addOne() {
return this ;
}
}
}
34. 34
Mutation Testing
Requisitos.
❖ Las pruebas deben tener el mismo resultado cada vez
❖ Se puede ejecutar en cualquier orden
❖ Se puede ejecutar en paralelo
❖ Básicamente: pruebas unitarias
35. 35
Mutation Testing
¿Qué excluir?
❖ Pruebas de integración y E2E
❖ Pruebas de rendimiento
❖ Tests de contrato
❖ Cualquier test que cambie el estado global…
37. 37
Mutation Testing
Conclusiones.
❖ Las pruebas de mutación reducen el riesgo de que falle su red de seguridad.
❖ Crea presión para reducir la cantidad de código y reducir la duplicación.
❖ Es fácil de instalar, ejecutar y leer.
38. 38
Mutation Testing
References.
● Pitest
● STAMP: Software testing amplification for DevOps
● Descartes: A Mutation Engine for PIT
● Performance comparison between Descartes and Gregor
● PitMP: Maven plugin to handle multi module projects for PiTest
● Imágenes:
○ https://www.freepik.es/
○ https://www.pexels.com/
● Gifs: https://tenor.com/