Le WordPress d'aujourd'hui, décodé pour les développeurs

Tests

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.

Par WordPress Développement • 10 février 2022 • 5 min de lecture • Aucun commentaire
Un test qui dépend de l'ordre d'exécution des autres : la cause fréquente

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.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Partager :

À propos de l'auteur

WordPress Développement

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi