~8 min de lecture

TDD : supprimer un test, ce n'est pas tricher

Il y a une croyance tenace chez les développeurs qui découvrent les tests : un test écrit est un test acquis. On en ajoute, jamais on n'en retire. Le compteur ne fait que monter, et une suite qui maigrit serait un aveu de faiblesse.

C'est faux, et c'est même contre-productif.

Sur le projet qui sert de fil rouge à cette série, un seul commit a supprimé 203 tests répartis dans 28 fichiers. Ce n'était pas un renoncement. C'était la bonne décision.


Un test est du code, avec les mêmes devoirs

On l'oublie parce qu'il vit dans un dossier à part, mais un test :

  • se lit (donc coûte de l'attention)
  • se maintient (donc coûte du temps à chaque refactoring)
  • s'exécute (donc coûte des secondes à chaque push)
  • prétend garantir quelque chose (donc coûte de la confiance quand il ment)

Un test qui ne rapporte plus rien mais coûte toujours autant est une dette. Le garder « au cas où » n'est pas de la prudence, c'est du refus de décider.

La vraie question n'est jamais « ai-je le droit de supprimer ce test ? », mais « qu'est-ce que je perds si je le supprime ? ». Si la réponse est « rien », tu as ta réponse.


Les cinq suppressions légitimes

1. Le code testé n'existe plus

Le cas évident, et pourtant le plus douloureux à assumer.

Sur ce projet, une première architecture avait été construite : une librairie de domaine avec toute sa couche d'abstraction (ports, use cases, builders) et des composants UI partagés. Le tout dans un libs/ en bonne et due forme, et 28 fichiers de spec. Le détail de ces couches importe peu ici : retiens qu'il s'agissait d'une structure nettement plus élaborée que ce que l'application demandait.

Puis il est apparu que cette structure était disproportionnée pour l'application visée. Un commit a tout retiré :

140 files changed, 170 insertions(+), 9561 deletions(-)

203 it() partis d'un coup.

La tentation, dans ce moment-là, est de garder les tests « parce qu'ils ont coûté cher ». C'est un raisonnement de coût irrécupérable : l'argent déjà dépensé ne doit pas influencer la décision suivante. Des tests qui gardent du code supprimé ne gardent rien.

2. La feature a changé de sens

Plus subtil. Le test compile, il passe, mais il ne décrit plus le produit.

Exemple réel. Au départ, la popup de fin de liste s'ouvrait automatiquement quand tous les produits étaient cochés, et confirmer démarrait une nouvelle liste :

it('should open a new list when I click on confirm button', async () => {
  // GIVEN
  await pageModel.initAllProductArePickedUp();

  // WHEN
  await pageModel.clickOnConfirmNextListDialog();

  // THEN
  pageModel.assertNoProduct();
});

Puis une nouvelle feature arrive : on peut terminer sa liste quand on veut, via un bouton, sans avoir tout coché. Le chemin d'entrée dans la popup n'est plus le même, et ce que le test vérifiait devient un cas particulier parmi d'autres.

Le test a été remplacé par deux tests qui décrivent le nouveau produit :

it('should close the dialog when I click on confirm button', async () => {
  // GIVEN
  await pageModel.addProductToList({ name: 'Lait' });
  await pageModel.clickOnCloseListButton();

  // WHEN
  await pageModel.clickOnConfirmNextListDialog();

  // THEN
  pageModel.assertNextListDialogIsClosed();
});

it('cannot open the dialog when I click on close list button', async () => {
  // WHEN
  await pageModel.clickOnCloseListButton();

  // THEN
  pageModel.assertNextListDialogIsClosed();
});

Le second couvre le cas limite : sur une liste vide, il n'y a rien à terminer, donc le bouton ne doit rien ouvrir.

💡 Le signal à reconnaître : quand tu te surprends à modifier le GIVEN d'un test pour qu'il continue de passer, tu n'es plus en train de le maintenir. Tu es en train d'en écrire un autre. Autant l'assumer et repartir de la description du comportement.

3. Le test est redondant

Trois tests qui passent par le même chemin et vérifient la même chose sous trois angles ne t'apprennent rien de plus que le premier. Ils triplent juste le coût de maintenance.

Le cas typique, c'est le même chemin parcouru trois fois avec des données différentes, sans qu'aucune règle ne les distingue :

// ❌ Trois fois le même chemin, trois fois la même règle
it('should add Lait to the list', async () => {
  await pageModel.addProductToList({ name: 'Lait' });
  pageModel.assertProductExists({ id: '...', name: 'Lait' });
});

it('should add Beurre to the list', async () => {
  await pageModel.addProductToList({ name: 'Beurre' });
  pageModel.assertProductExists({ id: '...', name: 'Beurre' });
});

it('should add Chocapic to the list', async () => {
  await pageModel.addProductToList({ name: 'Chocapic' });
  pageModel.assertProductExists({ id: '...', name: 'Chocapic' });
});

Les trois parcourent le même chemin et déclenchent la même règle. La seule règle qui regarde le nom est le refus d'un nom vide, et aucun des trois n'est vide : les deux derniers ne gardent rien de plus que le premier, ils triplent juste le temps de maintenance.

Le jour où un bug fait tomber « Beurre » sans faire tomber « Lait », c'est qu'une règle dépendant du nom vient d'apparaître - et c'est elle qu'il faut tester, une fois, pas trois.

Le contre-exemple à ne pas confondre : deux tests qui partent de la même action mais interrogent des niveaux différents - l'un l'état, l'autre le DOM rendu. Ceux-là tombent pour des raisons distinctes et se gardent tous les deux. Le critère est développé dans combien de tests par feature ?.

4. Le test ne peut plus échouer

Un test qui reste vert quoi qu'il arrive ne garde rien. Ça arrive quand :

  • l'assertion porte sur une valeur que le test a lui-même injectée, via une doublure à qui il a dicté quoi répondre (voir quand faut-il vraiment mocker ?)
  • le sélecteur ne matche plus rien et l'assertion est un toBeFalsy() sur null
  • la branche testée est devenue inatteignable

Le test de vérification : casse volontairement le code de production correspondant. Si la suite reste verte, le test est décoratif. Supprime-le ou répare-le.

5. Le test teste l'implémentation

// ❌ Meurt au premier refactoring, sans qu'aucun comportement n'ait changé
it('should call save on the repository', () => {
  component.submit();
  expect(mockRepository.save).toHaveBeenCalledOnce();
});

Celui-là ne se supprime pas sec : il se remplace par un test de comportement. Mais l'opération nette est bien une suppression, et il ne faut pas la repousser sous prétexte que « ça fait un test en moins ».


🚨 Le seul cas où c'est vraiment tricher

Il y en a un, et il est net :

Supprimer un test parce qu'il est rouge.

Un test rouge te dit une chose parmi trois :

  1. Le code de production a un bug ➜ corrige le code.
  2. Le comportement attendu a changé ➜ réécris le test en partant de la nouvelle spec, pas en bricolant l'ancienne.
  3. Le test était mal écrit ➜ répare le test, et demande-toi pourquoi il a passé jusqu'ici.

Aucune de ces trois réponses n'est « supprime-le et passe à autre chose ».

Comment faire la différence, concrètement

La question à se poser avant de supprimer :

« Est-ce que je supprime ce test avant ou après l'avoir vu rouge ? »

  • Avant (il est vert, mais obsolète, redondant, ou il garde du code mort) ➜ suppression saine.
  • Après (il est rouge, et le supprimer me rend la suite verte) ➜ tu es en train de masquer une information.

Le second cas se repère à un signe très fiable : tu ressens du soulagement en le supprimant. Un test dont la suppression soulage est un test qui te disait quelque chose.


En pratique : nettoyer pendant la phase REFACTOR

Le cycle TDD, c'est RED ➜ GREEN ➜ REFACTOR. La suppression de tests appartient à la troisième phase, exactement comme le nettoyage du code de production.

Phase Ce que tu fais Suppression de test ?
RED écrire un test qui échoue ❌ jamais
GREEN le minimum pour passer au vert ❌ jamais
REFACTOR améliorer sans changer le comportement ✅ ici

Et la contrainte qui rend l'opération sûre : la suite est verte avant, la suite est verte après. Si tu supprimes un test alors que la suite est rouge, tu ne sais pas ce que tu es en train de faire.


Conclusion

Une suite de tests n'est pas un trophée. C'est un outil, et un outil s'entretient : on affûte, on remplace, on jette ce qui ne coupe plus.

Le vrai indicateur de qualité n'a jamais été le nombre de tests. C'est le nombre de bugs qu'ils t'empêchent de livrer, rapporté au temps qu'ils te coûtent.

Ce que tu dois retenir :

Code supprimé ➜ tests supprimés, sans état d'âme

Feature qui change de sens ➜ test réécrit, pas rafistolé

Test qui ne peut plus échouer ➜ décoratif, vérifie-le en cassant le code

Test d'implémentation ➜ remplacé par un test de comportement

Suppression pendant REFACTOR, suite verte avant et après

Supprimer un test rouge : là, et seulement là, c'est de la triche

La règle d'or :

Tu supprimes un test parce qu'il ne garde plus rien. Jamais parce qu'il te dérange.


Pour aller plus loin


Envie de découvrir EasyAngularKit et son programme ?

Découvre EasyAngularKit : https://www.easyangularkit.com/easyangularkit

📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

AngularKit

Suite d'outils pour développeurs Angular francophones. Apprends, modernise tes réflexes, audite ta codebase.

Produits

Contact

Légal

© 2026 AngularKit. Tous droits réservés.