ConstructiCat Logo
CodeBust.
Browse section ▾

Eager Test.

Un Eager Test vérifie plusieurs méthodes ou comportements distincts de l'unité testée dans une seule méthode de test, au lieu de se concentrer sur un seul comportement.

##Signs and Symptoms

Une seule méthode de test sollicite de nombreuses méthodes de production sans rapport et vérifie chacune d'elles, faisant parcourir à l'objet toute une séquence d'opérations au lieu de contrôler un seul résultat.

Signes révélateurs :

  • Un nom de test vague et fourre-tout (testUserService, it('works')) ou un nom assemblé avec des "et" (creates_and_renames_and_deletes).
  • Plusieurs cycles act → assert dans un même corps : vous appelez une méthode, vérifiez, en appelez une autre, vérifiez à nouveau.
  • Des commentaires utilisés comme en-têtes de section à l'intérieur du test (// on teste maintenant delete) pour séparer les choses vérifiées.
  • Les assertions touchent plusieurs méthodes/champs différents du SUT qui ne partagent pas un seul résultat logique.
// ODEUR : un seul test vérifie create, rename, delete et list
test('user service', () => {
  const svc = new UserService();

  const user = svc.create({ name: 'Ada' });   // comportement 1
  expect(user.id).toBeDefined();

  svc.rename(user.id, 'Grace');                // comportement 2
  expect(svc.get(user.id).name).toBe('Grace');

  svc.delete(user.id);                         // comportement 3
  expect(svc.get(user.id)).toBeUndefined();

  expect(svc.list()).toHaveLength(0);          // comportement 4
});

Notez la distinction : un Eager Test consiste à tester plusieurs comportements, et non simplement à avoir plusieurs expect. Plusieurs assertions sur un seul résultat logique (p. ex. vérifier plusieurs champs du même objet renvoyé) sont acceptables et ne constituent pas cette odeur.

##Reasons for the Problem

Pourquoi cela se produit

  • Réutilisation de la préparation / paresse. Préparer le fixture est fastidieux ou coûteux ; il est donc tentant de continuer à solliciter l'objet déjà construit et de vérifier "tant qu'on y est."
  • Raisonnement par flux. L'auteur teste un parcours utilisateur ("créer, puis modifier, puis supprimer") comme un seul script au lieu d'isoler chaque comportement unitaire.
  • Dérive du TDD. Un test parti d'une intention ciblée accumule des assertions supplémentaires à mesure que de nouvelles fonctionnalités lui sont greffées au lieu d'avoir leur propre test.

Pourquoi c'est nuisible

  • Fausse confiance (le point majeur). La plupart des bibliothèques d'assertion interrompent le test à la première assertion qui échoue. Si create casse, les vérifications de rename, delete et list ne s'exécutent jamais — un passage du vert au rouge masque donc le nombre réel de comportements cassés, et un test qui passait n'exerçait en réalité jamais les étapes ultérieures dès qu'une étape antérieure régressait.
  • Diagnostics médiocres. Un échec vous dit "le test du service utilisateur a échoué", mais pas quel comportement. Vous devez lire toute la méthode pour localiser l'étape défaillante.
  • Intention obscurcie / lisibilité. Le test ne documente plus un fait unique sur le système ; le lecteur doit le découper mentalement en les comportements qu'il regroupe.
  • Fragilité et couplage. Les assertions ultérieures dépendent des mutations antérieures, si bien qu'une modification sans rapport en haut se répercute en cascade et rend le test fragile et difficile à refactoriser.
  • Maintenabilité. Il est plus difficile de supprimer, déplacer ou renommer la couverture d'un comportement lorsqu'elle est enchevêtrée avec trois autres dans une même méthode.

##Treatment

Découpez l'eager test en plusieurs tests ciblés — un comportement par test — et remontez l'étape d'arrangement partagée dans la préparation afin que le découpage ne duplique pas le code répétitif.

Étapes :

  1. Listez les comportements que le test regroupe (ici : create, rename, delete, list-après-delete).
  2. Extrayez un test par comportement, chacun portant un nom descriptif qui révèle l'intention.
  3. Déplacez la préparation partagée dans un beforeEach ou une fabrique/Creation Method afin que chaque test dispose encore d'un SUT propre sans code d'arrangement copié-collé.
  4. Conservez un seul Act par test et ne vérifiez que le résultat de ce comportement (plusieurs expect sur le même résultat sont acceptables).
  5. Si vous avez réellement besoin de valider un flux de bout en bout, conservez-le comme un seul test de scénario/d'intégration explicitement nommé — mais couvrez tout de même chaque comportement unitaire dans son propre test plutôt que de vous reposer sur le flux pour la couverture.
  6. Protégez-vous des régressions en activant une règle de nombre maximal d'assertions (voir les détecteurs) comme indicateur peu coûteux.
// APRÈS : tests ciblés, préparation partagée
let svc;
beforeEach(() => { svc = new UserService(); });

test('create() assigns an id', () => {
  expect(svc.create({ name: 'Ada' }).id).toBeDefined();
});

test('rename() updates the stored name', () => {
  const { id } = svc.create({ name: 'Ada' });
  svc.rename(id, 'Grace');
  expect(svc.get(id).name).toBe('Grace');
});

test('delete() removes the user', () => {
  const { id } = svc.create({ name: 'Ada' });
  svc.delete(id);
  expect(svc.get(id)).toBeUndefined();
});

Désormais, n'importe quel comportement peut échouer indépendamment, le nom du test défaillant pointe précisément la rupture, et chaque test se lit comme un fait documenté sur le SUT.

##Detected by