Un test flaky passe et échoue sur le même code. Rien n’a changé entre les deux exécutions, sinon le temps, l’ordre, ou quelque chose d’extérieur au test auquel personne ne pensait.

C’est pire qu’un test qui échoue toujours, et ce n’est pas une figure de style. Un test qui échoue toujours est corrigé avant midi. Un test qui échoue une fois sur dix apprend à toute une équipe à appuyer sur le bouton de relance, et une fois cette habitude prise, chaque échec authentique doit disputer au bruit l’attention de quelqu’un.

Ce que coûte l’instabilité

Elle entraîne les gens à ignorer le rouge. La première fois qu’une compilation échoue, quelqu’un enquête. À la vingtième fausse alerte, la réponse est une relance et un haussement d’épaules, et une vraie régression aura droit au même haussement d’épaules.

Elle se cache dans la foule. Une suite comptant une douzaine de tests peu fiables échoue assez souvent pour que personne ne puisse distinguer un échec peu fiable d’un vrai sans l’ouvrir, donc personne ne l’ouvre.

Elle rend la suite plus lente et moins digne de confiance à la fois. Chaque relance coûte des minutes ; le doute coûte davantage.

D’où vient l’instabilité

La quasi-totalité relève de l’une de cinq choses.

Attendre la mauvaise chose. De loin le cas le plus fréquent dans les tests de navigateur. Le test demande si quelque chose est visible alors que l’élément est encore en train d’apparaître, ou vérifie un état que l’interface atteint une fraction de seconde plus tard. Il passe sur une machine rapide et échoue sur un serveur de build chargé, c’est-à-dire exactement la machine sur laquelle vous ne pouvez pas déboguer.

Dépendance à l’ordre. Un test qui ne passe qu’après l’exécution d’un autre, parce que celui-ci a créé l’enregistrement, fixé l’état ou laissé quelque chose derrière lui. Exécutez la suite dans un autre ordre, ou en parallèle, et elle s’effondre.

État partagé. Une ligne de base de données, une valeur en cache, un fichier sur le disque, une horloge. Deux tests utilisant la même fixture finiront par s’exécuter assez près l’un de l’autre pour se gêner.

Le temps. Tout ce qui vérifie la date du jour finit par franchir minuit ; tout ce qui a un délai d’attente échoue sous la charge ; tout ce qui dépend de l’ordre de deux événements sans garantie d’ordre est un tirage à pile ou face que vous n’avez pas encore remarqué.

Le monde extérieur. Un test qui atteint un vrai réseau, un vrai service tiers ou une vraie horloge a emprunté la disponibilité de quelqu’un d’autre.

L’erreur qui en dissimule la plupart

Le fil commun aux cas de synchronisation mérite d’être énoncé à part, car il change la façon dont vous écrivez l’assertion.

Attendre une transition
Vérifier que le bouton affiche son libellé de repos, alors que l'animation qui efface l'état précédent tourne encore
Attendre un état
Vérifier que la classe "copied" a disparu, et seulement ensuite que le libellé de repos est là

Le premier est une course entre la patience du framework de test et le minuteur de l’interface. Il passe sur une machine tranquille et échoue sur une machine occupée, et le message d’erreur est déroutant plutôt qu’instructif : l’élément est présent, avec le bon texte, et simplement pas encore visible.

Cet exemple est réel : c’est un test de cette base de code, et il a échoué exactement une fois, sur un runner chargé, sur un commit qui modifiait deux fichiers d’image.

Quoi faire d’un test flaky

Ne le corrigez pas en attendant plus longtemps. Relever un délai d’attente global ralentit le chemin d’échec de tous les autres tests et masque la course suivante au lieu de la supprimer. Cela revient à monter le son de la musique.

Ne le supprimez pas non plus, en tout cas pas en premier. Un test flaky pointe généralement vers quelque chose de réel : une vraie course dans le produit, une interface qui annonce la fin avant d’avoir fini, une ressource partagée que deux choses utilisent. Corriger le test signifie parfois corriger l’application.

Quarantaine, puis correction, avec une échéance. Sortez-le de la suite bloquante pour qu’il cesse d’apprendre aux gens à ignorer le rouge, et fixez-lui une date. Une quarantaine sans échéance, c’est une suppression avec des étapes en plus et une conscience moins tranquille.

Comptez-les. Une équipe incapable de dire combien de tests flaky elle a l’apprendra quand le nombre sera grand. Si votre runner enregistre les relances, c’est ce nombre qu’il faut surveiller.

Quand l’instabilité vient du produit

Parfois le test a raison et c’est le logiciel qui n’est pas fiable : une requête qui arrive de temps en temps dans le désordre, une interface qui dit « enregistré » avant que l’enregistrement soit terminé, une tâche qui se termine d’habitude avant que la page ne se recharge.

Ce sont de vrais défauts et ils sont pénibles à signaler, car par définition ils ne se produisent pas à chaque fois. Ce qui les rend reproductibles pour quelqu’un d’autre, c’est le contexte de l’exécution qui a échoué, et non la description des dix qui sont passées.

Session Replay

Extension Chrome gratuite. Un clic sur la page qui se comporte mal capture la copie d'écran, la console et le journal réseau, et vous rend un lien à coller dans le ticket.

Installer l'extension

Pour un défaut intermittent, dites à quelle fréquence il se produit et ce que vous faisiez quand c’est arrivé : « trois fois sur une vingtaine de tentatives, toujours juste après l’enregistrement » est bien plus utile que la description d’une seule occurrence. Le guide du rapport de bogue couvre le reste.

La version courte

Un test flaky échoue sur du code inchangé, et son vrai coût est d’apprendre aux gens à ignorer les échecs. La plupart sont un test qui attend une transition au lieu d’un état, une dépendance à l’ordre, ou de l’état partagé. Corrigez la cause plutôt que le délai d’attente, mettez en quarantaine avec une échéance plutôt qu’indéfiniment, et prenez au sérieux la possibilité que le test ait raison et que ce soit le logiciel qui manque de fiabilité.