Sur un projet de plateforme de mise en relation entre artisans et particuliers, neuf fichiers de test différents recréaient chacun, à leur façon, un « artisan avec profil complet, trois avis et une zone d’intervention ». Trois versions légèrement différentes de cette fixture circulaient dans le code, avec des divergences subtiles qui ont fini par provoquer des faux positifs — un test passait sur une fixture incomplète alors que le vrai comportement de production échouait.
Ce problème n’est pas un problème de WP_UnitTestCase ni de ses factories natives : elles restent le socle. Le problème est l’absence de couche intermédiaire pour partager des combinaisons de fixtures métier réutilisables entre fichiers.
Repérer la duplication avant de centraliser
La première étape n’est pas d’écrire du code, mais de lister les combinaisons de fixtures réellement partagées entre fichiers. Sur ce projet, trois profils revenaient partout : l’artisan complet, l’artisan sans avis, et le particulier avec une demande en cours. Créer une abstraction pour une fixture utilisée une seule fois n’apporte rien — la centralisation ne se justifie qu’à partir de deux usages réels et déjà constatés.
Écrire la classe abstraite commune
La classe de base étend WP_UnitTestCase et expose des méthodes protégées dédiées à chaque combinaison de fixtures, appelables depuis n’importe quel fichier de test qui en hérite :
abstract class Test_Case_Artisans extends WP_UnitTestCase {
protected function creer_artisan_complet(array $overrides = []): int {
$artisan_id = $this->factory()->post->create(array_merge([
'post_type' => 'artisan',
'post_status' => 'publish',
], $overrides));
update_post_meta($artisan_id, 'zone_intervention', 'Lyon et périphérie');
for ($i = 0; $i < 3; $i++) {
$this->factory()->comment->create([
'comment_post_ID' => $artisan_id,
'comment_type' => 'avis_client',
]);
}
return $artisan_id;
}
protected function creer_particulier_avec_demande(): array {
$particulier_id = $this->factory()->user->create(['role' => 'particulier']);
$demande_id = $this->factory()->post->create([
'post_type' => 'demande',
'post_author' => $particulier_id,
]);
return ['particulier_id' => $particulier_id, 'demande_id' => $demande_id];
}
}
Hériter plutôt que copier
Chaque fichier de test spécifique hérite désormais de cette classe abstraite au lieu de WP_UnitTestCase directement, et se concentre sur ce qui le distingue réellement :
class Test_Notation_Artisan extends Test_Case_Artisans {
public function test_note_moyenne_calculee_sur_avis_publies(): void {
$artisan_id = $this->creer_artisan_complet();
$moyenne = calculer_note_moyenne($artisan_id);
$this->assertGreaterThanOrEqual(0, $moyenne);
$this->assertLessThanOrEqual(5, $moyenne);
}
}

Le fichier de test gagne en lisibilité : il ne reste que ce qui est spécifique au comportement vérifié, la fixture partagée est un simple appel de méthode dont le nom documente déjà son contenu.
Autoriser des variations sans multiplier les méthodes
Le paramètre $overrides évite d’avoir à écrire une nouvelle méthode de fixture pour chaque petite variation. Un test qui a besoin d’un artisan complet mais non publié peut le demander sans toucher à la classe de base :
public function test_artisan_brouillon_absent_des_resultats_recherche(): void {
$artisan_id = $this->creer_artisan_complet(['post_status' => 'draft']);
$resultats = rechercher_artisans(['ville' => 'Lyon']);
$this->assertNotContains($artisan_id, wp_list_pluck($resultats, 'ID'));
}
Les pièges de la centralisation
- Une classe abstraite qui grossit trop devient elle-même un problème de maintenance — au-delà d’une dizaine de méthodes de fixture, mieux vaut la scinder par domaine métier (artisans, demandes, avis).
- Une méthode de fixture qui fait trop d’hypothèses implicites (trois avis, toujours à Lyon) finit par contraindre des tests qui n’en ont pas besoin ; les paramètres par défaut doivent rester représentatifs, pas universels.
- Modifier une méthode de fixture partagée impacte potentiellement des dizaines de tests d’un coup : tout changement de comportement par défaut mérite de faire tourner la suite complète avant de committer.
Une fixture partagée bien conçue se lit comme une phrase :
$this->creer_artisan_complet(['post_status' => 'draft'])décrit exactement ce que le test manipule, sans avoir à ouvrir un second fichier pour comprendre.
Ce qu’il faut retenir
Centraliser les fixtures partagées dans une classe abstraite commune n’est pas une optimisation prématurée dès qu’un projet dépasse une poignée de fichiers de test liés au même domaine métier. Le gain ne se mesure pas seulement en lignes économisées, mais en cohérence : un artisan complet signifie exactement la même chose partout dans la suite, ce qui élimine la classe de faux positifs la plus insidieuse — celle où deux tests utilisent le même mot pour des fixtures en réalité différentes.