Tests instables : quelles sont leurs causes et comment les corriger définitivement

  1. Accueil
  2. Blog
  3. Automatisation des tests

Automatisation des tests

Tests instables : Quelles sont les causes et comment les corriger définitivement

Un test qui échoue au hasard apprend à votre équipe à ignorer les échecs. Apprenez à confirmer qu’un test est instable, à rechercher la véritable cause et à le corriger, avec des démos montrant ce qui fonctionne réellement.

9 min de lecture
Tests instables : quelles sont leurs causes et comment les corriger définitivement

À retenir

  • Un test irrégulier réussit et échoue sur le même code, sans changement.
  • Les causes habituelles sont le timing, l’état partagé, les données instables, les services externes et les différences d’environnement.
  • Remplacez les mises en veille fixes par des attentes pour une condition spécifique et attribuez à chaque test ses propres données.
  • Les nouvelles tentatives peuvent cacher de vrais bugs. Mettez en quarantaine un test instable, corrigez-le dans un délai donné ou supprimez-le.

La construction devient rouge, quelqu’un clique sur “réexécuter”, elle réussit et tout le monde passe à autre chose. Cela semble inoffensif, mais les tests irréguliers détruisent lentement ce qu’une suite de tests possède de plus précieux : la confiance des gens en elle. Lorsque les échecs sont généralement du bruit, de vrais bugs sont également signalés.

Ce guide explique pourquoi les tests deviennent instables et comment résoudre correctement chaque cause. Nous avons exécuté les démos de synchronisation et d’état partagé dans Python 3.13, et les chiffres affichés proviennent de ces exécutions. Il s’agit de petites démonstrations conçues pour rendre la desquamation facile à voir, de sorte que vos propres taux d’échec seront différents.

Qu’est-ce qu’un test feuilleté ?

Un test irrégulier donne des résultats différents sur le même code. Il réussit une exécution et échoue la suivante, même si rien dans le produit n’a changé. Le test peut être erroné ou le code peut contenir un véritable bug intermittent. Cette deuxième possibilité est exactement la raison pour laquelle vous ne devriez pas simplement les rejeter.

Pourquoi les tests irréguliers sont si coûteux

  • Confiance perdue. Une fois que les gens supposent que le rouge signifie « probablement floconneux », ils arrêtent d’enquêter sur les échecs réels.
  • Temps perdu. Les développeurs passent des heures à réexécuter les pipelines et à examiner les échecs qui n’ont rien à voir avec leur modification.
  • Livraison plus lente. Les fusions et les versions attendent les réexécutions, ce qui compromet l’intérêt de CI/CD.
  • Bogues cachés. Les conditions de concurrence dans le produit peuvent ressembler à du bruit de test, et ce sont celles-là qui nuisent à la production.

Les causes courantes en un coup d’œil

Parce que Signe typique Correction habituelle
Synchronisation et asynchrone Échoue davantage lorsque CI est occupé Attendre une condition
État partagé Échec en fonction de l’ordre de test Isoler les données, réinitialiser l’état
Dates et hasard Échec à certaines heures ou jours Contrôlez l’horloge et les graines
Sélecteurs fragiles Échec après les modifications de l’interface utilisateur Utiliser des localisateurs stables
Prestations externes Échec lorsqu’une dépendance est lente Se moquer d’eux ou les écraser
Différences d’environnement Réussit localement, échoue en CI Aligner avec les conteneurs
Limites des ressources Échec lors d’exécutions parallèles Ajustez les travailleurs, corrigez les conflits

Cause 1 : synchronisation et mises en veille fixes

C’est la cause la plus courante. Un test suppose que le travail en arrière-plan sera terminé après un délai fixe. Pour voir l’effet, nous avons écrit un petit test dans lequel le travail prend jusqu’à 2 ms, tandis que le test attend seulement 1 ms avant de vérifier :

def flaky_timing():
    result = {}
    def work():
        time.sleep(random.uniform(0, 0.002))
        result["done"] = True
    threading.Thread(target=work).start()
    time.sleep(0.001)        # hope it has finished
    assert result.get("done")

La solution consiste à attendre la condition réelle, avec une limite supérieure généreuse :

def fixed_timing():
    result = threading.Event()
    def work():
        time.sleep(random.uniform(0, 0.002))
        result.set()
    threading.Thread(target=work).start()
    assert result.wait(timeout=1)   # continues as soon as it is ready

Nous avons exécuté chaque version 200 fois :

Version Des échecs sur 200
Sommeil fixe 102
Attendre une condition 0

