# Un test qui dépend de l’ordre d’exécution des autres : la cause fréquente

> Pourquoi un test qui passait la veille échoue-t-il aujourd'hui, sans qu'une seule ligne de code testé n'ait changé ? La réponse tient souvent à l'ordre d'exécution.

- Auteur : WordPress Développement
- Publié le : 2022-02-10
- Mis à jour le : 2022-02-10
- Catégorie : Tests
- URL : https://www.wpmoderne.fr/tests/test-depend-ordre-execution-cause-frequente/

## L’essentiel

- Un état global ou statique non réinitialisé entre deux tests
- Un test qui dépend implicitement du résultat d'un autre
- Une commande pour reproduire le problème de façon fiable

Pourquoi une suite de tests, verte la veille, affiche-t-elle un échec le lendemain matin, sans qu'aucune ligne de code source n'ait été modifiée entretemps ? Cette question, posée dans presque toutes les équipes qui font grandir leur suite de tests au-delà de quelques dizaines de méthodes, a une réponse récurrente : un test dépend, sans le savoir, d'un effet de bord laissé par un autre test exécuté juste avant lui.

PHPUnit n'exécute pas nécessairement les tests dans l'ordre où ils apparaissent dans le fichier, en particulier quand plusieurs classes sont concernées ou quand une option de randomisation est activée. Un test correctement isolé doit produire le même résultat quel que soit l'ordre dans lequel il s'exécute par rapport aux autres.

## Le symptôme : un échec impossible à reproduire isolément

```
# Cette commande passe sans problème
phpunit --filter test_compte_les_articles_publies

# Cette commande, exécutant toute la suite, échoue parfois sur ce même test
phpunit
```

Ce contraste est le signal le plus fiable d'une dépendance cachée à l'ordre d'exécution. Si un test échoue uniquement en présence d'autres tests, la cause ne se trouve jamais dans ce test isolément, mais dans une interaction avec l'état laissé par un ou plusieurs tests précédents.

## Les causes les plus fréquentes

> L'essentiel à retenir : Un état global ou statique non réinitialisé entre deux tests ; Un test qui dépend implicitement du résultat d'un autre ; Une commande pour reproduire le problème de façon fiable

- Une propriété statique de classe, initialisée une seule fois dans `setUpBeforeClass`, modifiée par un test sans être restaurée avant le suivant.
- Une option WordPress ou un transient modifié directement par un test, sans passer par les mécanismes de nettoyage automatique de `WP_UnitTestCase`.
- Un utilisateur défini comme utilisateur courant via `wp_set_current_user`, jamais réinitialisé à zéro en fin de test.
- Un cache d'objet en mémoire, interne à une classe de service, qui conserve une valeur calculée lors d'un test précédent et la réutilise à tort pour le test suivant.

Dans chacun de ces cas, le test qui échoue n'est pas fautif en lui-même : il subit simplement les conséquences d'un état que le test précédent n'a pas nettoyé correctement.

## Reproduire le problème de façon fiable

```
phpunit --order-by=random --random-order-seed=1234
```

Cette option force PHPUnit à exécuter les tests dans un ordre aléatoire, mais reproductible grâce à la graine fournie. Relancer la suite plusieurs fois avec des graines différentes permet de faire apparaître ce type de dépendance de façon nettement plus rapide qu'en attendant qu'elle se manifeste naturellement dans l'ordre habituel d'exécution.

## Un exemple concret de fuite d'état

```
class CompteurArticlesTest extends WP_UnitTestCase {

    public function test_categorie_actualites_vide(): void {
        $categorie_id = self::factory()->category->create( [ 'name' => 'Actualités' ] );
        $this->assertSame( 0, get_term( $categorie_id )->count );
    }

    public function test_categorie_actualites_avec_articles(): void {
        // Recherche par nom au lieu de créer une nouvelle catégorie dédiée :
        // ce test dépend de l'existence d'une catégorie créée ailleurs.
        $categorie = get_term_by( 'name', 'Actualités', 'category' );
        self::factory()->post->create( [ 'post_category' => [ $categorie->term_id ] ] );

        $categorie_mise_a_jour = get_term( $categorie->term_id );
        $this->assertSame( 1, $categorie_mise_a_jour->count );
    }
}
```

Le second test suppose implicitement que la catégorie « Actualités » créée par le premier test existe déjà. Exécuté seul, il échoue immédiatement avec une erreur sur un objet nul. Exécuté après le premier test, il fonctionne, mais uniquement parce que l'ordre d'exécution garantit, par hasard, la présence de cette donnée.

## Le correctif : chaque test crée sa propre fixture

```
public function test_categorie_avec_articles(): void {
    $categorie_id = self::factory()->category->create( [ 'name' => 'Actualités Locales' ] );
    self::factory()->post->create( [ 'post_category' => [ $categorie_id ] ] );

    $categorie_mise_a_jour = get_term( $categorie_id );
    $this->assertSame( 1, $categorie_mise_a_jour->count );
}
```

En créant sa propre catégorie plutôt qu'en cherchant à réutiliser celle d'un autre test, ce test devient indépendant de tout ce qui l'entoure. Il produit le même résultat, qu'il soit exécuté seul, en premier, en dernier, ou dans un ordre totalement aléatoire.

> La question à se poser systématiquement en écrivant un test n'est pas « ce test passe-t-il », mais « ce test passerait-il exactement de la même façon s'il était le seul test de toute la suite ».

## Le rôle de WP_UnitTestCase dans l'isolation

Une bonne partie de cette isolation est déjà gérée automatiquement par `WP_UnitTestCase`, qui encapsule chaque test dans une transaction de base de données annulée à la fin, garantissant qu'aucune donnée créée ne persiste réellement entre deux tests. Ce mécanisme couvre les données en base, mais pas l'état en mémoire PHP, comme les propriétés statiques, les variables globales, ou les caches internes à un service, qui restent sous la responsabilité du développeur.

## En résumé

Un test qui échoue uniquement en présence d'autres tests révèle presque toujours une dépendance implicite à un état laissé par un test précédent, en mémoire plutôt qu'en base de données. Utiliser l'exécution aléatoire pour révéler ce type de problème rapidement, puis garantir que chaque test construit sa propre fixture indépendante, élimine cette classe entière de bugs intermittents et rend la suite fiable quel que soit l'ordre d'exécution choisi.
