Nous avons supprimé 37 tests et notre couverture s'est améliorée

Erik Treviño avatar
Erik Treviño
Cover for Nous avons supprimé 37 tests et notre couverture s'est améliorée

Nous avons supprimé 37 tests le mois dernier.

Notre couverture s’est améliorée.

Pas « restée la même » — améliorée. La suite s’exécutait plus vite, flakait moins, et les tests WORKFLOW restants couvraient déjà chaque chemin que les tests supprimés vérifiaient. Nous n’avons perdu aucune assertion significative. Nous avons perdu du poids mort.

Voici ce que la plupart des équipes n’admettront pas : une suite de tests qui ne fait que grossir, c’est comme un codebase qui ne fait que grossir. Au bout du compte, le coût de maintenance dépasse la valeur ajoutée. Chaque équipe a le réflexe « ajoutons plus de tests ». Presque aucune n’a la discipline « auditons ce que nous avons déjà ».

Le système de classification à quatre étiquettes

J’ai construit un classificateur de tests. Pas un outil d’IA — une taxonomie. Une méthode systématique pour examiner chaque test d’une suite et répondre à une seule question : Ce test mérite-t-il sa place ?

Chaque test reçoit l’une des quatre étiquettes :

  • WORKFLOW — Teste un vrai parcours utilisateur de bout en bout. Connexion → navigation → action → vérification du résultat. C’est la colonne vertébrale. On garde.
  • FLUFF — Vérifie quelque chose déjà couvert par un autre test à une meilleure couche. Le test de validation du formulaire de connexion qui existe aussi en test unitaire. On supprime.
  • MERGE — Deux tests qui partagent 80 % de leur setup et de leurs assertions mais testent des branches légèrement différentes. On les combine en un seul test paramétré.
  • KEEP_DETAILED — Un cas limite qui compte vraiment (gestion des fuseaux horaires, frontières de permissions, chemins de migration de données). On garde, mais on évalue s’il a sa place au niveau E2E ou s’il pourrait descendre en intégration/unitaire.

La taxonomie est volontairement simple. Quatre étiquettes. Pas d’échelle d’ambiguïté. Pas de catégorie « peut-être ». Chaque test reçoit une classification définitive.

À quoi ressemble chaque étiquette en pratique

WORKFLOW : un vrai parcours utilisateur

// WORKFLOW — This test earns its place. It validates a complete user journey
// that can only be tested through a browser.
test('user creates a report and shares it with a teammate', async ({ page }) => {
  await page.goto('/reports');
  await page.getByRole('button', { name: 'New Report' }).click();

  // Fill out the report form
  await page.getByLabel('Report Name').fill('Q1 Sales Summary');
  await page.getByRole('combobox', { name: 'Template' }).selectOption('quarterly');
  await page.getByRole('button', { name: 'Generate' }).click();

  // Wait for the report to generate (involves backend processing)
  await page.waitForResponse(resp =>
    resp.url().includes('/api/reports') && resp.status() === 201
  );
  await expect(page.getByText('Q1 Sales Summary')).toBeVisible();

  // Share with a teammate
  await page.getByRole('button', { name: 'Share' }).click();
  await page.getByLabel('Email').fill('teammate@company.com');
  await page.getByRole('button', { name: 'Send' }).click();
  await expect(page.getByText('Report shared successfully')).toBeVisible();
});

Ce test exerce la navigation, la soumission de formulaire, le traitement backend, le rendu et une action secondaire. Il ne peut s’exécuter que dans un navigateur. Il valide un vrai scénario utilisateur. WORKFLOW.

FLUFF : déjà couvert ailleurs

// FLUFF — This test validates form validation rules through the browser.
// The exact same validation logic is covered by unit tests on the
// validation schema. The E2E test adds 45 seconds of browser execution
// to verify something the unit test covers in 12 milliseconds.
test('report name field shows error when empty', async ({ page }) => {
  await page.goto('/reports');
  await page.getByRole('button', { name: 'New Report' }).click();
  await page.getByLabel('Report Name').fill('');
  await page.getByLabel('Report Name').blur();
  await expect(page.getByText('Report name is required')).toBeVisible();
});

Ce test n’est pas faux. L’assertion est valide. Mais le test WORKFLOW ci-dessus navigue déjà vers ce formulaire. Si la validation était cassée, le test workflow échouerait à l’étape de soumission. Le test FLUFF ajoute 45 secondes de temps d’exécution pour re-vérifier une règle de validation qu’un test unitaire couvre en millisecondes.

