# Deux tests qui modifient la même transient : isoler les effets de bord

> Un test passe seul, échoue à côté d'un autre : la cause est souvent une transient partagée en base que personne ne nettoie entre les méthodes.

- Auteur : Clément Hadrot
- Publié le : 2021-03-23
- Mis à jour le : 2021-03-23
- Catégorie : Tests
- URL : https://wpmoderne.dev.wordpress-developpement.fr/tests/isoler-effets-de-bord-transients-partages-tests/

## L’essentiel

- Repérer une clé de transient en dur partagée entre tests
- Nettoyer explicitement dans tearDown
- Préférer des clés uniques par test quand c'est possible

Une extension de tableau de bord commercial calcule un indicateur coûteux — le chiffre d'affaires consolidé par équipe — et le met en cache douze heures via une transient nommée simplement `ca_consolide`. Deux méthodes de test différentes, dans deux fichiers distincts, manipulaient cette même transient. Prises séparément, chacune passait sans problème. Exécutées l'une après l'autre dans la même suite, la seconde héritait de la valeur laissée par la première — et ses assertions sur un montant recalculé échouaient de façon totalement imprévisible selon l'ordre d'exécution des fichiers.

Ce cas illustre un piège spécifique à WordPress : contrairement aux propriétés PHP en mémoire, une transient est stockée en base de données via `wp_options`, et `WP_UnitTestCase` ne la réinitialise pas automatiquement entre deux méthodes de la même façon qu'elle le fait pour d'autres tables — la transaction de retour arrière fonctionne bien, mais seulement si aucun test n'appelle explicitement `delete_transient()` ou ne force une écriture hors transaction.

## Symptôme observé

Le symptôme typique : `test_calcul_ca_equipe_commerciale` échoue uniquement quand il s'exécute après `test_cache_ca_expire_apres_douze_heures`, jamais dans l'ordre inverse. L'assertion attend un montant fraîchement calculé, mais reçoit la valeur figée par le test précédent :

```
public function test_calcul_ca_equipe_commerciale(): void {
    $ca = calculer_ca_consolide_equipe(3);
    $this->assertEquals(48500.00, $ca); // échoue : reçoit 12000.00
}
```

## Diagnostic : remonter à la transient

Un appel direct à `get_transient('ca_consolide')` en tout début de test suffit à confirmer la fuite :

```
public function test_calcul_ca_equipe_commerciale(): void {
    var_dump(get_transient('ca_consolide')); // révèle une valeur déjà présente
    $ca = calculer_ca_consolide_equipe(3);
    $this->assertEquals(48500.00, $ca);
}
```

Le test voisin, en amont, appelait `set_transient('ca_consolide', 12000.00, 12 * HOUR_IN_SECONDS)` pour préparer son propre scénario, sans jamais la retirer ensuite.

> L'essentiel à retenir : Repérer une clé de transient en dur partagée entre tests ; Nettoyer explicitement dans tearDown ; Préférer des clés uniques par test quand c'est possible

## Correctif immédiat : nettoyer dans tearDown

La correction la plus rapide consiste à supprimer explicitement la transient concernée dans le `tearDown()` de chaque test qui la manipule :

```
class Test_Cache_CA_Consolide extends WP_UnitTestCase {

    protected function tearDown(): void {
        delete_transient('ca_consolide');
        parent::tearDown();
    }

    public function test_cache_ca_expire_apres_douze_heures(): void {
        set_transient('ca_consolide', 12000.00, 12 * HOUR_IN_SECONDS);
        $this->assertEquals(12000.00, get_transient('ca_consolide'));
    }
}
```

Ce correctif fonctionne, mais il reste fragile : il dépend de la discipline de chaque développeur qui touchera ce fichier plus tard. Une régression est toujours possible si quelqu'un ajoute une nouvelle méthode qui écrit dans la même transient sans ajouter le nettoyage correspondant.

## Correctif structurel : des clés uniques par test

Une solution plus robuste, quand le code de production le permet, consiste à paramétrer la clé de transient plutôt que de la coder en dur, et à injecter une clé unique par contexte de test :

```
function calculer_ca_consolide_equipe(int $equipe_id, string $cle_cache = 'ca_consolide'): float {
    $cache = get_transient($cle_cache . '_' . $equipe_id);
    if ($cache !== false) {
        return $cache;
    }
    $ca = /* calcul réel */ 0.0;
    set_transient($cle_cache . '_' . $equipe_id, $ca, 12 * HOUR_IN_SECONDS);
    return $ca;
}
```

En suffixant la clé par l'identifiant d'équipe, deux tests qui portent sur des équipes différentes ne se marchent plus jamais dessus, même sans nettoyage explicite — le risque de collision devient un problème de conception résolu une bonne fois, plutôt qu'une discipline à maintenir indéfiniment.

## Étendre la vigilance au-delà des transients

- Les options globales (`update_option()`) posent exactement le même risque et méritent la même vigilance en `tearDown()`.
- Le cache d'objet (`wp_cache_set()`) est généralement réinitialisé entre tests par `WP_UnitTestCase`, mais seulement si le projet n'utilise pas un cache d'objet persistant externe activé pendant les tests — un point à vérifier explicitement sur chaque projet.
- Un test qui pose une transient avec une durée d'expiration doit aussi être testé avec le temps figé, pour ne pas dépendre de la vitesse réelle d'exécution de la suite.

> Sur ce projet, la règle adoptée ensuite était simple : aucune transient nommée en dur dans une fonction testée ne sort en revue de code sans qu'un identifiant contextuel soit ajouté à sa clé.

## En résumé

Un effet de bord partagé en base de données est plus difficile à repérer qu'une fuite d'état en mémoire, car il survit même à un rechargement complet du processus PHP entre deux exécutions de la suite. Nettoyer systématiquement dans `tearDown()` reste indispensable, mais la vraie protection durable consiste à concevoir des clés de cache qui ne peuvent tout simplement pas entrer en collision entre deux contextes de test différents.
