Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Mutation Testing com Stryker

Atualizado em: 6 de setembro de 2026

Terminal Linux usado em desenvolvimento e administração de aplicações Node.js

O Mutation Testing com Stryker avalia a qualidade dos testes modificando pequenas partes do código e verificando se a suíte detecta as alterações. Se uma condição é invertida, um operador é removido ou um retorno muda e os testes continuam passando, existe uma lacuna de verificação.

Coverage mostra quais linhas foram executadas, mas não confirma se os asserts realmente protegem o comportamento. Mutation testing complementa coverage ao medir quantas alterações artificiais são eliminadas pelos testes.

Neste guia, você aprenderá mutants, mutation score, instalação do StrykerJS, configuração, runners, TypeScript, incremental mode, performance, CI, surviving mutants e boas práticas.

O que é Mutation Testing?

Mutation testing cria versões modificadas do programa, chamadas mutants. A suíte é executada contra cada versão:

  • se os testes falham, o mutant foi morto;
  • se continuam passando, o mutant sobreviveu;
  • se o código não é coberto, o mutant fica sem cobertura;
  • se a execução excede o limite, ocorre timeout;
  • se não compila, pode ser classificado como erro ou inválido.

A documentação oficial do StrykerJS apresenta inicialização, configuração e execução. A documentação de métricas e estados explica mutation score e resultados.

Para coverage nativa, consulte Cobertura de Testes no Node.js. Para geração de casos, veja Property-Based Testing no Node.js.

Exemplo de mutant

Código original:

export function canApprove(order) {
  return order.status === 'pending'
    && order.totalCents > 0;
}

Mutants possíveis:

order.status !== 'pending'
order.totalCents >= 0
true
false

Uma suíte forte deve falhar para essas mudanças.

Coverage não basta

test('executa canApprove', () => {
  canApprove({
    status: 'pending',
    totalCents: 100
  });
});

A linha é coberta, mas não existe assert. Quase todos os mutants sobrevivem.

Teste que mata mutants

test('aprova pedido pendente com total positivo', () => {
  assert.equal(
    canApprove({
      status: 'pending',
      totalCents: 100
    }),
    true
  );
});

test('rejeita total zero', () => {
  assert.equal(
    canApprove({
      status: 'pending',
      totalCents: 0
    }),
    false
  );
});

test('rejeita pedido aprovado', () => {
  assert.equal(
    canApprove({
      status: 'approved',
      totalCents: 100
    }),
    false
  );
});

Instalação

npm init stryker@latest

O inicializador detecta test runner e cria stryker.config.mjs. Revise o arquivo antes de executar.

Executando

npx stryker run

A primeira execução pode demorar porque todos os mutants relevantes são testados.

Configuração básica

/** @type {import('@stryker-mutator/api/core').StrykerOptions} */
export default {
  testRunner: 'vitest',
  mutate: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/**/index.ts'
  ],
  reporters: [
    'clear-text',
    'progress',
    'html'
  ],
  coverageAnalysis: 'perTest',
  thresholds: {
    high: 80,
    low: 60,
    break: 60
  }
};

Plugins e opções exatas dependem do runner e versão.

Campo mutate

Selecione código de produção que contém comportamento:

  • casos de uso;
  • domínio;
  • validadores;
  • mappers importantes;
  • algoritmos;
  • policies.

Exclua arquivos gerados, tipos, barrels e configuração sem lógica.

Não mutacione testes

O objetivo é modificar o código testado, não a suíte. Os testes permanecem como detector.

Test runners

StrykerJS possui plugins para runners como Vitest, Jest, Mocha, Jasmine, Karma, Cucumber e TAP. Configure o plugin compatível com a versão atual.

Node Test Runner

O suporte depende do ecossistema e versão do Stryker. Consulte os plugins oficiais antes de assumir integração direta. Um projeto pode executar via TAP quando compatível.

TypeScript

Stryker pode mutar TypeScript e usar checker para detectar erros. Escolha transpiler e checker conforme o projeto.