MERGE : deux tests qui devraient n’en faire qu’un

// MERGE candidate A
test('admin can delete a report', async ({ page }) => {
  await loginAs(page, 'admin');
  await page.goto('/reports');
  await page.getByRole('row', { name: 'Q1 Sales' }).getByRole('button', { name: 'Delete' }).click();
  await page.getByRole('button', { name: 'Confirm' }).click();
  await expect(page.getByRole('row', { name: 'Q1 Sales' })).not.toBeVisible();
});

// MERGE candidate B — 90% identical setup, just checks a different element
test('admin sees delete confirmation dialog', async ({ page }) => {
  await loginAs(page, 'admin');
  await page.goto('/reports');
  await page.getByRole('row', { name: 'Q1 Sales' }).getByRole('button', { name: 'Delete' }).click();
  await expect(page.getByRole('dialog', { name: 'Confirm deletion' })).toBeVisible();
});

Ces deux tests partagent le même setup, la même navigation, le même rôle et la même action initiale. Le test B est un sous-ensemble du test A. On les fusionne — le premier test clique déjà sur le dialogue de confirmation et vérifie la suppression.

Les résultats de l’audit

J’ai appliqué cette taxonomie à une suite E2E de 180 tests. Pas en un seul passage — trois tours de classification avec revue manuelle à chaque étape.

ÉtiquetteNombrePourcentageAction
WORKFLOW13474 %Garder — c’est le cœur de la suite
FLUFF3721 %Supprimer — couvert par d’autres couches ou par des tests workflow
MERGE21 %Combiner en 1 test chacun
KEEP_DETAILED74 %Garder en E2E, ou migrer vers la couche intégration

21 % de la suite était du FLUFF. Un test sur cinq consommait du temps de CI, contribuait au taux de flake et nécessitait de la maintenance — tout en apportant zéro couverture unique.

La boucle d’audit en trois tours

Une classification en un seul passage n’est pas fiable. J’ai raté des classifications au premier tour, les ai rattrapées au deuxième, et affiné les règles de la taxonomie au troisième.

Tour 1 : classification initiale. Appliquer les quatre étiquettes à chaque fichier de test. Utiliser l’analyse statique pour repérer les tests qui ne contiennent pas d’actions de navigation (probablement FLUFF), les tests qui partagent plus de 80 % de leurs locators avec un autre test (probablement MERGE), et les tests qui ne vérifient qu’un seul élément (probablement FLUFF ou KEEP_DETAILED).

