---
title: "Guerre des exécutions de tests"
type: "test-smell"
slug: "test-run-war"
url: "http://localhost:3000/fr/test-smells/test-run-war.md"
category: "Odeurs erratiques"
description: "Une guerre des exécutions de tests se produit lorsque les tests réussissent pour une personne mais échouent de manière aléatoire dès que plusieurs personnes ou tâches d'intégration continue (CI) exécutent la suite en même temps, parce que les tests entrent en collision sur une fixture partagée et persistante."
---
# Guerre des exécutions de tests

> Une guerre des exécutions de tests se produit lorsque les tests réussissent pour une personne mais échouent de manière aléatoire dès que plusieurs personnes ou tâches d'intégration continue (CI) exécutent la suite en même temps, parce que les tests entrent en collision sur une fixture partagée et persistante.

## Signs and Symptoms

Vous reconnaissez une guerre des exécutions de tests à _qui_ et _quand_, et non au corps du test lui-même :

* La suite est verte sur votre machine et lors des exécutions CI en solo, mais passe au rouge « sans raison » lorsqu'un coéquipier l'exécute en même temps, ou lorsque deux tâches/pipelines CI sollicitent le même environnement.
* Les échecs sont **transitoires et non reproductibles** — relancer le même test le fait généralement réussir.
* Les échecs se concentrent autour d'une ressource partagée, mutable et persistante : une base de données de test partagée, un bucket/une file/un cache S3 partagé, ou un compte/locataire fixe.
* Les formes d'erreur sont révélatrices : violations de clé dupliquée / de contrainte d'unicité, `expected 1 row but found 2`, ou `record not found` (l'exécution de quelqu'un d'autre l'a supprimé).

```js
// Les deux tests supposent qu'ils possèdent exclusivement une base de données de test PARTAGÉE.
test('creates a user', async () => {
  await db.users.insert({ id: 42, email: 'alice@example.com' }); // clé codée en dur
  expect(await db.users.findById(42)).toMatchObject({ email: 'alice@example.com' });
});

test('counts active users', async () => {
  expect(await db.users.countActive()).toBe(1); // suppose que personne d'autre n'a ajouté de lignes
});

```

Lorsque deux exécuteurs sollicitent la même base de données en même temps, l'insertion entre en collision sur `id: 42` (clé dupliquée) et le décompte global n'est plus `1`. Chaque test réussit isolément ; ensemble mais en concurrence, ils s'affrontent.

## Reasons for the Problem

**Pourquoi cela arrive.** Les tests s'appuient sur une **Shared Fixture** (fixture partagée) globale — généralement une unique ressource persistante (une base de données de test partagée, un bucket partagé, un utilisateur fixe) au lieu d'une **Fresh Fixture** (fixture neuve) construite pour chaque test. Des identifiants codés en dur et des assertions sur l'état global supposent tacitement que le test _possède_ la ressource. Tant qu'une seule exécution y touche à la fois, l'illusion tient. Ajoutez de la concurrence — un deuxième développeur, des tâches CI parallèles ou des workers de test parallèles — et les exécutions s'entremêlent sur les mêmes lignes et les mêmes clés.

**Pourquoi c'est nuisible.** Meszaros classe ce problème comme une forme d'**Erratic Test** (test erratique) : il s'agit essentiellement d'un problème d'Interacting Tests (tests qui interagissent) où l'interaction se produit _entre des exécutions de tests concurrentes_ plutôt qu'entre les tests d'une même exécution.

* **Fiabilité :** les échecs sont non déterministes et dépendent de qui d'autre exécute la suite, si bien qu'on ne peut pas les reproduire à la demande.
* **Fausse confiance et perte de confiance :** l'équipe prend l'habitude de « simplement relancer » et finit par balayer d'un revers de main les builds en rouge — y compris les vraies régressions cachées dans le bruit.
* **Maintenabilité / facilité de débogage :** la cause est invisible dans le test qui échoue ; elle réside dans une _autre_ exécution, et l'entrelacement rend la recherche de la cause racine atroce.
* **Scalabilité :** cela bloque les deux choses que les équipes désirent le plus — paralléliser la suite et augmenter le nombre de personnes pouvant l'exécuter simultanément.

## Treatment

Éliminez l'état partagé et disputé. Étapes concrètes, à peu près par ordre de préférence :

1. **Préférez une Fresh Fixture (fixture neuve).** Chaque test crée exactement les données dont il a besoin et les supprime — ou s'exécute dans une transaction annulée au moment du teardown. Ne dépendez pas de lignes partagées préexistantes.
2. **Isolez par exécuteur (Database Sandbox / partitionnement).** Donnez à chaque développeur _et_ à chaque tâche/worker CI sa propre base de données, son propre schéma ou son propre namespace (un schéma par worker, une base de données de conteneur éphémère ou une base de données de branche). Les exécutions concurrentes ne peuvent alors tout simplement pas se voir.
3. **Utilisez des Distinct Generated Values (valeurs générées distinctes) pour les clés.** Remplacez les identifiants/e-mails codés en dur par des valeurs générées uniques (UUID, séquence ou `timestamp + workerId`) afin que les exécutions concurrentes créent des objets _distincts_ au lieu d'entrer en collision.
4. **Ne faites pas d'assertions sur des décomptes globaux.** Limitez les assertions aux données créées par ce test (filtrez selon sa clé/son locataire unique) plutôt que d'écrire `count == 1`.
5. **Rendez les ressources externes spécifiques à chaque test/worker.** Répertoires temporaires uniques, noms de file/de sujet uniques, une base de données en mémoire ou de type testcontainer par processus.

```js
// AVANT : clé codée en dur + assertion globale sur une base de données partagée
await db.users.insert({ id: 42, email: 'alice@example.com' });
expect(await db.users.countActive()).toBe(1);

```

```js
// APRÈS : clé unique + assertion limitée (Fresh Fixture + Distinct Generated Values)
const id = crypto.randomUUID();
const email = `alice+${id}@example.com`;
await db.users.insert({ id, email });
expect(await db.users.findById(id)).toMatchObject({ email });
// ...et faites pointer chaque worker vers son propre schéma / sa propre base de données sandbox

```

Remarque : il n'existe pas de linter pour cela — le problème n'apparaît qu'à l'exécution, sous concurrence. Rendez-le reproductible en exécutant délibérément la suite deux fois en parallèle sur le même environnement (ou en augmentant le nombre de workers de l'exécuteur de tests) dans la CI ; si cela fait passer la suite au rouge, vous avez une guerre des exécutions de tests à corriger.
