Testes de mutação: configure em projetos back-end com a StackSpot EDP

Capa do artigo sobre Testes de mutação: configure em projetos back-end com a StackSpot EDP. Na imagem, temos um time de pessoas desenvolvedoras com cinco pessoas, duas sentadas, uma mulher branca e ruiva e um homem branco de cabelo e barba castanhos. Em pé, temos três pessoas, duas mulheres brancas e loiras e uma mulher negra de cabelos pretos e crespos. Todos estão conversando sobre um projeto.
Aprenda o que são testes de mutação e como criá-los usando os recursos de uma plataforma de desenvolvimento, a StackSpot EDP.

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

Imagem do artigo sobre testes de mutação. A imagem apresenta um diagrama que ilustra o processo de teste de mutação em dois passos principais.Primeiro Passo: Introdução de FalhasO diagrama começa com um bloco que representa o "Programa Original".Uma seta, com o rótulo "Fault Introduction" (Introdução de Falha), aponta do "Programa Original" para um grupo de blocos denominados "Mutant Program" (Programa Mutante).Esses "Programas Mutantes" são versões do programa original, mas com pequenas modificações (falhas) introduzidas de forma deliberada.Segundo Passo: Aplicação de Casos de TesteNa segunda parte do diagrama, tanto o "Programa Original" quanto os "Programas Mutantes" são submetidos aos mesmos casos de teste.Há uma seta que aponta para ambos os programas com a anotação "Test Cases Applied to Both Original & Mutant Program" (Casos de Teste Aplicados ao Programa Original e Mutante).Em seguida, os resultados dos testes são comparados.Se os resultados para o "Programa Original" e o "Programa Mutante" forem diferentes, o programa mutante é considerado "KILLED" (eliminado), como indicado por um ícone de balança e a anotação "if results for original and mutant program are different, mutant is KILLED" (se os resultados para o programa original e o programa mutante forem diferentes, o mutante é eliminado).

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:

Java
public class Calculadora {
    public int soma(int a, int b) {
        return a + b;
    }
}

Mutante:

Java
public class Calculadora {
    public int soma(int a, int b) {
        return a - b; // Alteração intencional
    }
}

Teste Unitário:

Java
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

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.

Imagem do artigo sobre testes de mutação. A imagem mostra uma interface de usuário de uma plataforma ou ferramenta chamada "qa-stacks." Na parte superior, há um título "qa-stacks" em destaque, seguido pela opção para copiar o "Slug" e um botão para "Nova Versão."Logo abaixo, há uma seção que parece estar vazia, indicada pela mensagem "Nenhum link disponível."Abaixo desta seção, há um menu com as seguintes opções:Plugins (selecionado)StartersActionsDocumentaçãoNa aba de Plugins, há filtros para "Tecnologias" e "Todos," e um plugin listado chamado "mutation-java." Este plugin é marcado como pertencente à categoria "App" e está na versão 0.1.1. À direita do nome do plugin, há um botão para "Detalhes do plugin." A imagem mostra a interface de usuário na ferramenta stackspot EDP onde está selecionado o estudio "qa tools" e a stack "qa-stacks." Logo abaixo, há uma seção que parece estar vazia, indicada pela mensagem "Nenhum link disponível."Abaixo desta seção, há um menu com as seguintes opções:Plugins (selecionado)StartersActionsDocumentaçãoNa aba de Plugins, há filtros para "Tecnologias" e "Todos," e um plugin listado chamado "mutation-java." Este plugin é marcado como pertencente à categoria "App" e está na versão 0.1.1. À direita do nome do plugin, há um botão para "Detalhes do plugin."A interface tem um design escuro, com textos em branco e detalhes em cores como rosa e laranja.

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:

Imagem do artigo sobre testes de mutação. A imagem é um relatório do PITest que mostra a cobertura de testes de uma classe específica (CalculoServiceImp) no projeto. O relatório indica que, de 14 mutantes gerados, 11 foram mortos, resultando em 79% de cobertura de mutação. A mesma porcentagem se aplica à cobertura de linhas de código, com 11 de 14 linhas cobertas. O pacote analisado é com.money.pequenoinvestidor.services.imp. O relatório usa barras de progresso coloridas para ilustrar a cobertura atingida.

Confira os tipos de mutantes gerados com base no grupo selecionado, junto com alguns detalhes específicos de cada mutação:

Imagem do artigo sobre testes de mutação. A imagem é um relatório de mutações do PITest que lista as mutações aplicadas ao código e seus resultados. A seção "Mutations" mostra as alterações feitas, como substituir ou remover código, e se os testes conseguiram detectar essas mutações (KILLED, SURVIVED ou NO_COVERAGE). A maioria dos mutantes foi eliminada, mas alguns sobreviveram ou não foram cobertos. A seção "Active mutators" lista os tipos de mutadores usados, como BOOLEAN_FALSE_RETURN e VOID_METHOD_CALL_MUTATOR.

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.

Consuma inovação,
comece a transformação

Assine nosso boletim informativo para se manter atualizado sobre as práticas recomendadas mais recentes para aproveitar a tecnologia para gerar impacto nos negócios

Related posts