ConstructiCat Logo
CodeBust.
Browse section ▾

Nombre magique dans les tests.

Un test code en dur des littéraux numériques inexpliqués dans ses entrées et ses assertions, masquant la signification des nombres et leur provenance.

##Signs and Symptoms

Vous repérez un test à nombre magique lorsque les arguments et les assertions d’un test sont remplis de nombres bruts dont la signification et l’origine ne ressortent pas du code. Le lecteur doit faire de la rétro-ingénierie (ou simplement faire confiance) pour comprendre pourquoi une valeur particulière est attendue.

Signes révélateurs :

  • Des littéraux numériques apparaissent directement comme arguments d’assertion : expect(result).toBe(54.13), assertEquals(86400, ttl).
  • Le même littéral est répété dans la mise en place, l’action et la valeur attendue, sans qu’aucun nom ne les relie.
  • Des nombres encodent des concepts métier qui ne sont pas explicités (3600 = une heure, 200 = HTTP OK, 0.0825 = un taux de taxe).
  • Un commentaire de code se trouve à côté du nombre pour l’expliquer — signe que le nombre lui-même aurait dû être nommé.
  • Lors de la relecture, on demande « pourquoi 42 ? » ou « d’où vient 54.13 ? » et personne ne peut répondre sans réexécuter le code.
// Odeur : c'est quoi 8.25 ? pourquoi 54.13 ? et le 50 caché dans le helper ?
test('checkout works', () => {
  const total = checkout(cartFor(50), 8.25);
  expect(total).toBe(54.13);
});

Il s’agit d’une variante spécifique, côté test, de l’odeur générale Nombre magique, et d’un contributeur classique à l’odeur Test obscur (Obscure Test) (Meszaros) : le lecteur ne peut pas comprendre le test à partir du test seul.

##Reasons for the Problem

Pourquoi cela arrive

  • Le littéral est la voie de moindre résistance : vous tapez la valeur que vous avez vue dans un débogueur, ou vous copiez la sortie réelle d’une exécution en échec dans l’assertion jusqu’à ce qu’elle passe au vert (« deviner la valeur » / collage de sortie).
  • L’auteur a déjà le contexte métier en tête, si bien que 3600 ou 8.25 lui paraît évident au moment où il l’écrit.
  • Les valeurs de fixture sont choisies arbitrairement (new User(25, ...)) juste pour satisfaire le constructeur, sans réfléchir à leur signification.

Pourquoi c’est nuisible

  • Lisibilité / intention. Un nombre comme 54.13 énonce un fait mais pas une raison. Les relecteurs et les futurs mainteneurs ne peuvent pas déterminer s’il s’agit d’une attente délibérée, d’une borne ou d’un accident. Le test cesse d’être une documentation exécutable.
  • Maintenabilité. Lorsque la règle change (le taux de taxe, le délai d’expiration, la taille de page), vous devez traquer chaque copie du littéral et savoir quel 7 signifiait « jours » plutôt que « nombre maximal de tentatives ». La duplication anonyme rend les modifications sûres coûteuses et sujettes aux erreurs.
  • Fiabilité / fausse confiance. Si la valeur attendue est fausse — ou juste par coïncidence —, rien dans le test ne le révèle. Pire, les auteurs « corrigent » souvent un test à nombre magique en recalculant la valeur attendue avec la formule de production (expect(total).toBe(subtotal * (1 + rate)))), transformant l’assertion en une tautologie qui réimplémente le code testé et ne peut jamais échouer pour la bonne raison.
  • Diagnostic. Lorsqu’un tel test casse, le message d’échec se résume à « attendu 54.13, obtenu 54.12 » sans aucun indice sur l’entrée ou la règle qui a produit ce nombre, ce qui ralentit le débogage.

##Treatment

Appliquez Remplacer un nombre magique par une constante symbolique (Meszaros) : donnez à chaque valeur significative un nom qui explique son rôle, et rendez explicite la relation entre les entrées et le résultat attendu.

Étapes concrètes :

  1. Nommez les entrées. Extrayez les littéraux utilisés comme données de test dans des constantes locales bien nommées ou des appels de constructeur de fixture (const SUBTOTAL = 50.00, const TAX_RATE_PCT = 8.25). Utilisez un Object Mother / un builder pour les fixtures d’objets, afin que seules les valeurs importantes pour le test soient visibles.
  2. Nommez et expliquez la valeur attendue. Conservez le résultat attendu comme un littéral indépendant, mais nommez-le et documentez la façon dont il a été dérivé (const EXPECTED_TOTAL = 54.13; // 50.00 + 8.25% de taxe). Ne le recalculez pas avec la formule de production — cela ne ferait que retester le code contre lui-même.
  3. Reliez les entrées à l’assertion pour qu’un lecteur puisse vérifier le calcul à l’œil nu, ou vérifiez par rapport à une valeur de référence dérivée mais indépendante.
  4. Laissez tranquilles les valeurs véritablement évidentes. 0, 1, -1, les index de tableau et les comptes évidents (items).toHaveLength(2)) n’ont généralement pas besoin de noms ; réservez les constantes aux valeurs dont la signification n’est pas explicite. Ne promouvez une constante partagée en source de vérité unique que lorsqu’il s’agit véritablement du même concept partout.
// Avant
test('checkout works', () => {
  const total = checkout(cartFor(50), 8.25);
  expect(total).toBe(54.13);
});

// Après
const SUBTOTAL = 50.00;
const TAX_RATE_PCT = 8.25;
const EXPECTED_TOTAL = 54.13; // SUBTOTAL plus 8.25% de taxe de vente

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

Les noms portent désormais l’intention ; si la règle de taxe change, la modification est locale et évidente, et la valeur attendue reste une vérification honnête et indépendante plutôt qu’une tautologie.

##Detected by