Como usar o Stryker para mutation testing em projetos JavaScript
1. Introdução ao Mutation Testing e Stryker
O mutation testing é uma técnica de avaliação da qualidade de testes que vai muito além da simples cobertura de código. Enquanto a cobertura tradicional apenas verifica se uma linha foi executada, o mutation testing introduz pequenas alterações (mutações) no código fonte e verifica se os testes existentes são capazes de detectar essas mudanças.
No mutation testing, cada versão alterada do código é chamada de mutante. Quando um teste falha ao executar contra um mutante, dizemos que o mutante foi morto. Quando todos os testes passam mesmo com a mutação, o mutante sobreviveu. A taxa de mutação (mutation score) é a porcentagem de mutantes mortos em relação ao total.
O grande problema da cobertura de código tradicional é que ela pode mostrar 100% de cobertura mesmo quando os testes não verificam corretamente o comportamento do código. Por exemplo, um teste pode executar uma função de validação de CPF mas nunca verificar se ela realmente rejeita CPFs inválidos.
O Stryker Mutator é a principal ferramenta de mutation testing para ecossistemas JavaScript e TypeScript. Ele suporta múltiplos frameworks de teste (Jest, Mocha, Jasmine, Vitest) e oferece integração com ferramentas de CI/CD, além de gerar relatórios detalhados em HTML e dashboard.
2. Instalação e Configuração Inicial do Stryker
Para instalar o Stryker em seu projeto JavaScript, execute:
npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
Crie o arquivo de configuração stryker.conf.js na raiz do projeto:
// stryker.conf.js
module.exports = {
mutate: ['src/**/*.js', '!src/**/*.test.js'],
testRunner: 'jest',
jest: {
config: require('./jest.config.js')
},
reporters: ['progress', 'html', 'clear-text'],
coverageAnalysis: 'perTest',
concurrency: 4,
thresholds: {
high: 80,
low: 60,
break: 50
}
};
Os parâmetros principais são:
- mutate: Define quais arquivos serão mutados (e quais ignorar)
- testRunner: Especifica o framework de testes (jest, mocha, jasmine)
- reporters: Define como os resultados serão exibidos
- coverageAnalysis: Pode ser 'off', 'perTest' ou 'all' — o modo 'perTest' é mais rápido pois só executa testes relevantes
3. Executando a Primeira Campanha de Mutation Testing
Vamos criar um exemplo prático com uma função simples de validação de CPF:
// src/validaCPF.js
function validaCPF(cpf) {
cpf = cpf.replace(/\D/g, '');
if (cpf.length !== 11) return false;
if (/^(\d)\1{10}$/.test(cpf)) return false;
let soma = 0;
for (let i = 0; i < 9; i++) {
soma += parseInt(cpf.charAt(i)) * (10 - i);
}
let resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
if (resto !== parseInt(cpf.charAt(9))) return false;
soma = 0;
for (let i = 0; i < 10; i++) {
soma += parseInt(cpf.charAt(i)) * (11 - i);
}
resto = (soma * 10) % 11;
if (resto === 10) resto = 0;
if (resto !== parseInt(cpf.charAt(10))) return false;
return true;
}
module.exports = validaCPF;
Agora, execute o comando:
npx stryker run
O relatório inicial pode mostrar algo como:
[Survived] ConditionalExpression: 4 mutants survived
[Survived] BinaryOperator: 3 mutants survived
Mutation score: 72.34%
Isso indica que 27,66% dos mutantes sobreviveram — ou seja, há lacunas nos testes que não detectam alterações no comportamento da validação.
4. Análise de Mutantes Sobreviventes e Falsos Positivos
Ao analisar os mutantes sobreviventes, encontramos dois tipos:
Mutantes equivalentes: São mutações que produzem código funcionalmente idêntico ao original. Por exemplo, alterar if (resto === 10) resto = 0; para if (resto !== 10) resto = 0; muda a lógica, mas em alguns contextos pode não ser detectável.
Mutantes sobreviventes legítimos: Indicam que os testes não cobrem adequadamente o comportamento. Por exemplo, se a mutação altera return false para return true e o teste passa, significa que não há teste validando CPFs inválidos.
Para lidar com falsos positivos, podemos configurar exclusões:
// stryker.conf.js
module.exports = {
// ... outras configurações
mutator: {
excludedMutations: ['StringLiteral', 'ObjectLiteral']
},
ignorePatterns: ['src/dados/**']
};
5. Otimizando a Performance do Stryker
O mutation testing pode ser computacionalmente caro. Para otimizar:
Paralelismo:
// stryker.conf.js
module.exports = {
concurrency: 8, // Ajuste conforme número de cores da CPU
maxTestRunnerReuse: 10 // Reutiliza runners para evitar overhead
};
Escopo reduzido:
module.exports = {
mutate: ['src/modulo-critico/**/*.js', '!src/modulo-critico/**/*.test.js']
};
Timeout adequado:
module.exports = {
timeoutMS: 5000, // 5 segundos por teste
timeoutFactor: 1.5
};
6. Integração Contínua e Relatórios Avançados
Para integrar com GitHub Actions:
# .github/workflows/mutation-test.yml
name: Mutation Testing
on: [push, pull_request]
jobs:
stryker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npx stryker run
env:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
Para gerar relatórios HTML avançados:
module.exports = {
reporters: ['html', 'dashboard', 'clear-text'],
dashboard: {
project: 'github.com/seu-usuario/seu-repo',
version: 'main'
}
};
Defina thresholds para bloquear o build se a qualidade cair:
module.exports = {
thresholds: {
high: 80, // Meta desejável
low: 60, // Abaixo disso é alerta
break: 50 // Abaixo disso bloqueia o build
}
};
7. Boas Práticas e Armadilhas Comuns
Quando NÃO usar mutation testing:
- Projetos sem testes unitários (primeiro crie os testes)
- Bases de código muito grandes com tempo de execução excessivo (use escopo incremental)
- Código legado sem cobertura de testes mínima
Boas práticas:
- Comece com mutação em módulos críticos (validações, regras de negócio)
- Combine com testes de unidade, integração e regressão
- Use estratégia incremental: em PRs, mude apenas o código novo
Armadilhas comuns:
- Ignorar mutantes equivalentes pode levar a metas irreais
- Executar Stryker em toda a base de código sem filtro pode ser inviável
- Não configurar coverageAnalysis corretamente pode tornar a execução extremamente lenta
O Stryker é uma ferramenta poderosa para elevar a qualidade dos testes JavaScript. Quando usado corretamente, ele revela vulnerabilidades que a cobertura de código tradicional jamais mostraria.
Referências
- Documentação oficial do Stryker Mutator — Guia completo de instalação, configuração e uso do Stryker para JavaScript/TypeScript
- Stryker Dashboard — Plataforma para visualizar relatórios históricos de mutation testing e comparar resultados entre branches
- Mutation Testing: A Complete Guide — Artigo técnico explicando os fundamentos do mutation testing e como aplicá-lo em projetos reais
- Stryker + Jest: Tutorial Prático — Tutorial passo a passo com exemplos reais de integração entre Stryker e Jest
- GitHub Actions + Stryker — Ação oficial para integrar Stryker em pipelines de CI/CD com GitHub Actions