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 vem54.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
3600ou8.25parece 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.13declara 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
7significava "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:
- 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. - 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. - 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.
- 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
- eslint no-magic-numbers — Números mágicos devem ser declarados como constantes nomeadas
- typescript-eslint @typescript-eslint/no-magic-numbers — Proibir números mágicos (TypeScript)
- sonar javascript:S109 — Números mágicos não devem ser usados
- checkstyle MagicNumber — MagicNumber
- tsDetect Magic Number Test — Magic Number Test (detector específico para testes)