ConstructiCat Logo
CodeBust.
Browse section ▾

Número Mágico em Teste.

Um teste fixa literais numéricos sem explicação em suas entradas e asserções, escondendo o que os números significam e de onde vieram.

##Signs and Symptoms

Você identifica um Número Mágico em Teste quando os argumentos e as asserções de um teste estão repletos de números soltos cujo significado e origem não são óbvios a partir do código. O leitor precisa fazer engenharia reversa (ou simplesmente confiar) sobre por que um valor específico é esperado.

Sinais reveladores:

  • Literais numéricos aparecem diretamente como argumentos de asserção: expect(result).toBe(54.13), assertEquals(86400, ttl).
  • O mesmo literal se repete na configuração, na ação e no valor esperado, sem nenhum nome que os una.
  • Os números codificam conceitos de domínio que não são explicitados (3600 = uma hora, 200 = HTTP OK, 0.0825 = uma alíquota de imposto).
  • Um comentário no código fica ao lado do número para explicá-lo — um sinal de que o próprio número deveria ter sido nomeado.
  • Durante a revisão, as pessoas perguntam "por que 42?" ou "de onde vem 54.13?" e ninguém consegue responder sem reexecutar o código.
// Smell: o que é 8.25? por que 54.13? o que é o 50 escondido dentro do helper?
test('checkout works', () => {
  const total = checkout(cartFor(50), 8.25);
  expect(total).toBe(54.13);
});

Esta é uma variante específica, do lado dos testes, do smell geral Número Mágico, e um clássico contribuinte para o smell Obscure Test (Meszaros): o leitor não consegue entender o teste apenas a partir do teste.

##Reasons for the Problem

Por que acontece

  • O literal é o caminho de menor resistência: você digita o valor que viu em um depurador ou copia a saída real de uma execução que falhou para dentro da asserção até que ela fique verde ("adivinhe o valor" / colar a saída).
  • O autor já tem o contexto de domínio na cabeça, então 3600 ou 8.25 parece autoevidente no momento da escrita.
  • Os valores da fixture são escolhidos arbitrariamente (new User(25, ...)) apenas para satisfazer o construtor, sem pensar no significado.

Por que é prejudicial

  • Legibilidade / intenção. Um número como 54.13 declara um fato, mas não uma razão. Revisores e futuros mantenedores não conseguem dizer se é uma expectativa deliberada, um limite ou um acidente. O teste deixa de ser documentação executável.
  • Manutenibilidade. Quando a regra muda (a alíquota de imposto, o timeout, o tamanho da página), você precisa caçar cada cópia do literal e saber qual 7 significava "dias" e qual significava "máximo de tentativas". A duplicação sem nome torna as edições seguras caras e propensas a erros.
  • Confiabilidade / falsa confiança. Se o valor esperado está errado — ou certo apenas por coincidência — nada no teste revela isso. Pior, os autores muitas vezes "corrigem" um teste com número mágico recalculando o valor esperado com a fórmula de produção (expect(total).toBe(subtotal * (1 + rate)))), transformando a asserção em uma tautologia que reimplementa o código sob teste e nunca pode falhar pela razão certa.
  • Diagnóstico. Quando um teste desses quebra, a mensagem de falha é apenas "esperado 54.13, obtido 54.12", sem nenhuma pista sobre qual entrada ou regra produziu o número, o que torna a depuração mais lenta.

##Treatment

Aplique Replace Magic Number with Symbolic Constant (Meszaros): dê a cada valor significativo um nome que explique seu papel e torne explícita a relação entre as entradas e o resultado esperado.

Passos concretos:

  1. Nomeie as entradas. Extraia os literais usados como dados de teste para constantes locais bem nomeadas ou chamadas de fixture-builder (const SUBTOTAL = 50.00, const TAX_RATE_PCT = 8.25). Use um Object Mother / builder para fixtures de objeto, de modo que apenas os valores que importam para o teste fiquem visíveis.
  2. Nomeie e explique o valor esperado. Mantenha o resultado esperado como um literal independente, mas dê-lhe um nome e documente como ele foi derivado (const EXPECTED_TOTAL = 54.13; // 50.00 + 8.25% de imposto). Não o recalcule com a fórmula de produção — isso apenas testa o código contra ele mesmo.
  3. Vincule as entradas à asserção para que o leitor possa verificar a aritmética a olho nu, ou faça a asserção contra um valor de referência derivado, porém independente.
  4. Deixe em paz os valores genuinamente autoevidentes. 0, 1, -1, índices de array e contagens óbvias (items).toHaveLength(2)) normalmente não precisam de nomes; reserve as constantes para valores cujo significado não é autoexplicativo. Promova uma constante compartilhada a uma única fonte de verdade somente quando ela for realmente o mesmo conceito em todos os lugares.
// Antes
test('checkout works', () => {
  const total = checkout(cartFor(50), 8.25);
  expect(total).toBe(54.13);
});

// Depois
const SUBTOTAL = 50.00;
const TAX_RATE_PCT = 8.25;
const EXPECTED_TOTAL = 54.13; // SUBTOTAL mais 8.25% de imposto sobre vendas

test('applies sales tax to the subtotal', () => {
  const total = checkout(cartFor(SUBTOTAL), TAX_RATE_PCT);
  expect(total).toBe(EXPECTED_TOTAL);
});

Agora os nomes carregam a intenção; se a regra de imposto mudar, a edição é local e óbvia, e o valor esperado continua sendo uma verificação honesta e independente, em vez de uma tautologia.

##Detected by