export default {
  checker: 'typescript',
  tsconfigFile: 'tsconfig.json'
};

Type checking aumenta custo, mas evita executar mutants inválidos.

Mutators comuns

  • troca de operadores aritméticos;
  • inversão de booleanos;
  • mudança de comparadores;
  • remoção de condicionais;
  • substituição de strings;
  • remoção de chamadas;
  • mudança de arrays e objetos;
  • alteração de optional chaining.

Mutation score

Uma forma simplificada:

mortos / (mortos + sobreviventes) * 100

A ferramenta pode considerar outros estados conforme a métrica escolhida.

Score não é meta absoluta

Buscar 100% a qualquer custo pode produzir testes acoplados à implementação ou asserts sem valor. Analise risco e comportamento.

Thresholds

thresholds: {
  high: 85,
  low: 70,
  break: 65
}

break pode falhar o CI. Comece com baseline realista e aumente gradualmente.

Surviving mutant

Quando um mutant sobrevive, pergunte:

  • faltou um cenário?
  • o assert é fraco?
  • o código é desnecessário?
  • o mutant é equivalente?
  • o teste não chegou ao comportamento?
  • há mock excessivo?

Mutant equivalente

Um mutant equivalente muda a sintaxe sem alterar o comportamento observável. Exemplo:

value > 0

pode ser equivalente a:

value >= 1

quando value é sempre inteiro. Não existe teste capaz de diferenciá-los dentro do domínio.

Como tratar equivalentes

Opções:

  • aceitar o surviving mutant;
  • refatorar para expressão mais clara;
  • desabilitar mutação específica com comentário;
  • excluir uma linha apenas com justificativa.

Não desabilite sem análise

Uma exclusão ampla pode esconder lacunas futuras. Documente o motivo e mantenha escopo pequeno.

Mutants sem cobertura

Indicam código nunca executado pela suíte. Primeiro escreva um teste que alcance a linha; depois melhore asserts para matar a alteração.

Timeout mutants

Um mutant pode criar loop ou caminho lento. Stryker usa timeout calculado com base na suíte original e fator configurável.

Timeout excessivo

Se muitos mutants expiram:

  • reduza testes lentos;
  • separe integração;
  • revise fake timers;
  • evite operações sem limite;
  • ajuste timeout apenas após investigar.

Sandbox

Stryker executa mutants em ambientes temporários. Não dependa de caminhos absolutos, arquivos externos mutáveis ou estado global.

Side effects nos testes

Uma suíte que envia e-mail, escreve em banco compartilhado ou chama API externa torna mutation testing lento e instável. Prefira unitários para a maior parte dos mutants.

Mocks

Mocks podem matar mutants de interação:

assert.equal(repository.save.mock.calls.length, 1);

Mas asserts de chamadas demais acoplam o teste. Priorize resultados e efeitos relevantes.

Domínio

Entidades e Value Objects são bons candidatos porque possuem regras rápidas e determinísticas.

Consulte Value Objects no Node.js e Domain-Driven Design no Node.js.

Result Pattern

Teste todas as variantes de Ok e Err. Mutants que trocam tipos de erro ou removem validação devem ser eliminados.

Consulte Result Pattern no Node.js.

Erros e boundaries

Mutar > para >= revela ausência de testes de limite. Inclua:

  • um valor abaixo;
  • o limite exato;
  • um valor acima.

Property-Based Testing

Properties geram muitos casos, mas não garantem matar todos os mutants. Stryker ajuda a avaliar se a propriedade realmente detecta alterações.

Snapshot tests

Snapshots grandes podem matar muitos mutants por diferenças irrelevantes ou esconder intenção. Revise snapshots e use asserts semânticos.

Consulte Snapshot Tests no Node.js.

Incremental mode

O modo incremental reutiliza resultados quando arquivos e testes não mudaram, acelerando execuções seguintes.