# Quick static analysis: find tests that never call page.goto() or navigate
# These are often fluff tests that rely on another test's setup
grep -rL "page\.goto\|page\.click.*nav\|page\.getByRole.*link" tests/e2e/*.spec.ts

Tour 2 : revue manuelle de chaque étiquette FLUFF. Pour chaque test étiqueté FLUFF, répondre à deux questions : (1) Existe-t-il un test WORKFLOW qui couvre déjà ce chemin ? (2) Un test unitaire ou d’intégration vérifie-t-il déjà ce même comportement ? Si les deux réponses sont oui, l’étiquette FLUFF tient. Si l’une est non, reclassifier.

Tour 3 : affiner et re-auditer. Mettre à jour les règles de classification en fonction de ce que vous avez appris au tour 2. Relancer l’analyse statique. Vérifier les faux négatifs — les tests étiquetés WORKFLOW qui devraient en fait être MERGE ou KEEP_DETAILED.

Trois tours. Pas un seul. Le coût d’un faux positif (supprimer un test qui apporte une couverture unique) est bien plus élevé que le coût d’un tour d’audit supplémentaire.

Avant et après

Performance de la suite

MétriqueAvant (180 tests)Après (141 tests)Évolution
Durée totale de la suite24 min 12 s17 min 45 s-27 %
Taux de tests flaky3,2 % des exécutions1,1 % des exécutions-66 %
Couverture workflow unique134 chemins134 cheminsAucun changement
Heures de maintenance hebdomadaires~4 h~2,5 h-38 %

La baisse du taux de flake est la métrique la plus révélatrice. Les 37 tests supprimés avaient le taux de flake le plus élevé de la suite — parce qu’ils testaient des détails d’implémentation à travers la couche la plus fragile possible. Une règle de validation de formulaire testée via un navigateur est sensible au timing de rendu, aux mutation observers du DOM et à la gestion du focus. La même règle testée en test unitaire a zéro surface de flake.

Ce que nous n’avons pas perdu

Voici le chiffre qui compte : 134 chemins workflow avant, 134 chemins workflow après. Chaque vrai parcours utilisateur testé avant l’audit est toujours testé après. Nous n’avons pas perdu de couverture — nous avons perdu de la redondance.

La distinction est importante. Une couverture qui existe sur deux couches (unitaire + E2E) n’est pas « plus de couverture » qu’une couverture qui existe sur une seule couche correcte. C’est un coût dupliqué pour la même protection.

Pourquoi les tests FLUFF s’accumulent

Les tests FLUFF ne sont pas créés par des ingénieurs négligents. Ils s’accumulent pour des raisons structurelles :

  1. Couches de test manquantes. Quand votre codebase n’a pas de couche de test de composants ni de couche de test d’intégration API, le E2E devient le fourre-tout. Chaque vérification de comportement, chaque cas limite, chaque « laisse-moi juste vérifier ce truc » finit en E2E parce qu’il n’y a nulle part ailleurs où le mettre.

  2. L’hypothèse « plus de tests = mieux ». Les métriques de vélocité d’équipe qui comptent les tests ajoutés par sprint encouragent la quantité au détriment de l’architecture. Personne n’est récompensé pour avoir supprimé un test, même quand la suppression améliore la suite.

  3. Création de tests par copier-coller. Un développeur copie un test E2E existant comme modèle, change l’assertion, et maintenant il y a deux tests qui partagent 90 % de leur setup. Aucun n’est faux individuellement. Ensemble, c’est un candidat MERGE.

  4. La peur de supprimer des tests. « Et si on en a besoin plus tard ? » C’est l’équivalent test du syndrome de l’accumulateur. Si la couverture existe sur une autre couche, supprimer le test E2E ne supprime pas le filet de sécurité — cela supprime le doublon.

Le protocole d’audit : ce que vous pouvez faire lundi matin

  1. Exportez votre liste de fichiers de tests. Chaque fichier de test E2E de votre suite, un par ligne.

  2. Étiquetez chaque test avec son assertion principale. Que vérifie réellement ce test ? « L’utilisateur peut se connecter. » « Un message d’erreur apparaît en cas de saisie invalide. » « Les données se chargent après la navigation. »

  3. Croisez avec vos tests unitaires et d’intégration. Pour chaque assertion E2E, un test plus rapide couvre-t-il déjà le même comportement ?

  4. Appliquez les quatre étiquettes. WORKFLOW, FLUFF, MERGE, KEEP_DETAILED.

  5. Revoyez les étiquettes FLUFF avec l’équipe. Ne supprimez pas unilatéralement. Montrez à l’équipe quels tests vous proposez de supprimer et quels tests workflow couvrent déjà ces chemins.

  6. Supprimez dans une seule PR avec des métriques avant/après. Exécutez la suite complète avant et après. Mesurez la durée, le taux de flake et les chemins uniques couverts. Les chiffres devraient parler d’eux-mêmes.

L’art de la soustraction

Chaque ingénieur sait comment ajouter un test. Écrire le setup, écrire l’assertion, le regarder passer au vert, commiter. Ça donne un sentiment de productivité. Le nombre de tests augmente. Le rapport de couverture affiche du vert.

Supprimer un test demande un ensemble de compétences différent. Il faut comprendre ce que le test couvre réellement (pas ce que son nom indique), si cette couverture existe ailleurs dans la suite, si l’assertion a sa place à cette couche, et si la suppression crée un trou ou élimine simplement un doublon.

C’est plus difficile. Cela exige de comprendre l’architecture complète des tests, pas seulement le fichier de test individuel. Cela exige la confiance que votre suite restante est suffisante — une confiance appuyée par l’analyse, pas par l’espoir.

Une suite légère, centrée sur les workflows, avec 141 tests qui s’exécutent en 17 minutes, a plus de valeur qu’une suite gonflée de 180 tests qui s’exécute en 24 minutes et flake deux fois plus souvent. La suite plus petite est plus rapide à exécuter, moins coûteuse à maintenir, plus fiable à interpréter, et couvre exactement les mêmes parcours utilisateurs.

Ajouter des tests, c’est facile. Supprimer des tests, c’est de l’ingénierie.