Conheça as vantagens da atualização para o PostgreSQL 14
22 de outubro de 2021Atualizações PostgreSQL 14.1, 13.5, 12.9, 11.14, 10.19 e 9.6.24 disponíveis!
29 de novembro de 20211. Visão Geral
O teste de software refere-se às técnicas usadas para avaliar a funcionalidade de um aplicativo de software. Neste artigo, vamos discutir algumas das métricas usadas na indústria de teste de software, como cobertura de código e teste de mutação, com interesse peculiar em como realizar um teste de mutação usando a biblioteca PITest.
Para simplificar, vamos basear esta demonstração em uma função de palíndromo básica – Observe que um palíndromo é uma string que lê a mesma coisa para frente e para trás.
2. Dependências Maven
Como você pode ver na configuração das dependências do Maven, usaremos JUnit para executar nossos testes e a biblioteca PITest para introduzir mutantes em nosso código – não se preocupe, veremos em um segundo o que é um mutante. Você sempre pode procurar a versão de dependência mais recente no repositório central maven seguindo este link.
<dependency>
<groupId>org.pitest</groupId>
<artifactId>pitest-parent</artifactId>
<version>1.1.10</version>
<type>pom</type>
</dependency>
Para ter a biblioteca PITest instalada e funcionando, também precisamos incluir o plug-in pitest-maven em nosso arquivo de configuração pom.xml:
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.1.10</version>
<configuration>
<targetClasses>
<param>com.baeldung.testing.mutation.*</param>
</targetClasses>
<targetTests>
<param>com.baeldung.mutation.test.*</param>
</targetTests>
</configuration>
</plugin>
3. Configuração do projeto
Agora que temos nossas dependências Maven configuradas, vamos dar uma olhada nesta função de 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); }}
Tudo o que precisamos agora é um teste JUnit simples para garantir que nossa implementação funcione da maneira desejada:
@Test
public void whenPalindrom_thenAccept() {
Palindrome palindromeTester = new Palindrome();
assertTrue(palindromeTester.isPalindrome(“noon”));}
Até aqui tudo bem, estamos prontos para executar nosso caso de teste com sucesso como um teste JUnit. A seguir, neste artigo, vamos nos concentrar na cobertura de código e mutação usando a biblioteca PITest.
4. Cobertura de código
A cobertura de código tem sido usada extensivamente na indústria de software, para medir qual porcentagem dos caminhos de execução foi exercida durante os testes automatizados.
Podemos medir a cobertura de código efetiva com base em caminhos de execução usando ferramentas como Eclemma disponíveis no Eclipse IDE.
Depois de executar TestPalindrome com cobertura de código, podemos facilmente atingir uma pontuação de cobertura de 100% – Observe que isPalindrome é recursivo, então é bastante óbvio que a verificação de comprimento de entrada vazia será coberta de qualquer maneira.
Infelizmente, as métricas de cobertura de código às vezes podem ser bastante ineficazes, porque uma pontuação de cobertura de código de 100% significa apenas que todas as linhas foram exercitadas pelo menos uma vez, mas não diz nada sobre a precisão dos testes ou a integridade dos casos de uso, e é por isso que o teste de mutação realmente importa.
5. Cobertura de mutação
O teste de mutação é uma técnica de teste usada para melhorar a adequação dos testes e identificar defeitos no código. A ideia é alterar o código de produção dinamicamente e fazer com que os testes falhem. Bons testes devem falhar.
Cada mudança no código é chamada de mutante e resulta em uma versão alterada do programa, chamada de mutação.
Dizemos que a mutação é morta se puder causar uma falha nos testes. Também dizemos que a mutação sobreviveu se o mutante não pudesse afetar o comportamento dos testes. Agora vamos executar o teste usando Maven, com a opção de objetivo definida como: org.pitest: pitest-maven: mutationCoverage. Podemos verificar os relatórios em formato HTML no diretório target / pit-test / YYYYMMDDHHMI:
-
100% de cobertura da linha: 7/7
-
Cobertura de mutação de 63%: 5/8
Claramente, nosso teste abrange todos os caminhos de execução, portanto, a pontuação de cobertura da linha é de 100%. Por outro lado, a biblioteca PITest introduziu 8 mutantes, 5 deles foram mortos – causou uma falha – mas 3 sobreviveram. Podemos verificar o relatório com.baeldung.testing.mutation / Palindrome.java.html para obter mais detalhes sobre os mutantes criados:
Estes são os mutadores ativos por padrão ao executar um teste de cobertura de mutação:
-
INCREMENTS_MUTATOR
-
VOID_METHOD_CALL_MUTATOR
-
RETURN_VALS_MUTATOR
-
MATH_MUTATOR
-
NEGATE_CONDITIONALS_MUTATOR
-
INVERT_NEGS_MUTATOR
-
CONDITIONALS_BOUNDARY_MUTATOR
Para mais detalhes sobre os modificadores do PITest, você pode verificar o link da página de documentação oficial. Nossa pontuação de cobertura de mutação reflete a falta de casos de teste, já que não podemos ter certeza de que nossa função de palíndromo rejeita entradas de strings não palíndrômicas e quase palindrômicas.
6. Melhore a pontuação de mutação
Agora que sabemos o que é uma mutação, precisamos melhorar nossa pontuação de mutação matando os mutantes sobreviventes. Vamos pegar a primeira mutação – condicional negada – na linha 6 como exemplo. O mutante sobreviveu porque mesmo que mudássemos o snippet de código:
if (inputString.length() == 0) {
return true;
}
Para:
if (inputString.length() != 0) {
return true;
}
O teste vai passar e é por isso que a mutação sobreviveu. A ideia é implementar um novo teste que irá falhar, caso o mutante seja introduzido. O mesmo pode ser feito para os 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”));
}
Agora podemos executar nossos testes usando o plug-in de cobertura de mutação, para ter certeza de que todas as mutações foram eliminadas, como podemos ver no relatório PITest gerado no diretório de destino.
-
100% de cobertura da linha: 7/7
-
100% de cobertura de mutação: 8/8
7. Configuração de testes PITest
O teste de mutação pode exigir muitos recursos às vezes, portanto, precisamos colocar a configuração adequada para melhorar a eficácia dos testes. Podemos fazer uso da tag targetClasses, para definir a lista de classes a serem modificadas. O teste de mutação não pode ser aplicado a todas as classes em um projeto do mundo real, pois será demorado e crítico em termos de recursos.
Também é importante definir os mutadores que você planeja usar durante o teste de mutação, a fim de minimizar os recursos de computação necessários para realizar os testes:
<configuration>
<targetClasses>
<param>com.baeldung.testing.mutation.*</param>
</targetClasses>
<targetTests>
<param>com.baeldung.mutation.test.*</param>
</targetTests>
<mutators>
<mutator>CONSTRUCTOR_CALLS</mutator>
<mutator>VOID_METHOD_CALLS</mutator>
<mutator>RETURN_VALS</mutator>
<mutator>NON_VOID_METHOD_CALLS</mutator>
</mutators>
</configuration>
Além disso, a biblioteca PITest oferece uma variedade de opções disponíveis para personalizar suas estratégias de teste, você pode especificar o número máximo de mutantes introduzidos por classe usando a opção maxMutationsPerClass, por exemplo. Mais detalhes sobre as opções do PITest no guia de início rápido oficial do Maven.
8. Conclusão
Observe que a cobertura de código ainda é uma métrica importante, mas às vezes não é suficiente para garantir um código bem testado. Portanto, neste artigo, examinamos os testes de mutação como uma forma mais sofisticada de garantir a qualidade dos testes e endossar casos de teste, usando a biblioteca PITest. Também vimos como analisar relatórios básicos de PITest e, ao mesmo tempo, melhorar a pontuação de cobertura de mutação. Mesmo que o teste de mutação revele defeitos no código, ele deve ser usado com sabedoria, porque é um processo extremamente caro e demorado. Você pode verificar os exemplos fornecidos neste artigo no projeto GitHub vinculado.
Esse artigo é uma tradução livre de https://www.baeldung.com/java-mutation-testing-with-pitest
Clique aqui e veja como nós podemos te ajudar na implantação dos testes.