1. Información general
Las pruebas de software se refieren a las técnicas utilizadas para evaluar la funcionalidad de una aplicación de software. En este artículo, vamos a discutir algunas de las métricas utilizadas en la industria de pruebas de software, como la cobertura de código y las pruebas de mutación , con un interés particular en cómo realizar una prueba de mutación utilizando la biblioteca PITest .
En aras de la simplicidad, vamos a basar esta demostración en una función básica de palíndromo: tenga en cuenta que un palíndromo es una cadena que se lee igual hacia atrás y hacia adelante.
2. Dependencias de Maven
Como puede ver en la configuración de dependencias de Maven, usaremos JUnit para ejecutar nuestras pruebas y la biblioteca PITest para introducir mutantes en nuestro código; no se preocupe, veremos en un segundo qué es un mutante. Siempre puede buscar la última versión de dependencia en el repositorio central de maven siguiendo este enlace.
org.pitest pitest-parent 1.1.10 pom
Para tener la biblioteca PITest en funcionamiento, también necesitamos incluir el complemento pitest-maven en nuestro archivo de configuración pom.xml :
org.pitest pitest-maven 1.1.10 com.baeldung.testing.mutation.* com.baeldung.mutation.test.*
3. Configuración del proyecto
Ahora que tenemos nuestras dependencias de Maven configuradas, echemos un vistazo a esta función palíndromo autoexplicativa:
public boolean isPalindrome(String inputString) { if (inputString.length() == 0) { return true; } else { char firstChar = inputString.charAt(0); char lastChar = inputString.charAt(inputString.length() - 1); String mid = inputString.substring(1, inputString.length() - 1); return (firstChar == lastChar) && isPalindrome(mid); } }
Todo lo que necesitamos ahora es una simple prueba JUnit para asegurarnos de que nuestra implementación funciona de la manera deseada:
@Test public void whenPalindrom_thenAccept() { Palindrome palindromeTester = new Palindrome(); assertTrue(palindromeTester.isPalindrome("noon")); }
Hasta ahora todo va bien, estamos listos para ejecutar nuestro caso de prueba con éxito como una prueba JUnit.
A continuación, en este artículo, nos centraremos en el código y la cobertura de mutaciones utilizando la biblioteca PITest.
4. Cobertura de código
La cobertura de código se ha utilizado ampliamente en la industria del software para medir qué porcentaje de las rutas de ejecución se ha ejercido durante las pruebas automatizadas.
Podemos medir la cobertura de código efectiva en función de las rutas de ejecución utilizando herramientas como Eclemma disponibles en Eclipse IDE.
Después de ejecutar TestPalindrome con cobertura de código, podemos lograr fácilmente una puntuación de cobertura del 100%. Tenga en cuenta que isPalindrome es recursivo, por lo que es bastante obvio que la verificación de longitud de entrada vacía se cubrirá de todos modos.
Desafortunadamente, las métricas de cobertura de código a veces pueden ser bastante ineficaces , porque una puntuación de cobertura de código del 100% solo significa que todas las líneas se ejercitaron al menos una vez, pero no dice nada sobre la precisión de las pruebas o la integridad de los casos de uso , y es por eso que las pruebas de mutación realmente importan.
5. Cobertura de mutaciones
La prueba de mutación es una técnica de prueba que se utiliza para mejorar la adecuación de las pruebas e identificar defectos en el código. La idea es cambiar el código de producción de forma dinámica y hacer que las pruebas fallen.
Las buenas pruebas fallarán
Cada cambio en el código se llama mutante y da como resultado una versión alterada del programa, llamada mutación .
Decimos que la mutación se mata si puede provocar una falla en las pruebas. También decimos que la mutación sobrevivió si el mutante no podía afectar el comportamiento de las pruebas.
Ahora ejecutemos la prueba usando Maven, con la opción de objetivo establecida en: org.pitest: pitest-maven: mutationCoverage .
Podemos consultar los informes en formato HTML en el directorio target / pit-test / YYYYMMDDHHMI :
- Cobertura de línea del 100%: 7/7
- Cobertura de mutación del 63%: 5/8
Claramente, nuestra prueba recorre todas las rutas de ejecución, por lo tanto, la puntuación de cobertura de línea es del 100%. Por otro lado, la biblioteca PITest introdujo 8 mutantes , 5 de ellos murieron, causaron una falla, pero 3 sobrevivieron.
Podemos consultar el informe com.baeldung.testing.mutation / Palindrome.java.html para obtener más detalles sobre los mutantes creados:

