Testes de Mutação em JavaScript: como capturar bugs indetectáveis

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

Seus testes estão mentindo? Hora do teste de mutação

\n\n

Você 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.

\n\n

O que é teste de mutação em palavras simples

\n\n

Imagine 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\n

Se 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.

\n\n

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
\n\n

Por que code coverage é uma falsa segurança

\n\n

Muitos 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\n

E o teste "perfeito":

\n\n
test('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\n

Cobertura: 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".

\n\n

Ferramenta nº1: Stryker Mutator para JavaScript

\n\n

A 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\n

Instalação e início rápido

\n\n

Instale o Stryker globalmente ou localmente no seu projeto:

\n\n
npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
\n\n

Crie o arquivo de configuração stryker.conf.json:

\n\n
{\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\n

Execute a mutação

Blogs

Book Recommendations