Duplicação de Código de Teste.
Duplicação de Código de Teste é quando o mesmo código de configuração, ação ou asserção é copiado e colado em muitos testes, de modo que uma única mudança força edições em muitos lugares e os testes apodrecem em cópias frágeis e quase idênticas.
##Signs and Symptoms
Você reconhece esse smell quando os testes parecem ter sido escritos com copiar e colar em vez de reúso:
- A mesma construção de fixture/objeto é reconstruída literalmente no topo de teste após teste.
- Sequências de asserção idênticas (as mesmas 3–4 chamadas de
expectna mesma ordem) se repetem entre os testes. - Novos testes são obviamente clonados de um antigo com uma única linha ajustada.
- Os mesmos literais mágicos (IDs, URLs, datas, strings de erro) aparecem repetidamente.
- Uma mudança em um construtor ou na assinatura de uma API quebra dezenas de testes de uma só vez (cirurgia com espingarda).
- Você vê testes quase duplicados que diferem apenas nos valores de entrada/esperados — um claro candidato a um teste parametrizado/de tabela.
test('flight can be cancelled', () => {
const airport = new Airport('YYC', 'Calgary'); // duplicado
const flight = new Flight('AC123', airport, new Date('2026-06-01T10:00')); // duplicado
flight.cancel();
expect(flight.status).toBe('CANCELLED');
});
test('flight can be delayed', () => {
const airport = new Airport('YYC', 'Calgary'); // copiar e colar
const flight = new Flight('AC123', airport, new Date('2026-06-01T10:00')); // copiar e colar
flight.delay(30);
expect(flight.status).toBe('DELAYED');
});
##Reasons for the Problem
Por que acontece
- Copiar e colar o teste anterior é a forma mais rápida de escrever o próximo.
- Os testes são tratados como "cidadãos de segunda classe" — não são refatorados nem mantidos no mesmo padrão DRY que o código de produção.
- Não existem fixtures compartilhadas, Creation Methods ou builders de dados de teste, e os autores não estão familiarizados com testes parametrizados/orientados a tabela.
Por que é prejudicial
- Manutenibilidade: Meszaros liga esse smell diretamente ao Fragile Test — quando "as mesmas sequências de código aparecem muitas vezes em muitos testes", uma única mudança em produção significa editar a mesma coisa em N lugares. O custo de manutenção cresce com o número de cópias, não com o número de comportamentos distintos.
- Legibilidade: o boilerplate repetido enterra a única linha que de fato torna cada teste diferente, de modo que os leitores não conseguem ver rapidamente o que está sendo verificado.
- Confiabilidade: copiar e colar convida a erros de cópia, e as correções acabam aplicadas a uma cópia, mas não às suas irmãs, deixando testes inconsistentes e contraditórios.
- Falsa confiança: uma asserção falha que foi duplicada agora está errada em muitos lugares ao mesmo tempo, e os testes clonados se desviam silenciosamente até não exercitarem mais o que seus nomes afirmam.
Ressalva — DRY vs DAMP: os testes também valorizam ser Descriptive And Meaningful Phrases. Não abstraia demais a ponto de o leitor ter que perseguir helpers para entender um teste. Extraia a duplicação genuína, reveladora de intenção; mantenha o detalhe essencial de cada teste visível e local.
##Treatment
Elimine a duplicação incidental mantendo óbvia a essência de cada teste:
- Extraia Test Utility / Creation Methods (Object Mother, Test Data Builder) para a construção repetida de objetos, de modo que cada teste nomeie apenas os valores com os quais se importa.
- Use
beforeEach/ Implicit Setup para o contexto que é genuinamente compartilhado e relevante para todos os testes do bloco — mas evite esconder o estado do qual um teste depende (isso troca a duplicação por um Obscure Test). - Extraia Custom Assertions / helpers de verificação para sequências de asserção de múltiplos passos que se repetem, idealmente verificando uma única condição lógica.
- Una testes quase idênticos em testes Parametrizados / orientados a tabela (
it.each/test.each), para que os pares de entrada + saída esperada fiquem em uma única tabela. - Substitua os literais mágicos duplicados por constantes nomeadas ou por valores padrão do builder.
Antes — construção duplicada e três testes quase idênticos:
test('rejects negative amount', () => {
expect(() => validateAmount(-1)).toThrow(RangeError);
});
test('rejects zero amount', () => {
expect(() => validateAmount(0)).toThrow(RangeError);
});
test('rejects NaN amount', () => {
expect(() => validateAmount(NaN)).toThrow(RangeError);
});
Depois — um Creation Method remove a duplicação da configuração, e uma tabela substitui os clones:
// Creation Method compartilhado: os testes declaram apenas os overrides que importam
const aFlight = (overrides = {}) =>
new Flight('AC123', new Airport('YYC', 'Calgary'),
new Date('2026-06-01T10:00'), overrides);
it.each([-1, 0, NaN])('rejects invalid amount %p', (amount) => {
expect(() => validateAmount(amount)).toThrow(RangeError);
});
Execute um detector de copiar e colar (abaixo) sobre os fontes dos seus testes e, então, refatore primeiro os blocos maiores/mais repetidos.
##Detected by
- eslint-sonarjs no-identical-functions — Funções não devem ter implementações idênticas
- eslint-sonarjs no-duplicate-string — Literais de string não devem ser duplicados
- sonar javascript:S4144 — Funções não devem ter implementações idênticas
- pmd cpd — Copy/Paste Detector (CPD) — blocos de código duplicados