Introdução: os testes passam, mas os bugs permanecem?
\n\nImagine 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?
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\nNeste 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\nO que é teste de mutação?
\n\nTeste 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\nEm 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\nExemplo de mutação
\n\nSuponha que você tenha uma função simples:
\n\nfunc IsEven(n int) bool {\n return n%2 == 0\n}\n\nUma mutação típica é substituir == por !=:
func IsEven(n int) bool {\n return n%2 != 0\n}\n\nSe 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.
Por que a simples cobertura de código não é suficiente?
\n\nMuitos desenvolvedores em Go estão acostumados a confiar no go test -cover. É uma métrica útil, mas enganosa. Considere o exemplo:
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\nTeste:
\n\nfunc 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\nCobertura? 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.
O teste de mutação em Go identificará imediatamente esse problema, criando um mutante com multiplicação e verificando se o teste falha.
\n\nFerramentas para teste de mutação em Go
\n\nA principal ferramenta é o go-mutesting. É uma biblioteca popular que cria automaticamente mutações e executa os testes. Instalação:
\n\ngo get github.com/zimmski/go-mutesting/cmd/go-mutesting\n\nExistem também alternativas, como o mutesting da empresa "Go Test It", mas o go-mutesting é o padrão de facto.
Como executar
\n\nExecução básica:
\n\ngo-mutesting ./...\n\nEste 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.
Exemplo prático: teste de mutação em Go
\n\nVamos criar um pacote simples calculator com funções de adição e