Teste de Mutação em C++: quando 100% de cobertura de código é uma mentira
\n\nVocê se orgulha dos seus 95% de code coverage? Talvez esteja enganado. Cobertura de código é como uma fechadura que verificam apenas puxando a maçaneta. Já o teste de mutação é um arrombador que realmente tenta abrir o cofre. Neste artigo, vamos explorar como o teste de mutação C++ transforma seus testes de uma formalidade em uma verdadeira arma. E sim, não vamos copiar e colar definições chatas da wikipedia.
\n\nImagine: você escreveu uma função, cobriu-a com testes, o CI está verde. E então um bug vai para produção. Dor familiar? Na maioria das vezes, o problema não está no código, mas nos testes que não verificam o que deveriam. Mutation testing para C++ resolve exatamente esse problema — ele mostra o quão perigosos seus testes realmente são para os bugs.
\n\nO que é teste de mutação e por que ele é importante para o desenvolvedor C++?
\n\nTeste de mutação (ou mutation testing) é uma metodologia para avaliar a qualidade dos testes através da introdução de pequenas alterações (mutações) no código fonte. Se após a mutação os testes ainda passam — significa que eles são fracos. O mutante sobreviveu. Se os testes falham — o mutante foi morto, o teste funciona.
\n\nPara C++, isso é especialmente relevante porque:
\n- \n
- O código C++ frequentemente contém lógica complexa com ponteiros, templates e RAII. \n
- Métricas padrão de cobertura (line coverage, branch coverage) são fáceis de enganar. \n
- O custo de um bug em C++ (especialmente em embedded, finance, game engines) pode ser catastrófico. \n
Um exemplo simples: você tem uma função de verificação de idade:
\n\nbool is_adult(int age) {\n return age >= 18;\n}\n\nTeste:
\n\nTEST(AdultTest, Positive) {\n EXPECT_TRUE(is_adult(20));\n}\n\nCobertura: 100% das linhas. Mas a mutação age >= 18 → age > 18 não será detectada, porque 20 ainda passa. O mutante sobreviveu. E se você tivesse um teste para is_adult(18) e is_adult(17) — você teria pego o bug. Teste de mutação C++ força você a escrever esses testes de limite.
Principais operadores de mutação em C++
\n\nAs ferramentas de teste de mutação aplicam um conjunto de operadores (mutators). Aqui estão os mais comuns para C++:
\n\n- \n
- Substituição de operadores aritméticos:
+→-,*→/\n - Substituição de operadores lógicos:
&&→||,==→!=\n - Alteração de condições de limite:
>=→>,<→<=\n - Remoção de expressões: chamada de função substituída por uma constante \n
- Alteração do valor de retorno:
return true→return false\n - Mutação de loops: alteração da condição de saída ou do passo \n
Exemplo com código real:
\n\nint factorial(int n) {\n if (n <= 1) return 1;\n return n * factorial(n - 1);\n}\n\nMutação #1: n <= 1 → n < 1. Se o teste chama factorial(1) e espera 1, mas a mutação remove o caso base — o teste falhará apenas se você verificar factorial(0) ou factorial(1) separadamente.
Mutação #2: return n * factorial(n - 1) → return n + factorial(n - 1). Se os testes são apenas para números positivos, eles podem não perceber a substituição da multiplicação pela adição.
São exatamente esses casos que o mutation testing em C++ revela.
\n\nFerramentas para teste de mutação C++
\n\nNem todas as ferramentas são igualmente úteis. Aqui estão três principais players no mercado C++:
\n\n1. Mull
\nProjeto moderno e ativo. Funciona com base em LLVM pass. Suporta C++17/20, integra-se com Google Test, Catch2, doctest