Teste de Mutação em Java: Quebrando o Código para Melhorá-lo

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Seus testes estão mentindo, e você nem sabe

\n\n

Imagine a situação: você escreveu testes unitários, o code coverage (cobertura de código) é de 95%. O chefe está satisfeito, o pipeline CI/CD está verde. Mas na sexta-feira à noite, um bug aparece em produção. Familiar?

\n\n

O problema é que a cobertura de código clássica é uma ilusão. Ela mostra quais linhas de código foram executadas, mas não responde à pergunta principal: os testes realmente verificam a lógica? É aqui que entra o mutation testing — uma técnica que "quebra" impiedosamente seu código para verificar o quão bons são seus testes.

\n\n

Neste artigo, vamos explorar o que é teste de mutação em Java, como ele funciona e mostrar exemplos reais usando a ferramenta PIT (Pitest). Você aprenderá como transformar seus testes de "um item no relatório" em uma proteção real contra bugs.

\n\n

O que é mutation testing e por que é importante?

\n\n

Mutation testing (ou teste de mutação) é um método de avaliação da qualidade dos testes, no qual pequenas alterações deliberadamente incorretas (chamadas de mutações) são introduzidas no código-fonte. Em seguida, seus testes são executados. Se pelo menos um teste falhar — o mutante foi "morto", o teste funcionou. Se os testes passarem — o mutante "sobreviveu", e isso é um sinal de alerta.

\n\n

Uma metáfora simples: você verifica o quão bem seu alarme detecta ladrões. Você coloca um "ladrão" (mutante) e observa se o alarme (teste) dispara. Se o ladrão sai tranquilamente, o alarme não serve para nada.

\n\n

Por que o code coverage é um indicador ruim?

\n\n

Considere este código:

\n\n
public boolean isAdult(int age) {\n    if (age >= 18) {\n        return true;\n    }\n    return false;\n}
\n\n

E o teste:

\n\n
@Test\nvoid testIsAdult() {\n    assertTrue(isAdult(18));\n}
\n\n

A cobertura de código mostrará 100% (ambos os ramos do if-else foram executados?). Não! Apenas o ramo true foi executado. Mas o teste está verde. Agora imagine que alguém acidentalmente muda a condição para age > 21. O teste ainda passará, porque 18 > 21 é false, e assertTrue espera true. O bug irá para produção.

\n\n

O teste de mutação pegaria essa situação instantaneamente, criando um mutante que altera >= para > ou inverte a condição.

\n\n

Como funciona o mutation testing: teoria simplificada

\n\n

O processo consiste em três etapas:

\n\n
    \n
  1. Geração de mutantes. A ferramenta (por exemplo, PIT) cria várias versões do seu código com pequenas alterações: troca operadores, remove chamadas de métodos, inverte condições.
  2. \n
  3. Execução dos testes. Para cada mutante, seus testes unitários são executados.
  4. \n
  5. Análise dos resultados. Se o teste falhar — o mutante foi morto. Se passar — está vivo. A porcentagem de mutantes mortos é chamada de mutation score (pontuação de mutação).
  6. \n
\n\n

Um bom indicador: 80-90% de mutantes mortos. Qualquer valor abaixo disso é motivo para reescrever os testes.

\n\n

Exemplo prático em Java com PIT

\n\n

Vamos usar um exemplo real. Suponha que temos um serviço para calcular desconto:

\n\n
public class DiscountService {\n    public double calculateDiscount(double amount, boolean isPremium) {\n        double discount = 0.0;\n\n        if (amount > 100) {\n            discount = 0.1; // 10% de desconto\n        }\n\n        if (isPremium) {\n            discount += 0.05; // adicional 5%\n        }\n\n        // O desconto não pode ser maior que 30%\n        if (discount > 0.3) {\n            discount = 0.3;\n        }\n\n        return discount;\n    }\n}
\n\n

Vamos escrever um teste ingênuo:

\n\n
import org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass DiscountServiceTest {\n\n    private final DiscountService service = new                

Blogs

Book Recommendations