Le résultat va bien au-delà de la simple fiabilité. La version fixe se déplace au moment où le travail est terminé, elle est donc généralement plus rapide aussi. Une veille plus longue entraînerait des échecs moins fréquents de la première version, mais chaque exécution serait alors plus lente et elle pourrait toujours échouer sur une machine CI occupée.

Dans les tests de navigateur, le même principe s’applique. Utilisez l’attente automatique de Playwright, ou les Selenium waits explicites, au lieu de time.sleep :

# Playwright: retries until true or a timeout is reached
expect(page.get_by_text("Order placed")).to_be_visible()

# Selenium: an explicit wait for a condition
WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.ID, "confirmation"))
)

Cause 2 : État partagé et ordre de test

Lorsque les tests partagent des données, l’un peut en affecter un autre et le résultat dépend de l’ordre dans lequel ils sont exécutés. Ces deux tests utilisent une liste partagée :

store = []

def test_add():
    store.append("x")
    assert len(store) == 1

def test_empty():
    assert store == []
Commande test_add test_empty
vide, puis ajoutez Passer Passer
ajouter, puis vider Passer Échec

Notre exécution l’a confirmé, et c’est pourquoi une suite peut durer des mois, puis s’interrompre lorsque quelqu’un ajoute des exécutions parallèles ou un plugin de classement aléatoire. Le remède est l’isolement :

  • Donnez à chaque test de nouvelles données, en utilisant un appareil qui les configure et les nettoie. Notre tutoriel pytest montre comment procéder.
  • Créez des enregistrements uniques, tels qu’un e-mail avec un suffixe aléatoire, à la place d’un « utilisateur test » partagé.
  • Enveloppez les tests de base de données dans une transaction et annulez-la, là où votre configuration le permet.
  • Exécutez régulièrement des tests dans un ordre aléatoire, afin que ces problèmes apparaissent tôt.

Cause 3 : dates, fuseaux horaires et caractère aléatoire

Un test qui réussit toute la semaine peut échouer un samedi, à minuit ou le dernier jour du mois. Faites du temps une entrée, plutôt que de lire l’horloge dans votre logique :

def is_weekend(today=None):
    today = today or datetime.date.today()
    return today.weekday() >= 5

is_weekend(datetime.date(2026, 11, 7))   # True  (a Saturday)
is_weekend(datetime.date(2026, 11, 9))   # False (a Monday)

Ces tests donnent la même réponse n’importe quel jour. De même, définissez une valeur de départ fixe pour les valeurs aléatoires dans les tests et enregistrez-la lorsque vous utilisez des données aléatoires afin de pouvoir reproduire un échec. Ne supposez pas un ordre particulier à partir d’ensembles, de dictionnaires provenant de sources externes ou de requêtes de base de données sans ORDER BY.

Cause 4 : Sélecteurs fragiles

Un test qui trouve un bouton avec div:nth-child(3) > span s’interrompt avec tout ajustement de mise en page et peut échouer uniquement pour certaines tailles d’écran ou variantes. Préférez les localisateurs basés sur ce que voient les utilisateurs ou des attributs de test dédiés. Notre guide des Localisateurs de dramaturges explique l’ordre dans lequel les essayer. Les animations sont une autre source fréquente de problèmes, pensez donc à les désactiver dans les environnements de test.

Cause 5 : services et réseaux externes

Si un test fait appel à un véritable fournisseur de paiement ou à une API tierce, son résultat dépend de la disponibilité et des limites de débit de quelqu’un d’autre. Moquez-vous de ces appels pour la plupart des tests et conservez un petit nombre de vérifications de contrat distinctes par rapport au service réel, afin de toujours savoir s’il change. Notre guide sur les tests unitaires et d’intégration explique comment les diviser.

Astuce : « Réussit localement, échoue dans CI » signifie généralement une différence de vitesse, de fuseau horaire, de ressources ou de versions. L’exécution de tests dans la même image de conteneur localement et dans CI en supprime un grand nombre. Notre Tutoriel Docker Compose peut vous aider.

Comment diagnostiquer un test floconneux

  1. Confirmez-le. Exécutez le test plusieurs fois sans modifications. Avec pytest, une simple boucle shell fonctionne, et un plugin tel que pytest-repeat vous donne --count=100.
  2. Enregistrez les preuves. Enregistrez les journaux, les captures d’écran et les traces pour chaque échec. La visionneuse de traces de Playwright est particulièrement utile ici.
  3. Insistez sur les conditions. Exécutez-le avec un processeur limité, en parallèle ou dans un ordre aléatoire. Le faire échouer plus souvent facilite l’étude.
  4. Testez-le seul. S’il réussit toujours seul mais échoue dans la suite, suspectez un état partagé.
  5. Recherchez des modèles. Vérifiez quelles machines, heures de la journée et tests voisins sont impliqués.
  6. Vérifiez le produit. Demandez honnêtement s’il s’agit d’une véritable condition de concurrence critique dans votre application.

