Question posée en revue de code presque chaque semaine sur les projets qui adoptent les tests automatisés pour la première fois : « ce test doit-il hériter de WP_UnitTestCase ou d’un simple PHPUnit\Framework\TestCase ? ». La réponse ne dépend ni des habitudes ni de la mode, mais d’une seule question concrète : la fonction testée touche-t-elle à WordPress lui-même — base de données, hooks, options — ou se contente-t-elle de manipuler des données déjà en mémoire ?
Dans l’écosystème WordPress, le terme « test unitaire » est souvent utilisé à tort pour désigner ce qui est en réalité un test d’intégration, parce que WP_UnitTestCase porte ce nom malgré lui. La distinction mérite d’être clarifiée avec des exemples de code, pas seulement de la théorie.
Un vrai test unitaire ne connaît pas WordPress
Un test unitaire, au sens strict, isole une fonction pure ou une classe, sans dépendance à une base de données, à un système de fichiers ou au cœur de WordPress. Il hérite de PHPUnit\Framework\TestCase, sans bootstrap WordPress :
use PHPUnit\Framework\TestCase;
final class Test_Formateur_Prix extends TestCase {
public function test_applique_la_remise_fidelite() {
$formateur = new Formateur_Prix();
$prix = $formateur->calculer( 100.0, remise: 0.10 );
$this->assertEquals( 90.0, $prix );
}
}
Ce test s’exécute en quelques millisecondes, sans base de données ni installation WordPress. Il convient parfaitement à une classe de calcul de prix, un formateur de date, un validateur de format, ou toute logique métier isolable de WordPress.
Un test d’intégration charge le cœur pour de vrai
Dès qu’une fonction appelle get_post(), wp_insert_user(), un hook, ou une option, il faut un test d’intégration, qui hérite de WP_UnitTestCase et s’exécute contre une base de données de test réelle :
class Test_Inscription_Formation extends WP_UnitTestCase {
public function test_inscrit_un_utilisateur_a_une_formation() {
$formation_id = self::factory()->post->create( array( 'post_type' => 'formation' ) );
$user_id = self::factory()->user->create();
mon_plugin_inscrire( $user_id, $formation_id );
$inscrits = get_post_meta( $formation_id, '_inscrits', true );
$this->assertContains( $user_id, $inscrits );
}
}

Coût et vitesse : ce que ça change vraiment
La différence n’est pas que théorique : sur un projet avec plusieurs centaines de tests, la vitesse d’exécution devient un vrai sujet d’ergonomie de développement.
| Critère | Test unitaire | Test d’intégration |
|---|---|---|
| Base de classe | PHPUnit\Framework\TestCase | WP_UnitTestCase |
| Base de données | Aucune | Base de test réelle, réinitialisée par transaction |
| Vitesse typique | Quelques millisecondes | 50 à 200 ms par test |
| Ce qu’il prouve | La logique pure est correcte | L’intégration avec WordPress fonctionne |
Un exemple de code testable des deux façons
Le bon réflexe consiste à extraire la logique pure d’une fonction couplée à WordPress, pour pouvoir la tester en test unitaire pur, et ne garder le test d’intégration que pour la façade WordPress :
- Une fonction
calculer_remise( float $prix, int $anciennete_mois ): floatse teste en isolation totale - La fonction
mon_plugin_appliquer_remise( int $user_id, int $produit_id ), qui lit des métadonnées utilisateur puis appelle la première, nécessite un test d’intégration - Séparer les deux permet d’écrire dix cas de calcul en tests unitaires rapides, et seulement deux ou trois tests d’intégration pour vérifier le branchement
Notion à retenir : ce n’est pas un choix binaire
Un projet WordPress mature combine les deux niveaux plutôt que de choisir un camp. Les tests unitaires purs protègent la logique métier et s’exécutent en continu pendant le développement local ; les tests d’intégration valident que cette logique s’articule correctement avec les hooks, la base et l’API de WordPress, généralement lancés avant un commit ou en intégration continue.
En résumé
La question à se poser n’est jamais « quel type de test dois-je écrire par principe », mais « qu’est-ce que cette fonction touche réellement ». Ce choix se fait fonction par fonction, pas projet par projet, et une bonne architecture de plugin facilite justement la multiplication des tests unitaires purs, moins coûteux à faire tourner. La stratégie de couverture globale à l’échelle d’une agence, elle, relève d’un arbitrage différent et plus organisationnel.