Un test réussit seul, puis échoue dès qu’il s’exécute juste après un autre, sans qu’aucune modification n’ait été apportée à son propre code. C’est le symptôme le plus caractéristique d’un état conservé en mémoire par le process PHP d’un test à l’autre, un phénomène distinct de celui du cache persistant et qui mérite un diagnostic différent.
Ce sujet ne traite pas des transients WordPress, stockés en base de données ou dans le cache objet selon la configuration, ni de wp_cache_flush_group(), déjà couvert ailleurs : il se concentre sur ce qui reste en mémoire du seul fait que PHPUnit exécute plusieurs tests dans un même process PHP.
Pourquoi un seul process suffit à créer ce problème
Sauf configuration de parallélisation explicite, PHPUnit exécute l’ensemble des méthodes d’une suite de tests dans un unique process PHP. Ce process ne redémarre pas entre deux méthodes de test : seules les données de la requête HTTP simulée et la transaction de base de données sont réinitialisées par WP_UnitTestCase. Tout ce qui vit en dehors de ce périmètre — une propriété statique, un singleton, une variable globale déclarée par global — continue d’exister au moment où le test suivant démarre.
Les trois sources les plus fréquentes
- Les propriétés statiques d’une classe applicative. Un compteur, un tableau de configuration résolu une seule fois, ou un drapeau booléen mémorisé en statique conservent leur valeur d’un test à l’autre, même si chaque test recrée une nouvelle instance de la classe.
- Les singletons. Un motif singleton qui stocke son instance unique dans une propriété statique de classe garde exactement le même état que celui laissé par le test précédent, sauf si le code prévoit explicitement une méthode de réinitialisation destinée aux tests.
- Les variables globales PHP. Une extension qui utilise
global $mon_etat;pour partager un état entre plusieurs fonctions procédurales expose ce même risque, la portée globale n’étant jamais remise à zéro automatiquement par PHPUnit.
Un exemple reproductible
class CompteurAppelsApi
{
private static int $nombreAppels = 0;
public static function enregistrerAppel(): int
{
return ++self::$nombreAppels;
}
}
class QuotaApiTest extends WP_UnitTestCase
{
public function test_le_premier_appel_est_compte(): void
{
$this->assertSame(1, CompteurAppelsApi::enregistrerAppel());
}
public function test_un_appel_isole_devrait_aussi_valoir_un(): void
{
// Échoue si ce test s'exécute après le précédent :
// le compteur statique vaut déjà 1, pas 0.
$this->assertSame(1, CompteurAppelsApi::enregistrerAppel());
}
}

La correction : réinitialiser explicitement dans tearDown
La solution ne consiste pas à supprimer les propriétés statiques du code applicatif, souvent nécessaires pour de bonnes raisons de performance, mais à exposer un point de réinitialisation appelé explicitement à la fin de chaque test concerné :
class CompteurAppelsApi
{
private static int $nombreAppels = 0;
public static function enregistrerAppel(): int
{
return ++self::$nombreAppels;
}
public static function reinitialiserPourLesTests(): void
{
self::$nombreAppels = 0;
}
}
class QuotaApiTest extends WP_UnitTestCase
{
public function tearDown(): void
{
CompteurAppelsApi::reinitialiserPourLesTests();
parent::tearDown();
}
}
Distinguer ce problème du cache persistant
Une confusion fréquente consiste à accuser le cache objet persistant d’un symptôme qui relève en réalité de la mémoire du process. La différence se vérifie simplement : un état qui persiste uniquement à l’intérieur d’une même exécution de suite, mais disparaît dès qu’un nouveau process PHP est lancé (une nouvelle invocation de phpunit), relève de la mémoire du process, pas d’un cache persistant qui, lui, survivrait également à ce redémarrage.
En résumé
Un test qui échoue uniquement selon l’ordre d’exécution de la suite pointe le plus souvent vers un état conservé en mémoire par le process PHP partagé entre tous les tests, indépendamment de tout mécanisme de cache. Exposer une méthode de réinitialisation explicite pour chaque propriété statique ou singleton sensible, appelée systématiquement en tearDown(), élimine cette classe de bugs à la source.