Les tentatives automatiques sont-elles une solution ?

Les nouvelles tentatives constituent un filet de sécurité utile à court terme et ne constituent pas un remède. Une seule nouvelle tentative automatique dans CI peut faire avancer le travail pendant que vous enquêtez, mais un test qui nécessite une nouvelle tentative pour réussir vous dit toujours quelque chose. Pire encore, un véritable bug de produit intermittent qui échoue 20 % du temps passera généralement inaperçu après une nouvelle tentative.

Utilisez les tentatives avec ces règles : limitez-les à une ou deux, enregistrez chaque test qui en nécessitait une et traitez cette liste comme votre liste de tâches. Un test signalé comme « réussi après nouvelle tentative » devrait être considéré comme un problème à étudier, et non comme un succès.

Un processus d’équipe pour des tests irréguliers

  1. Mesurer. Suivez les tests qui échouent, puis transmettez le même commit. De nombreux outils CI et rapporteurs de tests peuvent le montrer.
  2. Quarantaine. Déplacez un test irrégulier confirmé hors de l’exécution bloquante, afin qu’il cesse de perturber tout le monde, mais continuez à l’exécuter et à le suivre.
  3. Attribuez un propriétaire et une date limite. Une quarantaine sans date limite devient tout simplement un cimetière.
  4. Corriger ou supprimer. Si un test ne vaut pas la peine d’être corrigé, supprimez-le et vérifiez que sa couverture existe toujours ailleurs.
  5. Prévenir. Exécutez de nouveaux tests plusieurs fois avant de fusionner. Un test qui échoue une fois sur cinquante est mieux réussi maintenant que dans six mois.

Traitez un test irrégulier comme un bug prioritaire, et non comme un bruit de fond. La gravité et la priorité s’appliquent également ici, comme indiqué dans gravité vs priorité. Si la fragilité est répandue, renseignez-vous sur les erreurs d’automatisation des tests plus larges et demandez-vous si vous avez trop de tests au niveau du navigateur.

Pour conclure

Les tests irréguliers semblent aléatoires, mais ils le sont rarement. Presque tout le monde se résume à une hypothèse sur le temps, l’ordre, les données ou l’environnement qui n’est pas toujours vraie. Choisissez le test qui échoue le plus souvent dans votre pipeline cette semaine. Exécutez-le cinquante fois, trouvez l’hypothèse et corrigez-la pour de bon. Une vraie solution apprend à une équipe que plus d’une centaine de rediffusions le feront.

Questions fréquentes

Qu’est-ce qu’un test feuilleté ?

Un test irrégulier réussit certaines exécutions et échoue sur d’autres sans aucune modification du code. La cause peut être liée au test, à l’environnement ou au produit.

Quelles sont les causes des tests irréguliers ?

Les causes les plus courantes sont les hypothèses de synchronisation, l’état partagé entre les tests, les sélecteurs instables, les services externes, la gestion des dates et les différences entre CI et les machines locales.

Dois-je supprimer les tests irréguliers ?

Essayez d’abord de les réparer. Si un test est de faible valeur et coûteux à corriger, il est possible de le supprimer, mais assurez-vous que sa couverture importante existe ailleurs.

Dois-je réessayer automatiquement les tests ayant échoué ?

En tant que filet de sécurité limité, oui, mais enregistrez chaque nouvelle tentative et étudiez-la. Les nouvelles tentatives masquent à elles seules les problèmes et peuvent laisser de vrais bugs intermittents atteindre la production.

Comment trouver des tests instables ?

Recherchez les tests qui échouent et réussissent le même commit, en utilisant votre historique CI ou un outil de reporting. Vous pouvez également exécuter des tests suspects plusieurs fois en boucle.

Continuez à apprendre

Travailler avec Stack Quora

Besoin d’aide pour les tests, WordPress ou le référencement ?

Nous aidons les équipes à proposer de meilleurs logiciels et à développer leurs sites. Dites-nous sur quoi vous travaillez et nous vous proposerons une prochaine étape claire.

Continuez à lire

Plus de guides sur tests, IA et développement moderne

Vous avez apprécié cet article ? Choisissez un sujet et accédez directement à nos nouveaux didacticiels.

