A StackSpot EDP é uma poderosa plataforma que suporta automação de testes, facilitando a criação e a ampliação de projetos de automação. No contexto de testes de mutação, a StackSpot EDP se destaca como uma solução eficiente, permitindo que o processo de configuração seja facilmente replicado em diferentes projetos.
Com a flexibilidade da plataforma, poucas modificações são necessárias, independentemente da tecnologia utilizada, agilizando a adoção dos testes de mutação em vários cenários de desenvolvimento.
Neste artigo, vamos mostrar como configurar testes de mutação em projetos back-end utilizando um Plugin desenvolvido com a StackSpot EDP.
O que são testes de mutação?
O teste de mutação é uma estratégia de testes usada para verificar a qualidade dos testes de software de uma aplicação.
Imagine um programa que executa diferentes funções e possui testes para garantir que tudo é executado conforme esperado. O teste de mutação verifica se esses testes são realmente eficazes em garantir o bom funcionamento do software.
Como funciona o teste de mutação
O conceito básico do teste de mutação é introduzir pequenos erros no código do programa, chamados de mutantes, para verificar se os testes conseguem identificar essas alterações.
O objetivo final é que, se seus testes forem robustos o suficiente, eles falhem ao encontrar essas mudanças intencionais. Se os testes não detectarem os mutantes, isso pode indicar que eles não são eficazes na identificação de problemas.
Essa técnica utiliza uma abordagem chamada estratégia de teste baseada em falhas para avaliar e melhorar a qualidade dos testes.
Processo de teste de mutação
O processo de teste de mutação envolve os seguintes passos:
- Introdução de falhas: alterações são feitas no código-fonte do programa para criar mutantes. Por exemplo, você pode substituir um sinal de adição por um sinal de subtração em uma expressão matemática.
- Aplicação de testes: os casos de teste são então aplicados tanto ao código original quanto ao programa mutante. Se o resultado dos testes para o código original e para os mutantes for diferente, significa que o mutante foi detectado e eliminado, mostrando que o teste é eficaz.
- Análise de resultados: se os testes não detectarem a alteração e os resultados forem iguais para o código original e o mutante, isso sugere que os testes não são sensíveis o suficiente para identificar erros. Nesse caso, pode ser necessário melhorar os casos de teste.
Exemplo de testes de mutação
Vamos considerar o seguinte exemplo usando Java:
Código Original:
public class Calculadora {
public int soma(int a, int b) {
return a + b;
}
}
Mutante:
public class Calculadora {
public int soma(int a, int b) {
return a - b; // Alteração intencional
}
}
Teste Unitário:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CalculadoraTest {
@Test
public void testSoma() {
Calculadora calc = new Calculadora();
assertEquals(5, calc.soma(2, 3)); // Teste que cobre o código original
}
}
No exemplo acima, o teste unitário testSoma verifica se o método soma da classe Calculadora retorna 5 para os valores 2 e 3. No código original, a soma de 2 e 3 é realmente 5, e o teste passa corretamente.
No entanto, quando o código é alterado para a – b (código mutante), o resultado da soma de 2 e 3 é -1, mas o teste continua passando pois não detecta a alteração intencional no código.
Mudanças em um programa mutante
Existem várias técnicas para gerar mutantes, sendo as mais comuns:
- Operadores de substituição de operando: substituem um operando por outro valor ou por um valor constante. Por exemplo, trocar um número por um valor fixo em uma expressão.
- Operadores de modificação de expressão: alteram a expressão original inserindo, substituindo ou removendo operadores. Isso pode incluir mudar um operador de adição para subtração, por exemplo.
- Operadores de modificação de instrução: modificam a instrução original, adicionando novas instruções ou removendo existentes. Isso pode envolver a alteração da lógica de um bloco de código.
Essas técnicas podem ser combinadas para criar mutantes com diferentes tipos de falhas, aumentando a eficácia do teste de mutação.
Benefícios de implementar testes de mutação
Implementar testes de mutação em seu projeto de software oferece vários benefícios:
- Melhora a qualidade dos testes: ao verificar se os testes conseguem identificar o código mutante, você garante que seus testes são capazes de detectar erros sutis no código.
- Aumenta a robustez do software: testes mais eficazes ajudam a garantir que o software se comportará corretamente, mesmo após modificações ou atualizações no código.
- Reduz o risco de falhas: ao identificar pontos fracos nos testes, você pode melhorá-los, reduzindo o risco de que erros não detectados afetem o funcionamento do software.
E, embora o teste de mutação possa parecer complexo, ele é uma ferramenta poderosa para melhorar a qualidade e a confiabilidade dos testes de software, garantindo que seu código funcione conforme o esperado em diferentes condições.
Ferramentas para testes de mutação
Existem várias ferramentas disponíveis para realizar testes de mutação em projetos que utilizam as linguagens Java ou Kotlin. Uma das mais conhecidas é o Pitest, que será usado neste artigo.
Para mais informações sobre as abordagens de geração de mutantes, consulte a documentação da ferramenta Pitest.
Pré-requisitos
- Java JDK.
- StackSpot CLI. Consulte a documentação para instalar a StackSpot CLI.
Projeto de exemplo
Neste artigo, vamos explorar o projeto Pequeno-Investidor, uma aplicação pessoal desenvolvida em Java que fornece informações do mercado financeiro através de uma API. Essa API coleta os dados por meio de web scraping, garantindo informações atualizadas e relevantes.
O projeto foi estruturado com o uso do gerenciador de dependências Maven. A estrutura do projeto é organizada da seguinte forma:
├───src
│ ├───main
│ │ ├───java
│ │ │ └───com
│ │ │ └───money
│ │ │ └───pequenoinvestidor
│ │ │ ├───configuration
│ │ │ ├───controller
│ │ │ ├───model
│ │ │ ├───services
│ │ │ │ └───imp
│ │ │ └───util
│ │ └───resources
│ └───test
│ └───java
│ └───com
│ └───money
│ └───pequenoinvestidor
│ └───integração
Conhecendo o Plugin da StackSpot EDP
Neste tutorial, foi criado um Plugin utilizando a StackSpot EDP – com fins demonstrativos em uma conta personal do GitHub. Essas contas permitem que pessoas usuárias criem soluções e experimentem a StackSpot EDP. Como base para a criação do Plugin, foi utilizado o seguinte código: plugins-stackspot-edp.
A publicação da solução seguiu as instruções da documentação oficial da StackSpot EDP. Inicialmente, foi criada uma Stack chamada “qa-stack” em uma conta pessoal associada (confira mais detalhes aqui). Em seguida, o Plugin “mutation-java” foi publicado (passo a passo aqui), com o objetivo de simplificar as configurações para a execução de testes de mutação em projetos Java.
Mais detalhes sobre o processo de criação de conteúdo podem ser encontrados na documentação oficial da StackSpot EDP.
Utilizando o Plugin
Para utilizar o Plugin mutation-java, certifique-se de estar no diretório da aplicação desejada. Neste exemplo, estamos utilizando a aplicação Pequeno Investidor.
cd pequenoinvestidor
Na raiz do projeto, execute o seguinte comando para aplicar o Plugin de configuração de testes de mutação ao seu projeto:
stk apply plugin qa-tools/mutation-java@0.1.1
Você precisará fornecer algumas informações para configurar o Plugin. A primeira delas é o gerenciador de dependências usado no projeto. O Plugin oferece suporte a projetos Maven e Gradle (usando Groovy). Neste exemplo, utilizaremos Maven.
Qual gerenciador de dependenciais do projeto?
» 1) Maven
2) Gradle
Answer: 1) Maven
A próxima informação que você precisará fornecer é sobre a biblioteca de logs usada no projeto. No projeto Pequeno Investidor, estamos utilizando a biblioteca org.slf4j.Logger.
Qual lib de logs utilizada no projeto? (Use shortcuts or arrow keys)
1) java.util.logging
2) org.apache.log4j
» 3) org.slf4j
4) org.apache.commons.logging
Answer: 3) org.slf4j
Nota: Trechos de logs podem interferir nos resultados dos testes de mutação. Para evitar isso, configure o Pitest para ignorar chamadas de log, utilizando a opção avoidCallsTo.
Defina a cobertura do teste
Em seguida, defina a cobertura de mutantes esperada para o projeto após a execução dos testes de mutação. Considerando que o projeto “Pequeno Investidor” tem uma cobertura de testes unitários reduzida, estabelecemos a meta de 50% para a cobertura de mutantes.
Qual cobertura de mutantes esperada?(1% a 99%)
Nota: A cobertura de mutantes é calculada pela porcentagem de mutantes mortos em relação ao número total de mutantes gerados: (Mutantes Mortos / Número Total de Mutantes) × 100.
Esse indicador é essencial para avaliar a eficácia dos testes unitários na identificação de mutantes. Portanto, uma alta taxa de mutantes sobreviventes indica uma baixa cobertura deles, sugerindo que os testes não estão detectando todos os possíveis defeitos.
Configure o grupo de mutantes
A seguir, você precisará especificar o grupo de mutantes que será configurado para o seu projeto. Essa configuração define a quantidade e os tipos de mutantes que serão gerados. Por padrão, a ferramenta PITest utiliza os mutantes OLD_DEFAULTS. No entanto, nesta configuração, optamos pela opção DEFAULTS.
Grupo de mutantes a ser utilizado?
1) OLD_DEFAULTS
» 2) DEFAULTS
3) STRONGER
4) ALL
Answer: 2) DEFAULTS
Nota: Mais detalhes sobre as mutações geradas no código da aplicação com base no grupo escolhido podem ser conferidas em mutators.
Agora, temos duas informações importantes. Primeiro, precisamos informar ao Pitest o pacote onde estão as classes de negócio da aplicação, para que ele possa gerar os mutantes corretamente.
Embora seja possível usar um padrão genérico como com.* para gerar mutantes para todas as classes do projeto, essa abordagem não é recomendada. Neste exemplo, vamos especificar o pacote com.money.pequenoinvestidor.services.imp.CalculoServiceImp.
Qual o pacote das classes a serem gerados mutantes?com.money.pequenoinvestidor.services.imp.CalculoServiceImp
Nota: Nos testes de mutação, é crucial focar nas classes onde as mutações serão aplicadas. O objetivo é verificar se os testes unitários conseguem detectar alterações nas regras de negócio. Dependendo da arquitetura do projeto, recomenda-se concentrar as mutações nas classes que implementam essas regras, ignorando interfaces, classes abstratas e outros elementos que possam distorcer os resultados dos testes de mutação.
Especifique o pacote
Precisamos especificar o pacote onde estão as classes de teste do nosso projeto. Neste exemplo, utilizaremos o pacote com.money.pequenoinvestidor.TestCalculoServiceImp.
Qual o pacote dos testes unitários? com.money.pequenoinvestidor.TestCalculoServiceImp
Ao concluir o processo, a StackSpot indicará que o Plugin foi aplicado com sucesso ao projeto.
- Plugin qa-tools/mutation-java@0.1.1 aplicado.
Ao acessar o arquivo pom.xml do projeto Pequeno Investidor, podemos verificar que as configurações do Plugin Pitest foram aplicadas corretamente. As entradas fornecidas serviram como base para a configuração dos testes de mutação.
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<configuration>
<excludes>
<exclude>**/FiisTest.java</exclude>
</excludes>
</configuration>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.4.0</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.5.0</version>
<configuration>
<targetClasses>
<param>com.money.pequenoinvestidor.services.imp.CalculoServiceImp</param>
</targetClasses>
<targetTests>
<param>com.money.pequenoinvestidor.TestCalculoServiceImp</param>
</targetTests>
<mutators>
<mutator>DEFAULTS</mutator>
</mutators>
<outputFormats>
<outputFormat>HTML</outputFormat>
<outputFormat>XML</outputFormat>
</outputFormats>
<failWhenNoMutations>false</failWhenNoMutations>
<avoidCallsTo>
<avoidCallsTo>org.slf4j</avoidCallsTo>
</avoidCallsTo>
<mutationThreshold>50</mutationThreshold>
</configuration>
</plugin>
</plugins>
Executando os teste de mutação
Com a ferramenta Pitest configurada no projeto, podemos executar os testes de mutação de forma simples. Para isso, basta executar o seguinte comando no Maven:
mvn test-compile org.pitest:pitest-maven:mutationCoverage
Teremos uma saída semelhante a esta:
--------------------------------------------------------------------------------
> Total : 1 seconds
--------------------------------------------------------------------------------
================================================================================
- Statistics
================================================================================
>> Generated 14 mutations Killed 11 (79%)
>> Ran 19 tests (1.36 tests per mutation)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.977 s
[INFO] Finished at: 2024-07-12T18:28:29-03:00
[INFO] ------------------------------------------------------------------------
Nota: Embora tenhamos definido um limite de cobertura de 50%, os resultados para a classe CalculoServiceImp foram promissores: dos 14 mutantes gerados, 79% foram detectados pelos testes unitários existentes.
O Pitest já gera relatórios de saída automaticamente, permitindo identificar diversos pontos, como dados de cobertura:
Confira os tipos de mutantes gerados com base no grupo selecionado, junto com alguns detalhes específicos de cada mutação:
Conclusão
Escrever testes unitários é apenas uma parte do processo; garantir a qualidade desses testes é fundamental. É aqui que o teste de mutação se torna uma ferramenta poderosa, ajudando a identificar falhas na lógica do código.
Este é mais um exemplo prático de como a StackSpot EDP facilita a aplicação/criação de testes e melhora a qualidade do software. A ferramenta simplifica a configuração de testes de mutação, tornando o processo muito mais eficiente e replicável.
Ficou com alguma dúvida ou tem sugestões? Deixe um comentário.