Teste de Mutação em Go: como capturar o que os testes unitários deixam escapar

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

Introdução: os testes passam, mas os bugs permanecem?

\n\n

Imagine a situação: você escreveu um módulo em Go, cobriu-o com testes unitários em 95% com go test -cover, todos os testes estão verdes. Você tem certeza de que o código funciona perfeitamente. Mas em produção surge um bug. Familiar?

\n\n

O problema é que a métrica de cobertura de código (code coverage) é apenas a quantidade de linhas que foram executadas. Ela não diz o que exatamente os testes verificam. Você pode ter 100% de cobertura, mas nunca verificar se a função retorna o valor correto, e não apenas se não falha com um pânico.

\n\n

É aqui que entra o teste de mutação (mutation testing) em Go — uma técnica que introduz artificialmente erros no seu código e verifica se os testes conseguem detectá-los. Se os testes não falharem no código mutado, significa que seus testes são inúteis.

\n\n

Neste artigo, vamos analisar o que é o teste de mutação em Go, como configurá-lo e por que é um must-have para projetos onde a confiabilidade é crítica. Vamos lá!

\n\n

O que é teste de mutação?

\n\n

Teste de mutação é um método de avaliação da qualidade dos testes, no qual pequenas alterações (mutações) são introduzidas no programa, simulando erros típicos de desenvolvedor. Em seguida, os testes são executados. Se pelo menos um teste falhar — a mutação foi "morta", o teste funciona. Se todos os testes passarem — a mutação "sobreviveu", e isso é uma bandeira vermelha: você tem uma brecha nos testes.

\n\n

Em Go, o teste de mutação é frequentemente chamado de mutation testing golang, e é especialmente útil para verificar lógica de negócios, validadores, parsers e outros componentes críticos.

\n\n

Exemplo de mutação

\n\n

Suponha que você tenha uma função simples:

\n\n
func IsEven(n int) bool {\n    return n%2 == 0\n}
\n\n

Uma mutação típica é substituir == por !=:

\n\n
func IsEven(n int) bool {\n    return n%2 != 0\n}
\n\n

Se o seu teste verifica apenas se IsEven(2) não falha, mas não verifica se o resultado é true, então a mutação sobreviverá. O teste passará, embora a função agora funcione exatamente ao contrário.

\n\n

Por que a simples cobertura de código não é suficiente?

\n\n

Muitos desenvolvedores em Go estão acostumados a confiar no go test -cover. É uma métrica útil, mas enganosa. Considere o exemplo:

\n\n
func Divide(a, b int) (int, error) {\n    if b == 0 {\n        return 0, errors.New("division by zero")\n    }\n    return a / b, nil\n}
\n\n

Teste:

\n\n
func TestDivide(t *testing.T) {\n    result, err := Divide(10, 2)\n    if err != nil {\n        t.Fatal(err)\n    }\n    if result != 5 {\n        t.Errorf("expected 5, got %d", result)\n    }\n}
\n\n

Cobertura? 100% — todas as linhas foram executadas. Mas e se alguém acidentalmente substituir a / b por a * b? O teste não perceberá, porque 10 * 2 = 20, e esperávamos 5. O teste falhará? Não, se não verificarmos o valor exato.

\n\n

O teste de mutação em Go identificará imediatamente esse problema, criando um mutante com multiplicação e verificando se o teste falha.

\n\n

Ferramentas para teste de mutação em Go

\n\n

A principal ferramenta é o go-mutesting. É uma biblioteca popular que cria automaticamente mutações e executa os testes. Instalação:

\n\n
go get github.com/zimmski/go-mutesting/cmd/go-mutesting
\n\n

Existem também alternativas, como o mutesting da empresa "Go Test It", mas o go-mutesting é o padrão de facto.

\n\n

Como executar

\n\n

Execução básica:

\n\n
go-mutesting ./...
\n\n

Este comando encontrará todos os arquivos _test.go no projeto e verificará quais mutações sobrevivem. O resultado é exibido no console com um relatório detalhado.

\n\n

Exemplo prático: teste de mutação em Go

\n\n

Vamos criar um pacote simples calculator com funções de adição e

Blogs

Book Recommendations