ConstructiCat Logo
CodeBust.
Browse section ▾

Assertion dupliquée.

Une seule méthode de test vérifie plusieurs fois la même condition — en répétant une assertion identique ou en revérifiant une logique équivalente — au lieu de supprimer la vérification redondante ou de répartir les cas distincts dans leurs propres tests ciblés.

##Signs and Symptoms

Vous voyez la même assertion apparaître deux fois (même matcher, mêmes arguments) dans un test, ou plusieurs blocs d'assertions copiés‑collés qui ne diffèrent que par des valeurs littérales et qui re‑testent le même comportement. Signes révélateurs :

  • Une ligne d'assertion littéralement identique apparaît deux fois ou plus dans le corps.
  • De nombreux appels expect(...)/assertEquals(...) exerçant la même condition avec des entrées différentes, tous entassés dans une seule méthode dont le nom ne décrit qu'un seul scénario.
  • Des assertions résiduelles issues du débogage (« laisse-moi aussi vérifier X ») qui n'ont jamais été nettoyées.
  • Le nom du test (par ex. testXmlSanitizer) ne donne aucun indice sur laquelle de ses nombreuses vérifications a échoué.
test('sanitizer accepts valid input', () => {
  expect(isValid('plain text')).toBe(true);
  expect(isValid('with spaces')).toBe(true);
  expect(isValid('Fritz-box')).toBe(true);   // "le moins est valide"
  expect(isValid('Fritz-box')).toBe(true);   // <-- doublon exact, n'apporte rien
  expect(isValid('<script>')).toBe(false);
});

La ligne Fritz-box dupliquée est l'exemple canonique de l'Assertion dupliquée ; le smell plus large est qu'une seule méthode regroupe silencieusement de nombreuses vérifications de même condition sous un seul nom.

##Reasons for the Problem

Pourquoi cela se produit

  • Copier‑coller. Un bloc d'assertions est dupliqué et les littéraux (voire rien) sont modifiés.
  • Résidu de débogage. Des assertions supplémentaires ajoutées pour sonder le comportement sont laissées en place.
  • Regroupement. Les développeurs testent « une méthode » en empilant tous les cas dans un seul test au lieu de les répartir, produisant des vérifications répétées et presque identiques.

Pourquoi c'est nuisible

  • Fausse assurance. Une assertion réellement identique et dupliquée ajoute zéro couverture — elle ne peut jamais échouer lorsque sa jumelle réussit — mais elle fait paraître le test plus rigoureux qu'il ne l'est.
  • Diagnostic d'échec difficile. Lorsque plusieurs assertions de même forme partagent une seule méthode, un rapport d'échec pointe vers la méthode, pas vers l'entrée spécifique qui a échoué. Pire, un test en configuration par défaut s'arrête à la première assertion qui échoue, de sorte que les vérifications dupliquées suivantes ne s'exécutent jamais — vous en corrigez une, relancez, tombez sur la suivante (recoupe le smell Roulette d'assertions).
  • Mauvaise lisibilité. Le lecteur ne peut pas dire si la répétition est intentionnelle (cas distincts) ou une erreur (vraie duplication), et le nom du test ne documente qu'une seule des nombreuses conditions.
  • Coût de maintenance. Modifiez le comportement testé et vous devez traquer et mettre à jour chaque assertion dupliquée ; en oubliez une et la suite de tests devient incohérente.

##Treatment

  1. Supprimez les doublons exacts. Si une assertion est identique octet pour octet à une autre dans le même test, retirez-la — c'est du poids mort, pas de la couverture.
  2. Paramétrez les cas équivalents. Lorsque les « doublons » sont en réalité la même condition avec des entrées différentes, convertissez-les en un test paramétré/tabulaire (test.each dans Jest/Vitest, @ParameterizedTest dans JUnit 5). Chaque ligne est rapportée et nommée séparément, de sorte que les échecs identifient précisément l'entrée fautive.
  3. Répartissez les conditions réellement différentes dans des tests distincts, chacun portant un nom qui indique ce qu'il vérifie (accepte les noms d'hôte avec trait d'union, rejette les balises script).
  4. Nommez selon l'intention. Un nom de test doit décrire un comportement ; si vous n'y arrivez pas, c'est un signal qu'il faut le scinder.
// Avant — assertions dupliquées / regroupées dans un test opaque
test('isValid', () => {
  expect(isValid('plain text')).toBe(true);
  expect(isValid('with spaces')).toBe(true);
  expect(isValid('Fritz-box')).toBe(true);
  expect(isValid('Fritz-box')).toBe(true); // doublon
  expect(isValid('<script>')).toBe(false);
});

// Après — une seule assertion, chaque cas nommé et rapporté indépendamment
test.each([
  ['plain text', true],
  ['with spaces', true],
  ['Fritz-box',  true],   // le moins est valide
  ['<script>',   false],  // rejette le balisage
])('isValid(%j) === %s', (input, expected) => {
  expect(isValid(input)).toBe(expected);
});

La ligne dupliquée a disparu, les entrées distinctes sont explicites, et un échec nomme le cas exact.