<h2>O Que é Mutation Testing e Por Que Importa</h2>
<p>Mutation testing é uma técnica de avaliação de qualidade que vai além da simples cobertura de código. Enquanto testes convencionais medem apenas se você executou uma linha, mutation testing verifica se seus testes <strong>realmente conseguem detectar bugs</strong>. A ideia é simples e poderosa: introduzir pequenas mudanças (mutações) no código-fonte e verificar se seus testes falham. Se um teste não captura uma mutação óbvia, há uma brecha na qualidade real da sua suíte.</p>
<p>Cobertura de 100% de linhas não significa cobertura de 100% de bugs. Um teste que apenas verifica se uma função é chamada, sem validar o resultado correto, criará uma falsa sensação de segurança. Mutation testing expõe essas vulnerabilidades. No JavaScript, a ferramenta mais madura para isso é o <strong>Stryker</strong>, que automatiza todo esse processo com inteligência e performance.</p>
<h2>Como Funciona o Stryker em Prática</h2>
<h3>Instalação e Configuração Inicial</h3>
<p>Instalar o Stryker é direto. Você precisa do framework de testes (Jest, Mocha, etc.) já funcionando:</p>
<pre><code class="language-bash">npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
npx stryker init</code></pre>
<p>O comando <code>init</code> gera um <code>stryker.conf.js</code> com padrões sensatos. Aqui está uma configuração mínima para um projeto com Jest:</p>
<pre><code class="language-javascript">// stryker.conf.js
export default {
testRunner: 'jest',
testPathPattern: './src/*/.test.js',
mutate: ['src/*/.js', '!src/*/.test.js', '!src/*/.spec.js'],
reporters: ['html', 'clear-text', 'progress'],
timeoutMS: 5000,
concurrency: 4
};</code></pre>
<p>O <code>mutate</code> define quais arquivos sofrem mutações. O <code>timeoutMS</code> evita testes presos em loops infinitos após uma mutação causar impasse. Com <code>concurrency: 4</code>, o Stryker executa 4 testes em paralelo, acelerando drasticamente a análise.</p>
<h3>Executando e Interpretando Resultados</h3>
<p>Execute com <code>npx stryker run</code>. O Stryker testará centenas de variações do seu código. Cada mutação tem um status:</p>
<ul>
<li><strong>Killed</strong>: seu teste detectou a mutação (bom!)</li>
<li><strong>Survived</strong>: a mutação não foi detectada (problema!)</li>
<li><strong>Timeout</strong>: a mutação causou loop infinito</li>
<li><strong>Ignored</strong>: você marcou para ignorar</li>
</ul>
<p>A métrica principal é a <strong>mutation score</strong>: percentual de mutações mortas. Uma suíte com score 80%+ é considerada robusta. O relatório HTML mostra exatamente onde estão as lacunas.</p>
<h2>Exemplo Real: Testando uma Função de Validação</h2>
<h3>Código Original com Teste Inadequado</h3>
<pre><code class="language-javascript">// src/validator.js
export function validateEmail(email) {
if (email.includes('@')) {
return true;
}
return false;
}
// src/validator.test.js (RUIM)
test('validateEmail returns true for email with @', () => {
expect(validateEmail('user@example.com')).toBe(true);
});</code></pre>
<p>Este teste tem cobertura 100% de linhas, mas é fraco. O Stryker criará mutações como <code>email.includes('@')</code> → <code>email.includes('#')</code>, e seu teste não falhará. Mutation score: baixo.</p>
<h3>Teste Adequado Que Mata Mutações</h3>
<pre><code class="language-javascript">// src/validator.test.js (BOM)
describe('validateEmail', () => {
test('returns true for valid email', () => {
expect(validateEmail('user@example.com')).toBe(true);
});
test('returns false for email without @', () => {
expect(validateEmail('userexample.com')).toBe(false);
});
test('returns false for empty string', () => {
expect(validateEmail('')).toBe(false);
});
test('returns false for @ only', () => {
expect(validateEmail('@')).toBe(false);
});
});</code></pre>
<p>Agora temos limites claros. Mutações como trocar <code>includes('@')</code> por <code>includes('#')</code>, ou <code>true</code> por <code>false</code>, serão capturadas. O mutation score sobe significativamente.</p>
<blockquote><p><strong>Lição crítica</strong>: Testes efetivos testam <strong>casos positivos E negativos</strong>, com <strong>asserções específicas</strong> sobre o resultado esperado.</p></blockquote>
<h2>Estratégias para Melhorar Mutation Score</h2>
<h3>Cobertura de Condições Booleanas</h3>
<p>Mutation testing excelente exige testar cada caminho através de condições. Para um operador AND, teste quando ambas as partes são verdadeiras E quando cada uma é falsa:</p>
<pre><code class="language-javascript">// src/permission.js
export function canEdit(user) {
return user.isAdmin || user.isOwner;
}
// Testes que cobrem todas as combinações booleanas
test('returns true if user is admin', () => {
expect(canEdit({ isAdmin: true, isOwner: false })).toBe(true);
});
test('returns true if user is owner', () => {
expect(canEdit({ isAdmin: false, isOwner: true })).toBe(true);
});
test('returns false if neither admin nor owner', () => {
expect(canEdit({ isAdmin: false, isOwner: false })).toBe(false);
});
test('returns true if both admin and owner', () => {
expect(canEdit({ isAdmin: true, isOwner: true })).toBe(true);
});</code></pre>
<p>O Stryker criará mutações trocando <code>||</code> por <code>&&</code>. Sem esses testes complementares, as mutações sobreviveriam.</p>
<h3>Validação de Valores Retornados</h3>
<p>Não confie apenas em tipo. Valide valores reais:</p>
<pre><code class="language-javascript">// src/calculator.js
export function add(a, b) {
return a + b;
}
// Fraco
test('add returns a number', () => {
expect(typeof add(2, 3)).toBe('number');
});
// Forte
test('add returns correct sum', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
expect(add(0, 0)).toBe(0);
});</code></pre>
<p>A primeira versão não mata a mutação <code>a + b</code> → <code>a - b</code>. A segunda mata.</p>
<h3>Observando o Relatório HTML</h3>
<p>Após <code>npx stryker run</code>, abra o arquivo gerado em <code>reports/mutation/index.html</code>. Ele destaca em vermelho cada linha com mutações sobreviventes. Isso guia sua escrita de testes de forma cirúrgica. Você vê exatamente onde adicionar cobertura.</p>
<h2>Conclusão</h2>
<p>Mutation testing com Stryker transformou minha forma de avaliar qualidade real de código. Três aprendizados fundamentais: <strong>(1)</strong> Cobertura de linhas é métrica enganosa — mutation score revela vulnerabilidades reais; <strong>(2)</strong> Testes robustos precisam de casos positivos, negativos e limites, com asserções específicas sobre valores, não apenas tipos; <strong>(3)</strong> O relatório HTML do Stryker é uma ferramenta de navegação que economiza horas de análise manual.</p>
<p>Implemente isso em seu próximo projeto. Comece com <code>stryker init</code>, veja o mutation score inicial e persiga melhorias incrementais. A experiência é reveladora.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://stryker-mutator.io/docs/" target="_blank" rel="noopener noreferrer">Stryker - Official Documentation</a></li>
<li><a href="https://stryker-mutator.io/blog/2021-09-01/why-mutation-testing" target="_blank" rel="noopener noreferrer">Mutation Testing: A Literature Review</a></li>
<li><a href="https://jestjs.io/docs/getting-started" target="_blank" rel="noopener noreferrer">Jest Documentation - Testing Framework</a></li>
<li><a href="https://martinfowler.com/bliki/TestCoverage.html" target="_blank" rel="noopener noreferrer">Martin Fowler - Test Coverage</a></li>
<li><a href="https://learning.oreilly.com/search/?query=javascript+testing" target="_blank" rel="noopener noreferrer">Effective JavaScript Testing - O'Reilly</a></li>
</ul>