Seus testes estão mentindo? Hora do teste de mutação
\n\nVocê escreve testes unitários, a cobertura de código (code coverage) agrada aos olhos — 85%, 90%, mas os bugs ainda aparecem em produção? Dor familiar. A métrica clássica de cobertura (line/branch coverage) é, basicamente, uma medição de quais linhas de código foram executadas. Mas ela não diz nada sobre o quão bem os testes verificam a lógica. Eles podem simplesmente "passar" pelas linhas, sem verificar a correção do resultado.
\n\nÉ aqui que entra o mutation testing (teste de mutação). É como uma injeção de vacina para o seu código: introduzimos deliberadamente pequenos "vírus" (mutações) no programa e vemos se nossos testes os eliminam. Se o teste não falhar após alterar === para !== — seus testes são fracos. Neste artigo no pythonlib.ru, vamos analisar como implementar o mutation testing em um projeto JavaScript para tornar seu código verdadeiramente confiável.
O que é teste de mutação em palavras simples
\n\nImagine que você é um engenheiro de QA que secretamente altera uma linha no código do desenvolvedor:
\n\n// Era\nif (user.age >= 18) { allowAccess(); }\n\n// Ficou (mutação)\nif (user.age > 18) { allowAccess(); }\n\nSe após essa substituição os testes unitários ainda estiverem verdes — os testes não verificam a condição de limite 18. O framework de mutação faz milhares dessas substituições automaticamente e gera um relatório: quantas mutações sobreviveram (survived). Quanto menos mutantes sobreviventes — mais confiáveis são seus testes.
Principais tipos de mutações em JavaScript:
\n- \n
- Alteração de operadores de comparação:
>→>=,===→!==\n - Remoção de chamadas de funções:
validate()→ string vazia \n - Alteração de literais numéricos:
0→1,100→0\n - Inversão de condições lógicas:
if (isActive)→if (!isActive)\n - Substituição de valores de string (menos comum, mas acontece) \n
Por que code coverage é uma falsa segurança
\n\nMuitos desenvolvedores JavaScript se iludem com 100% de cobertura de código. Vamos ver um exemplo real:
\n\n// Função que determina o desconto\nfunction getDiscount(price, isPremium) {\n if (price > 100 && isPremium) {\n return price * 0.8;\n }\n return price;\n}\n\nE o teste "perfeito":
\n\ntest('deve retornar o preço para não premium', () => {\n expect(getDiscount(50, false)).toBe(50);\n});\n\ntest('deve retornar o preço para premium com preço baixo', () => {\n expect(getDiscount(50, true)).toBe(50);\n});\n\nCobertura: 100% das linhas, 100% dos ramos. Mas a mutação price > 100 → price >= 100 sobreviverá! Os testes não verificam o valor limite 100.01 ou exatamente 100. Mutation testing destacará instantaneamente esse "ponto cego".
Ferramenta nº1: Stryker Mutator para JavaScript
\n\nA ferramenta mais popular e madura para teste de mutação no mundo JS é o Stryker Mutator (ou simplesmente Stryker). Ele suporta Jest, Mocha, Jasmine, Karma e funciona com TypeScript.
\n\nInstalação e início rápido
\n\nInstale o Stryker globalmente ou localmente no seu projeto:
\n\nnpm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner\n\nCrie o arquivo de configuração stryker.conf.json:
{\n "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",\n "packageManager": "npm",\n "reporters": ["html", "clear-text", "progress"],\n "testRunner": "jest",\n "coverageAnalysis": "perTest",\n "thresholds": {\n "high": 80,\n "low": 60,\n "break": 50\n }\n}\n\nExecute a mutação