Test Smells.
Recurring problems in test code — how to recognize each one, why it hurts, how to fix it, and the lint rules that detect it.
Un test entasse de nombreuses assertions non documentées dans une seule méthode : lorsqu'il échoue, impossible de savoir quelle assertion s'est déclenchée ni pourquoi — vous devez jouer à la roulette.
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.
Une assertion redondante compare une valeur à elle-même, ou à un littéral qui lui est égal par construction : son résultat est donc figé et elle ne peut jamais réellement échouer ni détecter une régression.
Une méthode de test qui exerce le code mais ne contient aucune assertion : elle réussit tant que rien ne lève d'exception, ce qui laisse son objectif réel et ce qu'elle vérifie totalement inconnus.
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.
Un test vérifie le comportement en comparant la représentation textuelle d'un objet (toString(), JSON.stringify(), HTML rendu) à un littéral de chaîne attendu, couplant ainsi le test à un formatage accessoire plutôt qu'aux valeurs qui comptent réellement.
Une configuration partagée construit une grande fixture « fourre-tout » couvrant les besoins de tous les tests, mais chaque test individuel n'en exploite qu'une petite partie.
Un test dont les entrées ou les résultats attendus se trouvent dans une ressource externe — un fichier, un jeu de données de base de données (seed) ou une fixture partagée — de sorte que vous ne pouvez ni comprendre ni faire confiance au test en le lisant seul.
L'optimisme sur les ressources, c'est lorsqu'un test suppose qu'une ressource externe (un fichier, un répertoire, une table de base de données, une variable d'environnement ou un point d'accès réseau) existe déjà et se trouve dans un état connu, au lieu de la provisionner et de la vérifier, ce qui fait réussir ou échouer le test de façon non déterministe.
Une classe de test initialise les champs de sa fixture dans un constructeur au lieu du hook de mise en place dédié du framework (setUp / @BeforeEach / TestInitialize), contournant le cycle de vie des tests.
Un Test obscur est un test où le lecteur ne peut pas déterminer, à partir de la seule méthode de test, quel scénario est mis en place, quel comportement est exercé et quel résultat est attendu — parce que l'intention est enfouie sous trop de détails, trop peu de contexte, ou une logique cachée ailleurs.
Un test qui recourt à des `if`/`switch`/ternaires, à des boucles ou à des `try`/`catch` pour décider quoi exécuter ou vérifier, de sorte que son comportement — et le fait même qu'il vérifie quoi que ce soit — dépend de la branche exécutée à l'exécution.
La duplication de code de test survient lorsque le même code de mise en place, d’action ou d’assertion est copié-collé dans de nombreux tests, de sorte qu’un seul changement impose des modifications à de multiples endroits et que les tests pourrissent en copies fragiles et quasi identiques.
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.
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.
Un test erratique (instable) passe et échoue par intermittence sur le même code parce que son résultat dépend du timing, de l'ordre, d'un état partagé ou d'autres facteurs non déterministes plutôt que du comportement testé.
Un Sleepy Test met l'exécution en pause avec un délai codé en dur (`Thread.sleep`, `setTimeout`, `cy.wait(2000)`, `page.waitForTimeout`) pour attendre un travail asynchrone, au lieu d'attendre la condition réelle.
Une guerre des exécutions de tests se produit lorsque les tests réussissent pour une personne mais échouent de manière aléatoire dès que plusieurs personnes ou tâches d'intégration continue (CI) exécutent la suite en même temps, parce que les tests entrent en collision sur une fixture partagée et persistante.
Un test réussit ou échoue selon les autres tests qui se sont exécutés avant lui, parce que les tests laissent fuir un état mutable partagé et s’y appuient, au lieu que chacun mette en place et démonte sa propre fixture.
Un test met en place tant d'objets simulés (mocks) et d'interactions bouchonnées que la configuration des mocks éclipse la vérification réelle : le test finit par exercer les mocks plutôt que le comportement réel.
Un test vérifie le fonctionnement interne du code — champs privés, appels de méthodes internes, structure du DOM ou classes CSS — au lieu du comportement observable dont dépend un véritable consommateur.
Un test sur-spécifié vérifie bien plus que ce qu'exige le comportement testé — figeant des chaînes de sortie exactes, des formes d'objet complètes, l'ordre d'une collection ou chaque appel de collaborateur interne — de sorte qu'il casse dès qu'un détail d'implémentation sans rapport change.
Un test vide est une méthode de test dont le corps ne contient aucune instruction exécutable : il réussit donc toujours tout en ne vérifiant rien.
Un test qui est validé dans la base de code mais ne s'exécute jamais parce qu'il a été ignoré, désactivé ou mis en commentaire, donnant l'apparence d'une couverture sans rien vérifier réellement.
Le code de production contient de la logique, des branches ou des membres qui n'existent que pour faciliter les tests, brouillant la frontière entre ce qui est livré et ce qui est seulement testé.
Le code de production contient des méthodes, des accesseurs d’état ou des joints qui n’existent que pour être utilisés par les tests, polluant la véritable API et incitant à des tests qui vérifient les détails internes plutôt que le comportement.
Du code de production dont la conception (couplage fort, dépendances cachées, état global, IO non déterministe ou interfaces uniquement asynchrones) force les tests à des contorsions maladroites, ou rend une unité tout simplement impossible à exercer en isolation.