Intelligence artificielle

Les hallucinations de l'IA expliquées : pourquoi les modèles de langage inventent les choses et comment les réduire

Les hallucinations de l'IA ont expliqué : ce qu'elles sont, pourquoi les modèles de langage les produisent, les types courants, comment les réduire …

Lire l'article →
DevOps

Variables d'environnement expliquées : fichiers .env, secrets et configuration correctement effectués

Variables d'environnement expliquées avec des exemples Python que nous avons exécutés : lecture et définition, pourquoi chaque valeur est une chaîne…

Lire l'article →
Développement logiciel

Limitation du débit de l'API expliquée : algorithmes, en-têtes et gestion des erreurs 429

Limitation du débit de l'API expliquée avec une simulation que nous avons exécutée : compartiment de jetons, fenêtre fixe, fenêtre glissante, 429 ré…

Lire l'article →
Automatisation des tests

Se moquer en Python : unittest.mock, patch et les erreurs qui font mentir les tests

Moquerie en Python avec unittest.mock, testée : Mock et return_value, side_effect, correctifs où un nom est utilisé, spec et autospec, dates moqueus…

Lire l'article →
Tests logiciels

Couverture du code expliquée : couverture des lignes par rapport aux branches et ce que signifie réellement 100 %

Couverture du code expliquée avec une véritable exécution de cover.py : couverture des lignes, des branches et des conditions, lecture d'un rapport,…

Lire l'article →
Bases de données et SQL

DELETE vs TRUNCATE vs DROP dans SQL : différences, restauration et quand les utiliser

DELETE vs TRUNCATE vs DROP expliqué avec SQL testé : ce que chacun supprime, restauration, déclencheurs, réinitialisation d'identité, vitesse et en …

Lire l'article →
Bases de données et SQL

Vues SQL expliquées : créez, utilisez et évitez les pièges, avec des exemples

Vues SQL expliquées avec les requêtes que nous avons exécutées : création d'une vue, résultats en direct, masquage des colonnes, performances, vues …

Lire l'article →
Développement logiciel

Interface TypeScript vs Type : différences réelles, testées avec le compilateur

Interface TypeScript vs type, vérifiée avec le vrai compilateur : ce que les deux peuvent faire, ce qu'un seul peut faire, la fusion des déclaration…

Lire l'article →
Développement logiciel

Python *args et **kwargs expliqués : fonctions flexibles avec des exemples clairs

Python *args et **kwargs expliqués avec le code que nous avons exécuté : emballage et déballage, ordre des paramètres, arguments de mot-clé uniqueme…

Lire l'article →
Développement logiciel

Fonctions Python Lambda : syntaxe, utilisations réelles et quand ne pas les utiliser

Fonctions Python lambda expliquées avec le code que nous avons exécuté : syntaxe, tri avec clé, carte et filtre, fermetures, piège de liaison tardiv…

Lire l'article →
Intelligence artificielle

Que sont les agents IA ? Comment ils fonctionnent, avec une simple boucle d'agent que vous pouvez exécuter

Que sont les agents IA ? Un guide simple sur la boucle d'agent, les outils, la mémoire et les garde-fous, avec une petite boucle Python que nous avo…

Lire l'article →
DevOps

Surveillance vs observabilité : la vraie différence, avec les journaux, les métriques et les traces

Surveillance et observabilité expliquées : échecs connus et inconnus, journaux, métriques et traces, présentation pas à pas des incidents de paiemen…

Lire l'article →
DevOps

Volumes Docker vs montages Bind : conserver les données du conteneur de la bonne manière

Volumes Docker et montages de liaison expliqués : pourquoi les données du conteneur disparaissent, volumes nommés, montages de liaison, tmpfs, exemp…

Lire l'article →
Automatisation des tests

TDD vs BDD : principales différences, une fonctionnalité conçue dans les deux sens et quand les utiliser

TDD vs BDD expliqué avec une fonctionnalité de remise construite dans les deux sens : refactor rouge-vert avec le code que nous avons exécuté, scéna…

Lire l'article →
Tests logiciels

Tests d'acceptation utilisateur (UAT) : processus, liste de contrôle et exemples réels

Les tests d'acceptation des utilisateurs sont expliqués : qui les exécute, en quoi ils diffèrent du contrôle qualité, un processus en sept étapes, u…

Lire l'article →
Bases de données et SQL

Explication des valeurs SQL NULL : IS NULL, COALESCE, NULLIF et les pièges à éviter

Les valeurs SQL NULL expliquées avec les requêtes que nous avons exécutées : logique à trois valeurs, IS NULL, COALESCE, NULLIF, comment les agrégat…

