Duplication de code de test.
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.
##Signs and Symptoms
Vous reconnaissez cette odeur lorsque les tests semblent avoir été écrits par copier-coller plutôt que par réutilisation :
- La même construction de fixture/d’objet est reconstruite mot pour mot en tête de test après test.
- Des séquences d’assertions identiques (les mêmes 3–4 appels à
expectdans le même ordre) reviennent d’un test à l’autre. - Les nouveaux tests sont manifestement clonés d’un ancien avec une seule ligne modifiée.
- Les mêmes littéraux magiques (identifiants, URL, dates, chaînes d’erreur) apparaissent encore et encore.
- Un changement d’un seul constructeur ou d’une signature d’API casse des dizaines de tests à la fois (chirurgie au fusil de chasse).
- Vous voyez des tests quasi dupliqués qui ne diffèrent que par les valeurs d’entrée/attendues — un candidat évident pour un test paramétré/par table.
test('flight can be cancelled', () => {
const airport = new Airport('YYC', 'Calgary'); // dupliqué
const flight = new Flight('AC123', airport, new Date('2026-06-01T10:00')); // dupliqué
flight.cancel();
expect(flight.status).toBe('CANCELLED');
});
test('flight can be delayed', () => {
const airport = new Airport('YYC', 'Calgary'); // copier-coller
const flight = new Flight('AC123', airport, new Date('2026-06-01T10:00')); // copier-coller
flight.delay(30);
expect(flight.status).toBe('DELAYED');
});
##Reasons for the Problem
Pourquoi cela arrive
- Copier-coller le test précédent est la façon la plus rapide d’écrire le suivant.
- Les tests sont traités comme des « citoyens de seconde zone » — ni refactorés ni soumis au même standard DRY que le code de production.
- Il n’existe pas de fixtures partagées, de méthodes de création (Creation Methods) ou de builders de données de test, et les auteurs ne connaissent pas les tests paramétrés/pilotés par table.
Pourquoi c’est nuisible
- Maintenabilité : Meszaros relie directement cette odeur au test fragile (Fragile Test) — lorsque « les mêmes séquences de code apparaissent de nombreuses fois dans de nombreux tests », un seul changement de production impose de modifier la même chose à N endroits. Le coût de maintenance croît avec le nombre de copies, et non avec le nombre de comportements distincts.
- Lisibilité : le code répétitif (boilerplate) ensevelit l’unique ligne qui distingue réellement chaque test, si bien que les lecteurs ne voient pas rapidement ce qui est vérifié.
- Fiabilité : le copier-coller invite aux erreurs de copier-coller, et les corrections sont appliquées à une copie mais pas à ses semblables, laissant des tests incohérents et contradictoires.
- Fausse confiance : une assertion défectueuse qui a été dupliquée est désormais fausse à de nombreux endroits à la fois, et les tests clonés dérivent silencieusement jusqu’à ne plus exercer ce que leurs noms prétendent.
Mise en garde — DRY vs DAMP : les tests gagnent aussi à être des phrases descriptives et significatives (Descriptive And Meaningful Phrases). N’abstrayez pas à l’excès au point qu’un lecteur doive courir après des helpers pour comprendre un test. Extrayez la duplication véritable et révélatrice de l’intention ; gardez visible et local le détail essentiel propre à chaque test.
##Treatment
Éliminez la duplication accidentelle tout en gardant évidente l’essence de chaque test :
- Extrayez des utilitaires de test / des méthodes de création (Creation Methods) (Object Mother, Test Data Builder) pour la construction d’objets répétée, afin que chaque test ne nomme que les valeurs qui l’intéressent.
- Utilisez
beforeEach/ une mise en place implicite (Implicit Setup) pour le contexte véritablement partagé et pertinent pour chaque test du bloc — mais évitez de cacher un état dont un test dépend (cela troquerait la duplication contre un test obscur (Obscure Test)). - Extrayez des assertions personnalisées / des helpers de vérification pour les séquences d’assertions multi-étapes récurrentes, en vérifiant idéalement une seule condition logique.
- Fusionnez les tests quasi identiques en tests paramétrés/pilotés par table (
it.each/test.each), afin que les paires entrée + sortie attendue vivent dans une seule table. - Remplacez les littéraux magiques dupliqués par des constantes nommées ou des valeurs par défaut de builder.
Avant — construction dupliquée et trois tests quasi identiques :
test('rejects negative amount', () => {
expect(() => validateAmount(-1)).toThrow(RangeError);
});
test('rejects zero amount', () => {
expect(() => validateAmount(0)).toThrow(RangeError);
});
test('rejects NaN amount', () => {
expect(() => validateAmount(NaN)).toThrow(RangeError);
});
Après — une méthode de création supprime la duplication de mise en place, et une table remplace les clones :
// méthode de création partagée : les tests n'indiquent que les surcharges qui importent
const aFlight = (overrides = {}) =>
new Flight('AC123', new Airport('YYC', 'Calgary'),
new Date('2026-06-01T10:00'), overrides);
it.each([-1, 0, NaN])('rejects invalid amount %p', (amount) => {
expect(() => validateAmount(amount)).toThrow(RangeError);
});
Exécutez un détecteur de copier-coller (ci-dessous) sur vos sources de test, puis refactorez en premier les blocs les plus volumineux/les plus répétés.
##Detected by
- eslint-sonarjs no-identical-functions — Les fonctions ne devraient pas avoir des implémentations identiques
- eslint-sonarjs no-duplicate-string — Les littéraux de chaîne ne devraient pas être dupliqués
- sonar javascript:S4144 — Les fonctions ne devraient pas avoir des implémentations identiques
- pmd cpd — Copy/Paste Detector (CPD) — blocs de code dupliqués