PHPUnit et tests WordPress : guide pratique pour développeurs
Guide pratique PHPUnit et tests WordPress : installer la WP Test Suite, structurer les tests, mocking Brain Monkey et CI/CD avec GitHub Actions.
Introduction : pourquoi tester son code WordPress
Les tests automatisés sont l’assurance qualité du développeur. Sur WordPress, où chaque mise à jour peut briser des fonctionnalités, les tests PHPUnit permettent de détecter les régressions avant qu’elles n’atteignent la production. Pourtant, moins de 20 % des plugins WordPress publiés incluent une suite de tests. Ce guide vous montre comment mettre en place des tests efficaces : PHPUnit, WP Test Suite, Brain Monkey pour le mocking et une pipeline CI/CD avec GitHub Actions.
Installer PHPUnit et la WP Test Suite
# 1. Ajouter PHPUnit en dépendance de développement
composer require --dev phpunit/phpunit:^11.0
# 2. Installer WP-CLI (si pas encore installé)
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
# 3. Créer la structure de tests via WP-CLI scaffold
wp scaffold plugin-tests mon-plugin
# Cela crée :
# bin/install-wp-tests.sh (script d'installation WP Test Suite)
# phpunit.xml.dist (config PHPUnit)
# tests/
# bootstrap.php (chargement WP dans les tests)
# test-sample.php (exemple de test)
# 4. Installer la WP Test Suite
bash bin/install-wp-tests.sh wordpress_test root '' localhost latest
Configuration phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
bootstrap="tests/bootstrap.php"
colors="true"
verbose="true"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Mon Plugin">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory suffix=".php">includes</directory>
</include>
</coverage>
</phpunit>
Structure d’un test PHPUnit : setUp, tearDown, assertions
<?php
use PHPUnitFrameworkTestCase;
class MonPluginServiceTest extends TestCase
{
private MonPluginService $service;
private array $testData;
// Exécuté avant chaque test
protected function setUp(): void
{
parent::setUp();
$this->service = new MonPluginService();
$this->testData = ['key' => 'value', 'count' => 42];
}
// Exécuté après chaque test
protected function tearDown(): void
{
unset($this->service);
parent::tearDown();
}
// Test : vérifier le traitement des données
public function test_process_returns_expected_result(): void
{
$result = $this->service->process($this->testData);
$this->assertIsArray($result);
$this->assertArrayHasKey('processed', $result);
$this->assertTrue($result['processed']);
$this->assertEquals(42, $result['count']);
}
// Test avec exception attendue
public function test_process_throws_exception_on_invalid_input(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Données invalides');
$this->service->process([]);
}
// Test avec données multiples (data provider)
/**
* @dataProvider emailProvider
*/
public function test_validate_email(string $email, bool $expected): void
{
$this->assertEquals($expected, $this->service->validateEmail($email));
}
public static function emailProvider(): array
{
return [
['test@example.com', true],
['invalid-email', false],
['test@.com', false],
['a@b.fr', true],
];
}
}
Tester un plugin WordPress : hooks, filtres et fonctions
<?php
// Test avec WP_UnitTestCase (intégration WordPress)
class MonPluginWordPressTest extends WP_UnitTestCase
{
// Créer des données de test avec les factories WP
public function test_plugin_creates_custom_post_type(): void
{
// Créer un article de test
$post_id = $this->factory()->post->create(array(
'post_type' => 'mon_cpt',
'post_status' => 'publish',
'post_title' => 'Test Article',
));
$this->assertIsInt($post_id);
$this->assertGreaterThan(0, $post_id);
$post = get_post($post_id);
$this->assertEquals('mon_cpt', $post->post_type);
$this->assertEquals('Test Article', $post->post_title);
}
// Tester les meta données
public function test_save_post_meta(): void
{
$post_id = $this->factory()->post->create();
// Simuler la sauvegarde du meta
update_post_meta($post_id, '_ma_meta', 'valeur_test');
$valeur = get_post_meta($post_id, '_ma_meta', true);
$this->assertEquals('valeur_test', $valeur);
}
// Tester un filtre WordPress
public function test_mon_filtre_modifie_contenu(): void
{
// Activer le filtre
add_filter('the_content', 'mon_plugin_modifier_contenu');
$contenu_original = 'Contenu original';
$contenu_filtre = apply_filters('the_content', $contenu_original);
$this->assertStringContainsString('Modifié', $contenu_filtre);
// Nettoyer
remove_filter('the_content', 'mon_plugin_modifier_contenu');
}
}
Mocking avec Brain Monkey
<?php
use BrainMonkey;
use BrainMonkeyFunctions;
class MonServiceTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
MonkeysetUp();
}
protected function tearDown(): void
{
Monkey earDown();
parent::tearDown();
}
public function test_service_utilise_get_option(): void
{
// Mocker la fonction WordPress get_option
Functionsexpect('get_option')
->once()
->with('mon_plugin_option')
->andReturn('valeur_mockee');
Functionsexpect('sanitize_text_field')
->once()
->andReturnFirstArg();
$service = new MonService();
$resultat = $service->getOption();
$this->assertEquals('valeur_mockee', $resultat);
}
public function test_hooks_sont_enregistres(): void
{
// Vérifier qu'un hook est bien ajouté
Functionsexpect('add_action')
->once()
->with('init', Mockery::type('callable'));
$plugin = new MonPlugin();
$plugin->init();
}
}
Tests d’intégration vs unitaires
| Type | Vitesse | Isolation | Base de données | Usage |
|---|---|---|---|---|
| Unitaire (Brain Monkey) | Très rapide | Totale | Non | Logique métier pure |
| Intégration (WP_UnitTestCase) | Lent | Partielle | Oui (SQLite/MySQL) | Fonctions WordPress |
| Fonctionnel (Codeception) | Très lent | Aucune | Oui | Scénarios utilisateur |
CI/CD avec GitHub Actions
# .github/workflows/tests.yml
name: Tests PHPUnit WordPress
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.1', '8.2', '8.3']
wordpress-version: ['latest', '6.6']
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress_test
options: --health-cmd="mysqladmin ping" --health-interval=10s
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mysqli, dom, libxml, mbstring
coverage: xdebug
- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress
- name: Install WordPress test suite
run: bash bin/install-wp-tests.sh wordpress_test root root localhost ${{ matrix.wordpress-version }}
- name: Run PHPUnit tests
run: vendor/bin/phpunit --coverage-clover=coverage.xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: coverage.xml
Pour des développements WordPress professionnels avec tests et CI/CD, notre développeur WordPress Paris peut auditer et renforcer votre codebase.
Questions fréquentes sur PHPUnit et les tests WordPress
Combien de temps faut-il pour mettre en place des tests sur un plugin existant ?
Pour un plugin existant sans tests, comptez 2 à 5 jours pour mettre en place l’infrastructure (PHPUnit, WP Test Suite, CI/CD) et écrire les premiers tests sur les fonctions critiques. Le retour sur investissement est rapide : chaque régression évitée en production vaut plusieurs heures de débogage. Commencez par les fonctions les plus utilisées et les plus risquées (traitement de formulaires, requêtes BDD, calculs).
Quelle est la couverture de code (code coverage) idéale ?
Il n’existe pas de chiffre universel, mais 80 % est souvent cité comme objectif raisonnable. Plus important que le pourcentage global : couvrir les chemins critiques (paiements, sauvegarde de données, authentification) à 100 %, et les utilitaires à 70-80 %. Une couverture de 100 % n’est pas toujours réaliste ni souhaitable : certains cas d’erreur système sont impossibles à déclencher en test.
Brain Monkey ou WP_UnitTestCase : lequel utiliser ?
Les deux sont complémentaires. Brain Monkey est idéal pour les tests unitaires purs : rapide, pas besoin de base de données, parfait pour tester la logique métier isolée. WP_UnitTestCase est nécessaire pour les tests d’intégration impliquant de vraies fonctions WordPress (WP_Query, options, posts). Une bonne suite de tests utilise les deux : Brain Monkey pour 70-80 % des tests (rapides) et WP_UnitTestCase pour les 20-30 % restants.
Peut-on tester WooCommerce avec PHPUnit ?
Oui. WooCommerce fournit ses propres factories de tests (WC_Helper_Product, WC_Helper_Order) disponibles via le package woocommerce/woocommerce. Vous pouvez créer des produits et commandes de test, simuler le tunnel d’achat et tester vos personnalisations WooCommerce. La configuration nécessite d’installer le package WooCommerce en mode test et de bootstrapper WooCommerce dans votre tests/bootstrap.php.