Lire l'article →
Bases de données et SQL

Instruction SQL CASE WHEN : syntaxe, exemples réels et erreurs courantes

SQL CASE WHEN expliqué avec les requêtes que nous avons exécutées : formulaires simples et recherchés, ordre des conditions, pivots avec SUM, astuce…

Lire l'article →
Développement logiciel

Qu'est-ce que JWT ? Les jetons Web JSON expliqués avec un exemple fonctionnel

Qu’est-ce que JWT ? JSON Web Tokens expliqué : les trois parties, un jeton construit et falsifié dans le code que nous avons exécuté, JWT vs session…

Lire l'article →
Développement logiciel

Boucle d'événements JavaScript expliquée : pile d'appels, microtâches et minuteries avec des exemples

La boucle d'événements JavaScript expliquée avec le code que nous avons exécuté : pile d'appels, files d'attente de tâches et de microtâches, pourqu…

Lire l'article →
Développement logiciel

Générateurs Python et rendement expliqués : économisez de la mémoire avec des exemples réels

Les générateurs Python et le rendement expliqués avec le code que nous avons exécuté : comment ils s'arrêtent et reprennent, les économies de mémoir…

Lire l'article →
Intelligence artificielle

Que sont les intégrations ? Vecteurs et similarité cosinus expliqués simplement

Que sont les intégrations ? Un guide en langage simple sur les vecteurs, la similarité cosinus et la recherche sémantique, avec un petit exemple que…

Lire l'article →
DevOps

Déploiement Bleu-Vert vs Canary : différences, compromis et comment choisir

Explication du déploiement bleu-vert ou canari : comment chacun fonctionne, coût, risque et restauration, regroupement d'utilisateurs stable que nou…

Lire l'article →
Automatisation des tests

Tests instables : quelles sont leurs causes et comment les corriger définitivement

Des tests irréguliers expliqués avec des démos que nous avons exécutées : les principales causes, comment en confirmer et en diagnostiquer une, les …

Lire l'article →
Tests logiciels

Tests de charge vs tests de contrainte : différences, métriques et exemples k6

Explication des tests de charge et des tests de stress : ce que chacun prouve, les tests de pointe et de trempage, les métriques qui comptent, pourq…

Lire l'article →
Tests logiciels

Scénario de test et scénario de test : principales différences, exemples et quand les utiliser

Scénario de test et scénario de test expliqués : des définitions claires, un tableau côte à côte, une fonctionnalité de réinitialisation de mot de p…

Lire l'article →
Bases de données et SQL

Optimisation des requêtes SQL : lire les plans d'exécution et corriger les requêtes lentes, avec des délais

Optimisation des requêtes SQL testée sur un million de lignes : lire les plans d'exécution, ajouter les bons index, corriger les filtres non Sargabl…

Lire l'article →
Bases de données et SQL

SQL UNION vs UNION ALL : différences, exemples et performances testées

SQL UNION vs UNION ALL expliqué avec les requêtes que nous avons exécutées : des doublons, les règles suivent toutes les deux, un véritable bug de d…

Lire l'article →
Développement logiciel

Tutoriel API JavaScript Fetch : GET, POST, gestion des erreurs et délais d'attente

Un didacticiel de l'API JavaScript Fetch testé sur un serveur local : GET et POST, pourquoi 404 ne lance pas, un assistant réutilisable, des chaînes…

Lire l'article →
Développement logiciel

Gestion des fichiers Python : lecture, écriture, CSV, JSON et pathlib avec exemples

Gestion des fichiers Python expliquée avec le code que nous avons exécuté : modes ouverts, avec lecture de fichiers volumineux, CSV, JSON, pathlib, …

Lire l'article →
Développement logiciel

Git merge vs rebase : différences, exemples et quand utiliser chacun

Git merge vs rebase expliqué avec de vraies sorties de log, un tableau comparatif, la règle d'or du rebase et un flux de travail sûr pour les équipes.

Lire l'article →
Développement logiciel

List comprehension Python : syntaxe, exemples et cas à éviter

La list comprehension Python expliquée : syntaxe, filtres, if-else, boucles imbriquées, temps mesurés et cas où une boucle for classique est préféra…

Lire l'article →
Bases de données et SQL

Jointures SQL expliquées avec exemples (Inner, Left, Right, Full)

Comprenez les jointures SQL pas à pas avec des tables et requêtes d'exemple : INNER, LEFT, RIGHT, FULL, CROSS et SELF, plus erreurs courantes et con…

Lire l'article →