incremental: true,
incrementalFile: '.stryker-tmp/incremental.json'

Confirme opções atuais e trate o arquivo como cache, não fonte definitiva.

Only changed

Algumas equipes executam mutants apenas em código alterado no pull request. Isso reduz tempo, mas uma mudança pode afetar regras em arquivos não modificados.

Estratégia de CI

  • pull request: código alterado ou módulos críticos;
  • main: escopo maior;
  • nightly: suíte completa;
  • release: baseline obrigatório.

Paralelismo

concurrency: 4

Mais workers consomem CPU e memória. Meça o runner; concorrência excessiva pode deixar tudo mais lento.

Coverage analysis

  • off: todos os testes para cada mutant.
  • all: usa coverage para reduzir testes.
  • perTest: executa apenas testes que cobrem o mutant.

Os modos disponíveis e comportamento dependem do runner.

Dry run

Antes de gerar mutants, Stryker executa os testes originais. Se a suíte falha ou é flakey, a análise não é confiável.

Flakiness

Elimine dependência de:

  • tempo real;
  • ordem;
  • rede;
  • random sem seed;
  • estado compartilhado;
  • timers não aguardados.

Reporters

Reporters comuns:

  • clear-text;
  • progress;
  • html;
  • json;
  • dashboard.

O relatório HTML permite navegar por arquivo e mutant.

Stryker Dashboard

O Dashboard pode armazenar score e tendências. Proteja tokens e não envie código sensível sem revisar a política do serviço.

Baseline

Registre o score inicial e evite queda. Depois aumente metas por módulo crítico.

Mutation score por módulo

Um score global alto pode esconder um módulo de pagamentos fraco. Observe resultados por domínio e risco.

Código gerado

Exclua clientes OpenAPI, migrations geradas, bundles e arquivos que não devem ser testados diretamente.

Código trivial

Getters simples podem gerar mutants equivalentes ou pouco relevantes. Concentre análise em decisões.

Performance

Mutation testing executa a suíte muitas vezes. Para reduzir custo:

  • mantenha unitários rápidos;
  • use per-test coverage;
  • limite mutate;
  • use incremental;
  • separe integração;
  • ajuste concorrência;
  • execute completo à noite.

Integração pesada

Não execute Testcontainers para cada mutant se uma suíte unitária pode proteger a regra. Mantenha integração para adapters.

Debug de mutant

O relatório informa localização e alteração. Reproduza mentalmente ou desabilite temporariamente outros mutants para entender o teste necessário.

Não escreva teste para a linha

Escreva um cenário de comportamento que falharia se a alteração fosse real. Isso evita testes artificiais.

Revisão em pull request

Ao adicionar regra crítica, verifique se os testes matam mudanças no limite, condição e retorno. O relatório pode entrar como artifact do CI.

Erros comuns

  • Meta 100% imediata: equipe desabilita mutants sem análise.
  • Mutar tudo: execução fica inviável.
  • Coverage confundida com qualidade: asserts fracos permanecem.
  • Ignorar sobreviventes: relatório não gera ação.
  • Teste de implementação: refatoração quebra sem mudança de comportamento.
  • Suíte flakey: resultados ficam falsos.
  • Integração por mutant: CI demora demais.

Boas práticas

  • Comece por módulos críticos.
  • Defina baseline.
  • Use thresholds graduais.
  • Analise cada sobrevivente.
  • Teste limites.
  • Mantenha unitários rápidos.
  • Use incremental.
  • Execute completo periodicamente.
  • Documente equivalentes.
  • Escreva testes de comportamento.

Conclusão

O Mutation Testing com Stryker verifica se os testes detectam alterações reais no comportamento. Mutants sobreviventes revelam asserts fracos, limites esquecidos e código não protegido.

Com escopo controlado, incremental mode, thresholds graduais e análise por risco, Stryker complementa coverage sem tornar o CI impraticável. O objetivo não é um número perfeito, mas confiança de que a suíte falha quando a regra muda.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita