ConstructiCat Logo
CodeBust.
Browse section ▾

Test verbeux.

Un test qui utilise bien plus de code qu'il n'en faut pour énoncer son scénario, enfouissant l'unique relation de cause à effet qu'il vérifie sous du code de configuration standard, des données non pertinentes et des assertions champ par champ.

##Signs and Symptoms

Vous n'arrivez pas à dire ce qu'un test prouve d'un coup d'œil, alors que rien de délicat ne s'y passe — l'intention est noyée dans le volume. Indices courants :

  • La méthode de test est longue (le catalogue Test Smells utilise un seuil approximatif de >30 lignes), ou déborde au-delà d'un écran.
  • Un gros bloc arrange construit des objets champ par champ, dont une grande partie est sans rapport avec le comportement testé.
  • Beaucoup de littéraux codés en dur sans lien évident entre les entrées et les sorties attendues.
  • Le résultat est vérifié par une pile d'assertions à champ unique au lieu d'une seule comparaison porteuse de sens.
  • En lisant les assertions, vous n'arrivez pas à associer facilement quelle entrée a produit quelle valeur attendue.
test('places an order for a returning customer', () => {
  const address = new Address();
  address.street = '123 Main St';
  address.city = 'Springfield';
  address.zip = '00000';                          // sans rapport avec ce test
  address.country = 'US';

  const customer = new Customer();
  customer.id = 42;
  customer.firstName = 'Ada';
  customer.lastName = 'Lovelace';
  customer.email = 'ada@example.com';
  customer.address = address;
  customer.createdAt = new Date('2020-01-01');     // du bruit

  const product = new Product();
  product.sku = 'SKU-1';
  product.name = 'Widget';
  product.price = 9.99;

  const cart = new Cart();
  cart.add(product, 2);

  const order = orderService.placeOrder(customer, cart);

  expect(order.status).toBe('CONFIRMED');          // les seules lignes qui comptent
  expect(order.lineItems.length).toBe(1);
  expect(order.lineItems[0].sku).toBe('SKU-1');
  expect(order.lineItems[0].quantity).toBe(2);
  expect(order.total).toBe(19.98);
  expect(order.customerId).toBe(42);
});

Environ 25 lignes de construction protègent une assertion de deux lignes portant sur les totaux et le statut.

##Reasons for the Problem

Pourquoi cela se produit

  • Construction en ligne avec des champs obligatoires. Construire de vrais objets via des constructeurs/setters vous oblige à fournir des données dont le test n'a que faire, simplement pour que le code compile et s'exécute. Meszaros note que le niveau de détail nécessaire pour rendre un test exécutable peut le rendre « si verbeux qu'il en devient difficile à comprendre ».
  • Croissance par copier-coller. Un test commence petit, puis chaque nouveau scénario est créé en copiant le précédent et en ajustant une valeur, si bien que le bruit s'accumule.
  • Absence d'utilitaires de construction partagés. Sans méthodes de création, builders ou fixtures, chaque test réénonce l'intégralité du graphe d'objets.
  • Données codées en dur et vérification champ par champ. Écrire des littéraux partout et faire des assertions sur chaque propriété donne une impression de « rigueur », mais multiplie le nombre de lignes.

Pourquoi c'est nuisible

  • Lisibilité. Le Test verbeux est une variante du Test obscur — le catalogue cite même « Test obscur » comme son alias. Le lecteur ne peut pas voir la relation de cause à effet entre la fixture et le résultat, ce qui est pourtant tout l'intérêt d'un test fondé sur l'exemple.
  • Maintenabilité. Un test de plus de 30 lignes tend à porter « plusieurs responsabilités », si bien qu'une petite modification de production se répercute sur de nombreuses lignes dans de nombreux tests. La configuration en ligne en masse engendre aussi de la duplication.
  • Fausse confiance. Les tests longs et bruyants cachent les bugs à la vue de tous : un littéral incorrect ou une assertion mal placée est facile à manquer, et la verbosité incite les relecteurs à survoler. Le volume paraît rigoureux sans rien prouver de plus.
  • Fiabilité. Plus un test fige d'état non pertinent, plus il risque de casser pour des raisons étrangères à son intention (sur-spécification), produisant des échecs fragiles et à faible signal.

##Treatment

Repoussez le bruit hors du corps du test pour que seules restent visibles les variables qui pilotent le comportement. Manœuvres concrètes (toutes issues de xUnit Test Patterns) :

  1. Extrayez des méthodes de création / utilisez un builder. Remplacez la construction d'objets en ligne par un utilitaire au nom évocateur qui fournit des valeurs par défaut raisonnables ; ne passez que les valeurs qui intéressent réellement le test (méthode de création paramétrée). Cela élimine à la fois le code standard et les informations non pertinentes.
  2. Mettez par défaut les données non pertinentes dans l'utilitaire, signalant ainsi que « ces valeurs n'affectent pas le résultat ».
  3. Remplacez les vérifications champ par champ par un objet attendu (toEqual/toMatchObject) ou par une assertion personnalisée / méthode de vérification qui nomme le concept vérifié.
  4. Vérifiez un seul comportement par test. Si un test est long parce qu'il exerce plusieurs résultats (un test avide), scindez-le.
  5. Paramétrez les tests verbeux quasi identiques avec test.each / it.each au lieu de copier-coller.
// avant — voir Signes et symptômes (≈30 lignes de configuration + 6 assertions)

// après
test('confirms the order and charges line-item total', () => {
  const customer = aCustomer();                          // méthode de création, valeurs par défaut
  const cart = aCartWith(aProduct({ price: 9.99 }), 2);  // uniquement l'entrée pertinente

  const order = orderService.placeOrder(customer, cart);

  expect(order).toMatchObject({                          // objet attendu, pas champ par champ
    status: 'CONFIRMED',
    total: 19.98,
  });
});

Le scénario se lit désormais en quelques secondes : un client achète deux unités d'un produit à 9,99 $ → la commande est confirmée avec un total de 19,98 $. Les utilitaires (aCustomer, aProduct, aCartWith) sont des outils partagés et testés, de sorte que le bruit vit à un seul endroit au lieu d'être dans chaque test.

Garde-fou : plafonnez la longueur des fonctions de test et le nombre d'assertions dans votre linter (voir détecteurs) afin que les tests ne puissent pas silencieusement redevenir des Tests verbeux.

##Detected by

  • eslint max-lines-per-functionImposer un nombre maximal de lignes par fonction — signale les corps de test qui dépassent la limite configurée, la mesure essentielle d'un Test verbeux.
  • eslint max-statementsImposer un nombre maximal d'instructions dans un bloc de fonction — limite ce qu'un seul test (un callback fléché/fonction) peut faire.
  • sonar S138Les fonctions ne devraient pas avoir trop de lignes de code — s'applique aux fonctions de test ; signale les méthodes de test surdimensionnées, aux responsabilités multiples (RSPEC-138).
  • eslint-jest jest/max-expectsImposer un nombre maximal d'appels expect() par test (5 par défaut) — détecte l'accumulation d'assertions des tests verbeux/avides.
  • eslint-vitest vitest/max-expectsImposer un nombre maximal d'appels expect() par test (5 par défaut) — équivalent Vitest de jest/max-expects.