ConstructiCat Logo
CodeBust.
Browse section ▾

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 expect na 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:

  1. 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.
  2. 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).
  3. 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.
  4. 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.
  5. 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-functionsFunções não devem ter implementações idênticas
  • eslint-sonarjs no-duplicate-stringLiterais de string não devem ser duplicados
  • sonar javascript:S4144Funções não devem ter implementações idênticas
  • pmd cpdCopy/Paste Detector (CPD) — blocos de código duplicados