O paradoxo da cobertura de 100% — ou por que seus testes podem estar mentindo
\n\nVocê escreveu um código ideal em Rust. O compilador está satisfeito, o clippy está em silêncio, os testes passam em uma onda verde, e o relatório de cobertura informa orgulhosamente: 100% das linhas estão cobertas. Pode respirar aliviado? Não. Testes de mutação em Rust é exatamente a técnica que mostrará que seus testes, talvez, protegem o ar, e não a lógica.
\n\nImagine: você acidentalmente muda + para - em uma função crítica. Os testes ainda estão verdes? Parabéns, você encontrou um mutante — um defeito que vive no seu código, mas que os testes não percebem. Neste artigo, vamos analisar como testes de mutação em Rust ajudam a encontrar esses pontos cegos e implementá-lo em um projeto real.
O que são testes de mutação (e por que Rust é ideal para eles)
\n\nTestes de mutação (ou teste de mutação) é um método para avaliar a qualidade dos testes, no qual pequenas alterações artificiais (mutações) são introduzidas no código-fonte e, em seguida, os testes são executados. Se os testes não falharem no código mutado — significa que o mutante sobreviveu. Quanto mais mutantes sobreviventes, piores são seus testes.
\n\nRust é uma linguagem que, por si só, detecta classes inteiras de erros em tempo de compilação: condições de corrida, ponteiros nulos, use-after-free. Mas testes de mutação em Rust visam especificamente erros lógicos — aquilo que o compilador não vê. E isso torna Rust uma plataforma ideal: você já está protegido contra 70% dos bugs típicos, e os testes de mutação eliminam os 30% restantes.
\n\nComo é uma mutação no código Rust
\n\nMutações típicas incluem:
\n- \n
- Alteração de operadores:
a + b→a - b\n - Inversão de condições:
if x > 5→if x < 5\n - Substituição de constantes:
let n = 42→let n = 0\n - Remoção de chamadas de função:
process(data)→ chamada vazia \n - Alteração de limites de iteração:
0..n→0..=n\n
Se após essa mutação pelo menos um teste não falhar — significa que você perdeu tempo escrevendo esse teste. Ele não verifica o que deveria.
\n\nFerramentas para testes de mutação em Rust: cargo-mutants
\n\nAtualmente, a principal ferramenta para testes de mutação em Rust é o cargo-mutants. Ele é maduro, ativamente mantido e fornece relatórios legíveis. A instalação é simples:
cargo install cargo-mutants\n\n\nDepois disso, basta executar:
\n\ncargo mutants\n\n\nE a ferramenta encontrará automaticamente todas as mutações no seu código, executará os testes e gerará um relatório HTML com indicação por cores: verde — mutantes mortos (testes funcionaram), vermelho — sobreviventes (problema).
\n\nAlternativas no radar
\n- \n
- mutagen — mais antigo, mas ainda funcional. Menos mutações, mais rápido em projetos pequenos. \n
- cargo-insta — não é exatamente teste de mutação, mas é próximo: ajuda a verificar visualmente alterações em snapshots de teste. \n
Para trabalho sério, recomendo o cargo-mutants — ele oferece o melhor equilíbrio entre velocidade e profundidade de análise.
Exemplo prático: testando uma calculadora de descontos
\n\nSuponha que temos um módulo para calcular descontos em uma loja online em Rust:
\n\n// src/discount.rs\n\npub fn calculate_discount(price: f64, customer_age: u32) -> f64 {\n let mut discount = 0.0;\n\n // Desconto de 10% para aposentados (maiores de 65)\n if customer_age > 65 {\n discount += 0.1;\n }\n\n // Desconto adicional de 5% para compras acima de 1000 reais\n if price > 1000.0 {\n discount += 0.05;\n }\n\n // Desconto máximo não superior a 30%\n if discount > 0.30 {\n discount = 0.30;\n