Estos son los mutadores activos por defecto cuando se ejecuta una prueba de cobertura de mutación:
- INCREMENTS_MUTATOR
- VOID_METHOD_CALL_MUTATOR
- RETURN_VALS_MUTATOR
- MATH_MUTATOR
- NEGATE_CONDITIONALS_MUTATOR
- INVERT_NEGS_MUTATOR
- CONDITIONALS_BOUNDARY_MUTATOR
Para obtener más detalles sobre los mutadores PITest, puede consultar el enlace de la página de documentación oficial .
Nuestra puntuación de cobertura de mutación refleja la falta de casos de prueba , ya que no podemos asegurarnos de que nuestra función palindrómica rechace las entradas de cadena no palindrómicas y casi palindrómicas.
6. Mejorar la puntuación de mutación
Ahora que sabemos qué es una mutación, debemos mejorar nuestra puntuación de mutación matando a los mutantes supervivientes .
Tomemos la primera mutación - condicional negada - en la línea 6 como ejemplo. El mutante sobrevivió porque incluso si cambiamos el fragmento de código:
if (inputString.length() == 0) { return true; }
A:
if (inputString.length() != 0) { return true; }
La prueba pasará y es por eso que la mutación sobrevivió . La idea es implementar una nueva prueba que fallará, en caso de que se introduzca el mutante . Lo mismo se puede hacer con los mutantes restantes.
@Test public void whenNotPalindrom_thanReject() { Palindrome palindromeTester = new Palindrome(); assertFalse(palindromeTester.isPalindrome("box")); } @Test public void whenNearPalindrom_thanReject() { Palindrome palindromeTester = new Palindrome(); assertFalse(palindromeTester.isPalindrome("neon")); }
Ahora podemos ejecutar nuestras pruebas usando el complemento de cobertura de mutaciones, para asegurarnos de que todas las mutaciones fueron eliminadas , como podemos ver en el informe PITest generado en el directorio de destino.
- Cobertura de línea del 100%: 7/7
- Cobertura de mutación del 100%: 8/8
7. Configuración de las pruebas PITest
Las pruebas de mutación pueden requerir muchos recursos a veces, por lo que debemos establecer la configuración adecuada para mejorar la eficacia de las pruebas. Podemos hacer uso de la etiqueta targetClasses para definir la lista de clases a mutar. Las pruebas de mutación no se pueden aplicar a todas las clases en un proyecto del mundo real, ya que requerirán mucho tiempo y serán críticos en recursos.
También es importante definir los mutadores que planea usar durante las pruebas de mutación, para minimizar los recursos informáticos necesarios para realizar las pruebas:
com.baeldung.testing.mutation.* com.baeldung.mutation.test.* CONSTRUCTOR_CALLS VOID_METHOD_CALLS RETURN_VALS NON_VOID_METHOD_CALLS
Moreover, the PITest library offers a variety of options available to customize your testing strategies, you can specify the maximum number of mutants introduced by class using the maxMutationsPerClass option for example. More details about PITest options in the official Maven quickstart guide.
8. Conclusion
Note that code coverage is still an important metric, but sometimes it is not sufficient enough to guarantee a well-tested code. So in this article we've walked through mutation testing as a more sophisticated way to ensure tests quality and endorse test cases, using the PITest library.
También hemos visto cómo analizar informes básicos de PITest mientras se mejora la puntuación de cobertura de mutaciones .
Aunque las pruebas de mutación revelan defectos en el código, deben usarse con prudencia, ya que es un proceso extremadamente costoso y que requiere mucho tiempo .
Puede consultar los ejemplos proporcionados en este artículo en el proyecto de GitHub vinculado .