Wenn Sie jemals erlebt haben, dass ein Plugin lokal „funktionierte“ und dann nach einem kleineren Update stillschweigend nicht mehr funktionierte, WordPress In Version 6.9.4 liegt das Problem selten an einer einzelnen Zeile. Es resultiert vielmehr aus fehlenden reproduzierbaren Tests und insbesondere aus einer mangelnden Trennung zwischen reiner Logik und WordPress-Integration.
Was wir bauen werden
Sie werden eine moderne Testumgebung (April 2026) für ein WordPress-Plugin einrichten, das mit PHP 8.1+ und WordPress 6.9.4+ kompatibel ist und aus zwei Ebenen besteht:
- Unit-Tests (schnell): Testen Sie Ihre PHP-Logik, ohne WordPress zu laden.
- Integrationstests (realistisch): WordPress laden und Hooks, Rollen/Berechtigungen, Nonces und Datenbankinteraktionen validieren über
WP_UnitTestCase.
Das Endergebnis: ein Beispiel-Plugin „BPCAB Demo“ mit einer testbaren Architektur (Dienste + einfache Dependency Injection), einer PHPUnit-Suite und einer GitHub Actions-Pipeline, die Tests auf einer PHP/WP-Matrix ausführt.
Am Ende werden Sie es wissen:
- Entwickeln Sie ein Plugin, das die Logik auch ohne WordPress testbar macht.
- Installieren und konfigurieren Sie die WordPress-Testsuite ordnungsgemäß über Composer.
- Schreiben Sie zuverlässige Tests (Unit- und Integrationstests), die keine Fehler verursachen.
- Automatisieren Sie die CI-Ausführung mit einer Matrix und Caches.
Kurze Zusammenfassung
- Wir trennen uns domaine (reines PHP) und Infrastruktur (WP, DB, REST, Admin-Hooks).
- Der Komponist verwaltet Autoload und Entwicklungsabhängigkeiten (
phpunit/phpunit). - Zwei PHPUnit-Suites: Einheit (ohne WP) und Integration (mit WP).
- Der Integrations-Bootstrap lädt WordPress über den WP-Testsuite und konfiguriert die Testdatenbank.
- Auf CI führen wir eine Matrix aus PHP 8.1/8.2/8.3 + WP 6.9.4: (und schließlich allabendlich (wenn Sie gerne gefährlich leben).
Wann sollte diese Lösung verwendet werden?
- Sie pflegen ein geschäftskritisches Plugin (E-Commerce, Mitgliederverwaltung, SEO, Formulare, Synchronisierung).
- Bei WordPress/PHP-Updates treten Regressionen auf.
- Ihr entwickelt euch als Team weiter und wollt PRs, die weniger Fehler verursachen.
- Sie verwenden komplexe Hooks (Prioritäten, kumulative Filter, Shortcodes, REST).
- Sie streben die Einzahlung an. WordPress.org Und Sie wollen ein Mindestmaß an Strenge.
Wann diese Lösung NICHT verwendet werden sollte
- Ihr „Plugin“ ist ein einfacher 15-zeiliger Code-Schnipsel, der in ein Snippets-Plugin eingefügt wurde (und selbst dann: Ein Unit-Test kann zwar noch nützlich sein, aber der Aufwand ist unverhältnismäßig).
- Sie arbeiten an einem einmaligen Projekt, das keine Wartung erfordert (selten bei WordPress, aber es kommt vor).
- Sie weigern sich, Composer einzuführen: Ohne Autoload und ohne Entwicklungsabhängigkeiten werden Sie Ihre Zeit mit Herumprobieren verschwenden.
- Du hast keine isolierte Umgebung (Docker/Testdatenbank). Ich habe schon erlebt, dass Tests in der Produktionsumgebung ohne Backups schiefgehen.
Bevor Sie beginnen (Voraussetzungen)
Versionen und Umgebung
- WordPress : 6.9.4 (Ziel) oder höher.
- PHP Mindestens 8.1 (empfohlen), 8.2/8.3 auch in Ordnung.
- Komponieren : 2.x.
- MySQL / MariaDB : eine speziell für Tests entwickelte Datenbank (z. B.:
wp_tests).
Datensicherung und Isolation
- Zeigen Sie nicht. nie die Testkonfiguration für Ihre Produktionsdatenbank.
- Erstellen Sie nach Möglichkeit einen eingeschränkten Datenbankbenutzer zu Testzwecken.
- Arbeiten Sie in einem Git-Repository: Sie werden Konfigurationsdateien und Skripte bearbeiten.
Nützliche offizielle Ressourcen
- WP-CLI (Entwicklerressourcen) (nützlich, aber optional).
- @wordpress/scripts (Falls Sie auch JS testen, außerhalb des hier behandelten Hauptbereichs).
- PHPUnit-Dokumentation.
- WordPress Core-Handbuch: PHPUnit.
- WordPress-Entwicklungs-Repository (Quelle der WP-Testsuite).
Schritt 1: Erstellen Sie ein testbares Plugin (Gerüst + Autoload)
Der eigentliche Vorteil liegt darin: Wenn Ihre Logik in anonymen Rückruffunktionen feststeckt, testen Sie WordPress anstatt Ihren Code. Ich trenne daher im Allgemeinen:
- src/Domain : reine Logik (Unit-Test).
- src/Infrastruktur : WordPress (Hooks, Optionen, REST, Admin).
- src/Plugin : Bootstrap- und Dienstregistrierung.
1) Erstellen Sie den Plugin-Ordner
Tanz wp-content/plugins/, erstellen:
bpcab-demo/bpcab-demo/bpcab-demo.phpbpcab-demo/src/bpcab-demo/tests/
2) Haupt-Plugin-Datei
schaffen wp-content/plugins/bpcab-demo/bpcab-demo.php :
<?php
/**
* Plugin Name: BPCAB Demo (Testable)
* Description: Plugin d'exemple pour tests unitaires + intégration WordPress.
* Version: 0.1.0
* Requires at least: 6.9
* Requires PHP: 8.1
*/
declare(strict_types=1);
if (!defined('ABSPATH')) {
exit;
}
// Autoload Composer (en dev, et aussi en prod si vous packez vendor/).
$autoload = __DIR__ . '/vendor/autoload.php';
if (file_exists($autoload)) {
require_once $autoload;
}
add_action('plugins_loaded', static function (): void {
// Bootstrap minimal. En vrai, je préfère une classe Plugin + container.
$plugin = new BpcabDemoPluginPlugin(__FILE__);
$plugin->boot();
});
3) Eine Plugin-Klasse + ein Mini-Container
schaffen src/Plugin/Plugin.php :
<?php
declare(strict_types=1);
namespace BpcabDemoPlugin;
use BpcabDemoInfrastructureHooksHelloHook;
use BpcabDemoPluginContainerContainer;
final class Plugin
{
private string $pluginFile;
private Container $container;
public function __construct(string $pluginFile)
{
$this->pluginFile = $pluginFile;
$this->container = new Container();
}
public function boot(): void
{
// Enregistrement des services.
$this->container->set(HelloHook::class, function (): HelloHook {
return new HelloHook();
});
// Activation des intégrations WP.
$this->container->get(HelloHook::class)->register();
}
}
schaffen src/Plugin/Container/Container.php :
<?php
declare(strict_types=1);
namespace BpcabDemoPluginContainer;
use RuntimeException;
final class Container
{
/** @var array<string, callable> */
private array $factories = [];
/** @var array<string, object> */
private array $instances = [];
/**
* @param callable():object $factory
*/
public function set(string $id, callable $factory): void
{
$this->factories[$id] = $factory;
}
public function get(string $id): object
{
if (isset($this->instances[$id])) {
return $this->instances[$id];
}
if (!isset($this->factories[$id])) {
throw new RuntimeException("Service introuvable: {$id}");
}
$instance = ($this->factories[$id])();
$this->instances[$id] = $instance;
return $instance;
}
}
4) Ein Beispiel für reine Logik + einen WP-Hook
schaffen src/Domain/Greeting.php :
<?php
declare(strict_types=1);
namespace BpcabDemoDomain;
final class Greeting
{
public function message(string $name): string
{
$name = trim($name);
if ($name === '') {
return 'Bonjour !';
}
// Cas réel : éviter les espaces multiples, et limiter la taille.
$name = preg_replace('/s+/', ' ', $name) ?? $name;
$name = mb_substr($name, 0, 60);
return "Bonjour {$name} !";
}
}
schaffen src/Infrastructure/Hooks/HelloHook.php :
<?php
declare(strict_types=1);
namespace BpcabDemoInfrastructureHooks;
use BpcabDemoDomainGreeting;
final class HelloHook
{
public function register(): void
{
add_shortcode('bpcab_hello', [$this, 'shortcode']);
}
/**
* @param array<string,mixed> $atts
*/
public function shortcode(array $atts = []): string
{
$atts = shortcode_atts(
[
'name' => '',
],
$atts,
'bpcab_hello'
);
$greeting = new Greeting();
// Sécurité : sortie échappée.
return esc_html($greeting->message((string) $atts['name']));
}
}
Erwartetes Ergebnis
Aktivieren Sie das Plugin in Erweiterungsoptionen → Installierte ErweiterungenFügen Sie auf einer Seite Folgendes hinzu:
[bpcab_hello name="Marie Curie"]
Sie müssen unbedingt „Hallo Marie Curie!“ sehen.
Schritt 2: PHPUnit + WordPress-Testsuite (Composer) installieren
Bei WordPress kommt es häufig zu einem Missverständnis: „Ich habe PHPUnit installiert, also kann ich testen.“ Nein. Für Integrationstests benötigen Sie zusätzlich die WP Test Suite (die Dateien). includes/bootstrap.phpFabriken usw.).
1) Composer initialisieren
Im Plugin-Ordner:
cd wp-content/plugins/bpcab-demo
composer init
Ich rate Ihnen, einen Paketnamen anzugeben. vendor/bpcab-demound um zu definieren src/ als Quelle.
2) PHPUnit und PSR-4-Autoloading hinzufügen
Erstellen/Bearbeiten composer.json :
{
"name": "vendor/bpcab-demo",
"type": "wordpress-plugin",
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
},
"autoload": {
"psr-4": {
"BpcabDemo\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"BpcabDemo\Tests\": "tests/"
}
},
"scripts": {
"test:unit": "phpunit -c phpunit.unit.xml",
"test:integration": "phpunit -c phpunit.integration.xml"
}
}
Dann:
composer install
composer dump-autoload
3) Die WordPress-Testsuite abrufen
Es gibt mehrere Methoden. Im Jahr 2026 bleibt die einfachste die beste:
- Cloner wordpress-develop irgendwo auf Ihrem Rechner,
- seine Datei verwenden
tests/phpunitals Folgetest.
Beispiel (wird angepasst):
mkdir -p ~/wp-tests
cd ~/wp-tests
git clone --depth=1 https://github.com/WordPress/wordpress-develop.git
Sie erhalten dann:
~/wp-tests/wordpress-develop/tests/phpunit~/wp-tests/wordpress-develop/src(der WP-Kern „entwickelbar“)
Offizielle Quelle zum PHPUnit-Kernansatz: Kernhandbuch: Automatisierte Tests / PHPUnit.
Schritt 3: Schreiben Sie den Test-Bootstrap und isolieren Sie die Umgebung
Wir werden créer zwei PHPUnit-Konfigurationen:
- Einheit Kein WordPress, schnell, keine Datenbank.
- Integration WordPress-Lade- und Testdatenbank.
1) PHPUnit-Konfiguration „unit“
schaffen phpunit.unit.xml im Stammverzeichnis des Plugins:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
bootstrap="tests/bootstrap-unit.php"
colors="true"
failOnRisky="true"
failOnWarning="true"
cacheDirectory=".phpunit.cache/unit"
>
<testsuites>
<testsuite name="unit">
<directory suffix="Test.php">tests/unit</directory>
</testsuite>
</testsuites>
<php>
<ini name="error_reporting" value="-1"/>
</php>
</phpunit>
schaffen tests/bootstrap-unit.php :
<?php
declare(strict_types=1);
// Bootstrap minimal pour tests unitaires : autoload uniquement.
$autoload = dirname(__DIR__) . '/vendor/autoload.php';
if (!file_exists($autoload)) {
fwrite(STDERR, "Autoload introuvable. Lancez 'composer install'.n");
exit(1);
}
require_once $autoload;
2) PHPUnit „integration“ konfigurieren
schaffen phpunit.integration.xml :
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
bootstrap="tests/bootstrap-integration.php"
colors="true"
failOnRisky="true"
failOnWarning="true"
cacheDirectory=".phpunit.cache/integration"
>
<testsuites>
<testsuite name="integration">
<directory suffix="Test.php">tests/integration</directory>
</testsuite>
</testsuites>
<php>
<ini name="error_reporting" value="-1"/>
<!-- Paramètres DB : ne mettez jamais la prod ici -->
<env name="WP_TESTS_DB_NAME" value="wp_tests"/>
<env name="WP_TESTS_DB_USER" value="root"/>
<env name="WP_TESTS_DB_PASS" value=""/>
<env name="WP_TESTS_DB_HOST" value="127.0.0.1"/>
<!-- Chemins vers wordpress-develop (à adapter à votre machine) -->
<env name="WP_DEVELOP_DIR" value="/home/vous/wp-tests/wordpress-develop"/>
</php>
</phpunit>
schaffen tests/bootstrap-integration.php :
<?php
declare(strict_types=1);
/**
* Bootstrap d'intégration WordPress.
* Hypothèse : vous avez cloné wordpress-develop et indiqué WP_DEVELOP_DIR.
*/
$autoload = dirname(__DIR__) . '/vendor/autoload.php';
if (!file_exists($autoload)) {
fwrite(STDERR, "Autoload introuvable. Lancez 'composer install'.n");
exit(1);
}
require_once $autoload;
$developDir = getenv('WP_DEVELOP_DIR');
if (!$developDir) {
fwrite(STDERR, "WP_DEVELOP_DIR manquant. Configurez-le dans phpunit.integration.xml.n");
exit(1);
}
$testsDir = rtrim($developDir, '/\') . '/tests/phpunit';
if (!is_dir($testsDir)) {
fwrite(STDERR, "Dossier tests WordPress introuvable: {$testsDir}n");
exit(1);
}
// Variables attendues par la WP test suite.
$_tests_dir = $testsDir;
// Charge les fonctions utilitaires de la suite.
require_once $_tests_dir . '/includes/functions.php';
/**
* Charger le plugin avant que WordPress ne finisse son bootstrap de tests.
* C'est le point standard (muplugins_loaded) utilisé par la suite.
*/
tests_add_filter('muplugins_loaded', static function (): void {
require dirname(__DIR__) . '/bpcab-demo.php';
});
// Démarre WordPress pour les tests.
require_once $_tests_dir . '/includes/bootstrap.php';
Erwartetes Ergebnis
In diesem Stadium liefert das Ausführen der Tests noch kein Ergebnis (keine Testdateien), aber:
composer test:unit
PHPUnit muss mindestens gestartet werden.
Schritt 4: Schreiben Sie echte Unit-Tests (ohne WordPress zu laden).
Wir testen BpcabDemoDomainGreetingDieser Test ist schnell, deterministisch und unabhängig vom WP-Kern.
1) Ordner und Test erstellen
schaffen tests/unit/GreetingTest.php :
<?php
declare(strict_types=1);
namespace BpcabDemoTestsUnit;
use BpcabDemoDomainGreeting;
use PHPUnitFrameworkTestCase;
final class GreetingTest extends TestCase
{
public function testMessageSansNomRetourneUneFormeCourte(): void
{
$greeting = new Greeting();
$this->assertSame('Bonjour !', $greeting->message(''));
$this->assertSame('Bonjour !', $greeting->message(' '));
}
public function testMessageNormaliseLesEspaces(): void
{
$greeting = new Greeting();
$this->assertSame('Bonjour Ada Lovelace !', $greeting->message('Ada Lovelace'));
}
public function testMessageLimiteLaLongueurPourEviterDesSortiesAbsurdes(): void
{
$greeting = new Greeting();
$name = str_repeat('A', 1000);
$result = $greeting->message($name);
$this->assertStringStartsWith('Bonjour ', $result);
$this->assertStringEndsWith(' !', $result);
// "Bonjour " (8) + 60 + " !" (2) = 70
$this->assertSame(70, mb_strlen($result));
}
}
2) Führen Sie die Unit-Tests aus
composer test:unit
Erwartetes Ergebnis
Sie sollten eine grüne Sequenz erhalten. Falls eine Fehlermeldung wie „Klasse nicht gefunden“ erscheint, liegt das fast immer an Folgendem:
- fehlerhaftes PSR-4-Autoloading in
composer.json, - Du hast vergessen
composer dump-autoload, - Sie haben die Datei im falschen Ordner abgelegt.
Schritt 5: WordPress-Integrationstests (WP_UnitTestCase)
Nun überprüfen wir, ob der Shortcode korrekt registriert ist und die Darstellung korrekt ist. Dazu laden wir WordPress, was zwar langsamer ist, aber so die eigentlichen Fehler aufdeckt.
1) Erstellen Sie einen Integrationstest
schaffen tests/integration/ShortcodeHelloTest.php :
<?php
declare(strict_types=1);
namespace BpcabDemoTestsIntegration;
use WP_UnitTestCase;
final class ShortcodeHelloTest extends WP_UnitTestCase
{
public function testShortcodeEstEnregistre(): void
{
// Le plugin est chargé via bootstrap-integration.php.
global $shortcode_tags;
$this->assertIsArray($shortcode_tags);
$this->assertArrayHasKey('bpcab_hello', $shortcode_tags);
}
public function testShortcodeRendLeTexteAttendu(): void
{
$html = do_shortcode('[bpcab_hello name="Marie Curie"]');
// esc_html() est appliqué, donc pas de HTML.
$this->assertSame('Bonjour Marie Curie !', $html);
}
}
2) Führen Sie die Integrationstests durch
composer test:integration
Erwartetes Ergebnis
Wenn die Testdatenbank zugänglich ist und dass WP_DEVELOP_DIR ist richtig, Sie erhalten eine grüne Sequenz.
Wenn die Fehlermeldung „Fehler beim Herstellen einer Datenbankverbindung“ angezeigt wird, beheben Sie das Problem nicht willkürlich. Überprüfen Sie zunächst Folgendes:
- Basis
wp_testsexistiert, - Der DB-Benutzer hat die Rechte.
- Sie haben nicht angegeben
localhostwährend Ihr MySQL-Server auf Folgendes lauscht127.0.0.1(oder umgekehrt, je nach Konfiguration).
Schritt 6: Hooks, Filter, Funktionen und Nonces testen
Die Tests, die die meisten Fehler in der Produktion aufdecken: diejenigen, die überprüfen, ob die Hooks an der richtigen Stelle und mit der richtigen Priorität eingebunden wurden und ob die Sicherheitsvorkehrungen (Nonce/Capabilities) eingehalten werden.
1) Fügen Sie eine sichere Administratoraktion hinzu (Beispiel)
Wir fügen einen sehr einfachen Admin-Aktionsendpunkt hinzu: Er aktualisiert eine Option, jedoch nur für Administratoren und mit einer Nonce.
schaffen src/Infrastructure/Admin/SettingsAction.php :
<?php
declare(strict_types=1);
namespace BpcabDemoInfrastructureAdmin;
final class SettingsAction
{
public const NONCE_ACTION = 'bpcab_demo_save';
public const OPTION_KEY = 'bpcab_demo_enabled';
public function register(): void
{
add_action('admin_post_bpcab_demo_save', [$this, 'handle']);
}
public function handle(): void
{
if (!current_user_can('manage_options')) {
wp_die('Accès refusé', 403);
}
check_admin_referer(self::NONCE_ACTION);
$enabled = isset($_POST['enabled']) && $_POST['enabled'] === '1';
update_option(self::OPTION_KEY, $enabled ? '1' : '0');
wp_safe_redirect(admin_url('options-general.php?page=bpcab-demo'));
exit;
}
}
Stecken Sie es ein src/Plugin/Plugin.php :
<?php
// ... (extrait) ...
use BpcabDemoInfrastructureAdminSettingsAction;
// ... dans boot() ...
$this->container->set(SettingsAction::class, function (): SettingsAction {
return new SettingsAction();
});
$this->container->get(SettingsAction::class)->register();
2) Testkapazität + Nonce
schaffen tests/integration/SettingsActionTest.php :
<?php
declare(strict_types=1);
namespace BpcabDemoTestsIntegration;
use BpcabDemoInfrastructureAdminSettingsAction;
use WP_UnitTestCase;
final class SettingsActionTest extends WP_UnitTestCase
{
public function setUp(): void
{
parent::setUp();
update_option(SettingsAction::OPTION_KEY, '0');
}
public function tearDown(): void
{
// Nettoyage : évite les tests interdépendants.
delete_option(SettingsAction::OPTION_KEY);
parent::tearDown();
}
public function testActionRefuseSansCapacite(): void
{
$userId = self::factory()->user->create(['role' => 'subscriber']);
wp_set_current_user($userId);
$_POST = [
'_wpnonce' => wp_create_nonce(SettingsAction::NONCE_ACTION),
'enabled' => '1',
];
// wp_die() jette une exception dans la suite de tests WP.
$this->expectException(WPDieException::class);
do_action('admin_post_bpcab_demo_save');
}
public function testActionRefuseSansNonceValide(): void
{
$userId = self::factory()->user->create(['role' => 'administrator']);
wp_set_current_user($userId);
$_POST = [
'_wpnonce' => 'nonce_invalide',
'enabled' => '1',
];
$this->expectException(WPDieException::class);
do_action('admin_post_bpcab_demo_save');
}
public function testActionMetAJourOptionAvecNonceEtCapacite(): void
{
$userId = self::factory()->user->create(['role' => 'administrator']);
wp_set_current_user($userId);
$_POST = [
'_wpnonce' => wp_create_nonce(SettingsAction::NONCE_ACTION),
'enabled' => '1',
];
// On évite la redirection réelle en interceptant wp_redirect.
add_filter('wp_redirect', static function (string $location): string {
// On renvoie une URL neutre, mais on laisse WordPress continuer.
return $location;
});
try {
do_action('admin_post_bpcab_demo_save');
} catch (WPDieException $e) {
// Certains environnements de test peuvent convertir exit en die.
// On tolère, l'essentiel est l'état final.
}
$this->assertSame('1', get_option(SettingsAction::OPTION_KEY));
}
}
Dieser Test verdeutlicht einen Punkt, dem ich häufig begegne: Die Admin-Handler beenden schließlich die Ausführung.Im Test kann dies in Folgendes übersetzt werden: wp_die oder eine Ausnahme, abhängig von der Sequenz. Das Muster: Sie prüfen den Zustand (Option, Beitragsmetadaten usw.) anstatt des genauen Ausgabestreams.
Nützliche offizielle Quellen:
Schritt 7: Fabriken, Anlagen, Datenbank und Bereinigung
Fehlgeschlagene Integrationstests entstehen oft durch einen unzureichend bereinigten Datenbankzustand oder durch einen Test, der von einem anderen abhängt. Die WP Test Suite bietet Testumgebungen, aber Disziplin ist unerlässlich:
- Erstellen Sie Ihre Beiträge/Benutzer/Begriffe im Test.
- manuelle Optionen/Transienten bereinigen in
tearDown(), - Verwenden Sie keine fest codierte ID.
Beispiel: Einen Beitrag erstellen und einen Filter testen
Fügen wir einen Filter hinzu, der den Titel eines Beitrags anhand einer Option ändert.
schaffen src/Infrastructure/Hooks/TitlePrefixHook.php :
<?php
declare(strict_types=1);
namespace BpcabDemoInfrastructureHooks;
final class TitlePrefixHook
{
public const OPTION_PREFIX = 'bpcab_demo_title_prefix';
public function register(): void
{
add_filter('the_title', [$this, 'filterTitle'], 10, 2);
}
public function filterTitle(string $title, int $postId): string
{
$prefix = (string) get_option(self::OPTION_PREFIX, '');
$prefix = trim($prefix);
if ($prefix === '' || is_admin()) {
return $title;
}
// Évite de préfixer les titres vides.
if (trim($title) === '') {
return $title;
}
return $prefix . ' ' . $title;
}
}
Stecken Sie es ein Plugin.php wie bei anderen Dienstleistungen.
schaffen tests/integration/TitlePrefixHookTest.php :
<?php
declare(strict_types=1);
namespace BpcabDemoTestsIntegration;
use BpcabDemoInfrastructureHooksTitlePrefixHook;
use WP_UnitTestCase;
final class TitlePrefixHookTest extends WP_UnitTestCase
{
public function tearDown(): void
{
delete_option(TitlePrefixHook::OPTION_PREFIX);
parent::tearDown();
}
public function testPrefixAppliqueSurTitreFront(): void
{
update_option(TitlePrefixHook::OPTION_PREFIX, '[VIP]');
$postId = self::factory()->post->create([
'post_title' => 'Mon article',
'post_status' => 'publish',
]);
// Simule un contexte front.
$this->go_to(get_permalink($postId));
$this->assertFalse(is_admin());
$title = get_the_title($postId);
$this->assertSame('[VIP] Mon article', $title);
}
public function testNePrefixPasSiOptionVide(): void
{
update_option(TitlePrefixHook::OPTION_PREFIX, '');
$postId = self::factory()->post->create([
'post_title' => 'Mon article',
'post_status' => 'publish',
]);
$this->go_to(get_permalink($postId));
$this->assertSame('Mon article', get_the_title($postId));
}
}
Dieser Test schützt Sie vor:
- ein fehlerhafter Hook (Verwechslung von Aktion und Filter),
- eine Priorität, die das Ergebnis ändert (falls ein anderes Plugin ihr ebenfalls ein Präfix voranstellt),
- ein nicht verwalteter Admin-/Frontend-Kontext.
Schritt 8: Automatisierung mit GitHub Actions (WP/PHP-Matrix)
Ohne CI bleibt das Testen „optional“. Mit CI führt eine Regression zu einem Pull Request-Fehler. Genau da beginnt sich der Aufwand auszuzahlen.
1) Einen GitHub Actions-Workflow hinzufügen
schaffen .github/workflows/tests.yml (im Stammverzeichnis des Repos, nicht in wp-content (falls Ihr Plugin ein eigenes Repository ist):
name: Tests
on:
push:
pull_request:
jobs:
phpunit:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ["8.1", "8.2", "8.3"]
wp: ["6.9.4"]
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wp_tests
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h 127.0.0.1 -proot"
--health-interval=10s
--health-timeout=5s
--health-retries=10
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- name: Cache Composer
uses: actions/cache@v4
with:
path: |
vendor
~/.composer/cache
key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('composer.lock') }}
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- name: Checkout wordpress-develop
run: |
mkdir -p $HOME/wp-tests
cd $HOME/wp-tests
git clone --depth=1 --branch ${{ matrix.wp }} https://github.com/WordPress/wordpress-develop.git
- name: Run unit tests
run: composer test:unit
- name: Run integration tests
env:
WP_DEVELOP_DIR: ${{ env.HOME }}/wp-tests/wordpress-develop
WP_TESTS_DB_NAME: wp_tests
WP_TESTS_DB_USER: root
WP_TESTS_DB_PASS: root
WP_TESTS_DB_HOST: 127.0.0.1
run: composer test:integration
Realistische Anmerkungen:
- Die Tags
6.9.4inwordpress-developExistiert, wenn ein Branch/Tag übereinstimmt. Andernfalls verwenden Sie--branch 6.9oder ein Commit. Passen Sie die Vorgehensweise an die zum Zeitpunkt des Lesens geltende Release-Strategie des Kernsystems an. - Wenn Sie WP Nightly hinzufügen, müssen Sie mit vorübergehenden Fehlern rechnen. Ich verwende es oft im Modus „Fehler tolerieren“ für kritische Plugins.
Referenz: offizielle Kernlagerstätte wordpress-develop.
Das vollständige Ergebnis
Wenn Sie alles auf einmal kopieren möchten, finden Sie hier die minimale Zusammenstellung (ohne die bereits angezeigten Dateien). Überprüfen Sie die Pfade bitte noch einmal.
Baum
bpcab-demo/
bpcab-demo.php
composer.json
phpunit.unit.xml
phpunit.integration.xml
src/
Domain/
Greeting.php
Infrastructure/
Admin/
SettingsAction.php
Hooks/
HelloHook.php
TitlePrefixHook.php
Plugin/
Plugin.php
Container/
Container.php
tests/
bootstrap-unit.php
bootstrap-integration.php
unit/
GreetingTest.php
integration/
ShortcodeHelloTest.php
SettingsActionTest.php
TitlePrefixHookTest.php
Schnelle Anpassung
- Fügen Sie eine Suite hinzu Vertrag (Kompatibilitätstests) falls Ihr Plugin öffentliche Filter bereitstellt.
- hinzufügen phpstan Bei der Entwicklung, wenn Sie die Typen sperren möchten (sehr effektiv bei einer „Domäne/Infrastruktur“-Architektur).
- Verlagern Sie die Erstellung von Diensten auf einen Dienstanbieter wenn Ihr Plugin wächst.
Anpassen für Divi 5 / Elementor / Avada
Page-Builder hindern Sie nicht am Testen. Der entscheidende Punkt: Testen Sie Ihre Seite. Logik und Ihre WordPress-Integrationennicht die Benutzeroberfläche des Builders (bei der es eher um E2E-Tests geht).
Divi 5
- Wenn Sie einen Shortcode wie
[bpcab_hello]Divi nutzt es einfach über ein „Code“- oder „Text“-Modul. - Empfohlener Integrationstest: do_shortcode () (bereits erledigt), und wenn Sie Assets hinzufügen, testen Sie
wp_enqueue_scripts(Hook + Abhängigkeiten).
Elementor
- Für Widget Original ElementorIch trenne die Widget-Klasse (Infrastruktur) und einen „Renderer“-Dienst (Domäne). Der Renderer ist eine Einheit, das Widget wird in der Integration getestet (indem überprüft wird, ob der Registrierungs-Hook aufgerufen wird).
- Ein häufig auftretender Sonderfall ist, dass der Code zur Widget-Registrierung zu früh ausgeführt wird. Überprüfen Sie die Priorität.
elementor/widgets/register(oder ein entsprechender Hook, abhängig von der Elementor-Version).
Avada (Fusion Builder)
- Dasselbe Prinzip gilt: Avada-Shortcodes/Elemente können Ihre Ausgabe umschließen. Ein hilfreicher Test: Überprüfen Sie, ob Ihr Shortcode eine stabile, maskierte Zeichenkette zurückgibt.
- Wenn Sie ein „Fusion Element“ bereitstellen, isolieren Sie die Generierung von Optionen (Arrays) und testen Sie es im Unit-Modus.
Endkontrolle
- Innerhalb des Plugins: Erweiterungsoptionen → „BPCAB Demo (Testbar)“ aktivieren.
- Erstellen Sie eine Seite mit
[bpcab_hello name="Test"]Sie sollten „Hallo Test!“ sehen. - In der Befehlszeilenschnittstelle, aus dem Plugin-Ordner:
composer test:unit: grüne Suite, sehr schnelle Ausführung.composer test:integration: grüne Suite, langsamer, Datenbank wird verwendet.
- Auf GitHub: push → der Workflow „Tests“ wechselt zu den PHP-Versionen der Matrix.
Wenn das Ergebnis nicht den Erwartungen entspricht
| Symptom | Mögliche Ursache | Überprüfung | Lösung |
|---|---|---|---|
| PHPUnit: „Klasse nicht gefunden“ | Autoload PSR-4 falsch konfiguriert | aussehen composer.json und der Dateinamensraum |
composer dump-autoload, corriger BpcabDemo\ → src/ |
| Integration: „WordPress-Testordner nicht gefunden“ | WP_DEVELOP_DIR unrichtig |
echo $WP_DEVELOP_DIR / Pfad überprüfen |
Den richtigen Weg einschlagen wordpress-develop |
| Integration: „Fehler beim Herstellen einer Datenbankverbindung“ | Die Testdatenbank existiert nicht oder die Anmeldedaten sind falsch. | Melden Sie sich mit den Zugangsdaten bei MySQL an. | Creieren wp_testskorrekter Host/Benutzer/Passwort |
| Der Shortcode wird im Testmodus nicht gespeichert. | Plugin nicht geladen in muplugins_loaded |
überprüfen tests/bootstrap-integration.php |
Korrigieren Sie die require .../bpcab-demo.php (Weg) |
| PHP-Fehler „Aufruf der nicht definierten Funktion add_action()“ in Einheit | Ein Unit-Test lädt Code, der von WP abhängig ist. | Stacktrace anzeigen | Verschieben Sie die Logik nach DomainMocking oder Tests in der Integration |
Probleme, die ich häufig bei der Fehlersuche beobachte:
- Der Code wurde an die falsche Stelle kopiert (z. B.:
tests/bootstrap-integration.phpeinstellensrc/). - Ein fehlendes Semikolon in einer von Bootstrap geladenen Datei: PHPUnit stoppt, bevor überhaupt ein Test angezeigt wird.
- Verwechslung von Aktionen und Filtern: Sie verwenden
add_actionstattadd_filter(oder umgekehrt), und der Test „stellt nichts fest“. - Du testest in der Produktionsumgebung „nur zum Spaß“: Die Testdatenbank überschreibt Optionen. Das solltest du nicht tun.
- Ihr Plugin wird zu früh/zu spät geladen: Hooks sind nicht verfügbar oder Abhängigkeiten wurden nicht geladen.
Häufige Fallstricke und Fehler
| Fehler | Verursachen | Lösung |
|---|---|---|
| Instabile (fehlerhafte) Integrationstests | Gesamtzustand von WP nicht bereinigt (Optionen, Transienten, Hooks hinzugefügt) | Reinigen in tearDown()Vermeiden Sie globale Singleton-Prozesse und begrenzen Sie die Nebenwirkungen. |
| „Header wurden bereits gesendet“ | Ein Test löst vor dem Puffern eine Umleitung/einen Abbruch aus. | Teste den Endzustand, fange ab wp_redirectVermeiden Sie Assertions auf Headern. |
| „Headerinformationen können nicht geändert werden“ nur in CI | Unterschiede bei PHP-Erweiterungen / Ausgabepufferung | Verlassen Sie sich in PHPUnit nicht auf den HTTP-Stream; verwenden Sie dafür End-to-End-Tests. |
| Ein altes Tutorial empfiehlt veraltete Skripte. | Entwicklung der PHPUnit-/WP-Testsuite | An der aktuellen Kerndokumentation ausrichten und mit wordpress-develop (April 2026) |
| Das Snippets-Plugin verursacht Fehler in den Tests. | Laufzeitcode, der in die Testumgebung geladen wird | Testen Sie Ihr Plugin in einer minimalen Umgebung; deaktivieren Sie unnötige MU-Plugins. |
| Fehler im Zusammenhang mit veraltetem PHP | CI oder lokaler Rechner unter PHP 7.x/8.0 | Aktualisieren Sie auf PHP 8.1+ (siehe Unterstützte PHP-Versionen) |
Variante / Alternative
Option „ohne WordPress-Testsuite“ (nur Unit)
Wenn Ihr Plugin hauptsächlich aus reiner Logik besteht (Berechnungen, Parsen, Generierung von API-Payloads), können Sie sich auf Folgendes beschränken:
- Composer + PHPUnit,
- Unit-Tests auf
src/Domain, - Die Integrationstests wurden durch eine kleine Reihe manueller „Rauchtests“ ersetzt.
Für ein einfaches internes Plugin ist dies akzeptabel, allerdings geht dadurch die Abdeckung von WP-Hooks, Rollen, Nonces und Verhaltensweisen verloren.
„Fortgeschrittenere“ Option: Integrationstests mit einer temporären Umgebung
Bei komplexen Plugins ziehe ich es oft vor, die Integration in einer temporären Umgebung auszuführen:
- Docker Compose (MySQL + WordPress + CLI),
- WordPress-Installation in der Elfenbeinküste,
- PHPUnit-Tests + gegebenenfalls Playwright/Cypress für End-to-End-Tests.
Es ist zwar schwerer, reduziert aber Überraschungen bei Stapeln kurz vor der Produktion.
Sicherheits-, Leistungs- und Wartungstipps
- Sicherheit Testen Sie stets auch „abgelehnte“ Pfade (ungültige Berechtigungen, Nonces). Sicherheitslücken entstehen oft dadurch, dass es funktioniert, wenn man Administratorrechte hat.
- Isolationswerte Die Testdatenbank darf niemals mit anderen Systemen geteilt werden. Nicht einmal lokal.
- Leistung 80 % der Tests sollten im Unit-Testing enthalten sein. Integrationstests sollten die Assembly validieren und nicht jeden einzelnen Fall wiederholen.
- Wartung Wenn WordPress 6.9.x weiterentwickelt wird, möchten Sie, dass die CI Ihnen mitteilt, wenn etwas nicht mehr funktioniert, bevor Ihre Benutzer davon betroffen sind.
- Kompat Wenn Sie mehrere WP-Versionen unterstützen, erstellen Sie eine CI-Matrix (z. B. 6.7, 6.8, 6.9.4) und akzeptieren Sie einige bedingte Anpassungen.
Weitere Infos
- Füge Tests hinzu auf REST API (Routen, Callback-Berechtigungen, Schemas).
- Testen Sie Ihre DB-Migrationen (Tabellenerstellung,
dbDelta()) in der Integration. - Richten Sie ein Dienstanbieter (Reinigerbehälter) und die Verkabelung testen.
- Füge einen Schritt hinzu statische Analyse (PHPStan/Psalm) + Kodierungsstandards (PHPCS WordPress) auf CI.
- Wenn Sie JS (Blöcke) haben, fügen Sie Jest/Vitest hinzu über @wordpress/scripts und die Rohrleitungen trennen.
Ressourcen
- WordPress Core-Handbuch – PHPUnit
- GitHub – WordPress/wordpress-develop
- WP_UnitTestCase (Referenz)
- Plugin-Handbuch – Nonces
- add_shortcode() (Referenz)
- Offizielle PHPUnit-Dokumentation
- WordPress Core Trac (nützlich zur Nachverfolgung von Test-/CI-Änderungen auf der Kernseite)
- PHP-Handbuch
FAQ
Warum „Unit“- und „Integrations“-Tests trennen, anstatt alles mit geladenem WordPress zu testen?
Da das Laden von WordPress Tests verlangsamt, unzuverlässiger macht und die Fehlersuche erschwert, nutze ich WordPress weiterhin zur Validierung der Assemblierung (Hooks, Rollen, Datenbank) und teste die Geschäftslogik in reinem PHP.
Ist PHPUnit 11 erforderlich?
Nein, aber ab PHP 8.1 ist es eine konsistente und bewährte Wahl. Falls Ihre Systemarchitektur Einschränkungen aufweist, passen Sie die Version entsprechend an. composer.jsonSiehe den Abschnitt zur Kompatibilität in der PHPUnit-Dokumentation.
Warum sollte man wordpress-develop klonen anstatt eines „normalen“ WordPress?
Weil die WP-Testsuite in wordpress-develop/tests/phpunitMan kann natürlich auch auf andere Weise damit herumexperimentieren, aber dabei erfindet man Skripte neu, die bereits vom Kernsystem gepflegt werden.
Meine Integrationstests sind sehr langsam. Was soll ich tun?
Reduzieren Sie deren Anzahl, konzentrieren Sie sie auf Integrationspunkte und verschieben Sie den Rest in Unit-Tests. Verwenden Sie auf CI Composer-Caches und vermeiden Sie es, zu viel Verlauf zu klonen (daher --depth=1).
Wie teste ich einen Hook, der von einer Reihenfolge/Priorität abhängt?
Testen Sie den Endeffekt (Ausgabe/Zustand) und fügen Sie einen Test hinzu, der das Vorhandensein des Callbacks überprüft. has_action() / has_filter() Falls relevant. Hinweis: Diese Funktionen überprüfen die Registrierung, nicht die Ausführung.
Wie testet man Code, der Folgendes tut? exit ?
Im Allgemeinen prüft man den Status (Aktualisierungsoption, Beitrag erstellt) und fängt ab, was man kann (Filter). wp_redirectKämpfe nicht darum, einen "Ausweg" zu erzwingen.
Ich sehe in einem Unit-Test die Fehlermeldung „Aufruf der undefinierten Funktion esc_html()“. Ist das normal?
Ja esc_html() ist eine WordPress-Funktion. Wenn Sie einen Unit-Test wünschen, isolieren Sie die Logik in einer reinen PHP-Klasse und testen Sie das Escaping in der Integration oder injizieren Sie eine Escaping-Strategie (fortgeschrittener).
Kann ich Elementor/Divi/Avada mit PHPUnit testen?
Sie können Ihren Integrationscode testen (z. B. „Mein Widget registriert sich am richtigen Hook“), aber nicht die gesamte Benutzeroberfläche. Verwenden Sie für die Benutzeroberfläche End-to-End-Tests (Playwright/Cypress) auf einer Testumgebung.
Soll ich mich festlegen? vendor/ in meinem Plugin?
Bei Plugins, die außerhalb von Composer vertrieben werden, beinhalten viele Plugins Folgendes: vendor/Für ein internes Plugin, das über Composer verwaltet wird, nein. In allen Fällen installieren Sie die Abhängigkeiten auf dem CI-Server.
Wie lässt sich die Kompatibilität mit mehreren Versionen in WordPress verwalten?
Fügen Sie eine CI-Matrix über mehrere WP-Versionen hinweg hinzu und halten Sie Ihre Unit-Tests unabhängig. Für Kernänderungen verfolgen Sie die Tickets auf [Link einfügen]. Lampenfieber und die PRs auf GitHub.
Was tun, wenn ein alter Tutorial-Ausschnitt unter WordPress 6.9.4 nicht mehr funktioniert?
Erzwingen Sie nichts. Orientieren Sie sich an der aktuellen WordPress-Testsuite (wordpress-develop) und passen Sie Ihr Bootstrap entsprechend an. Veraltete Testinstallationsskripte, die man auf veralteten Blogs findet, funktionieren oft nicht mehr, da PHPUnit (geänderte API) und die Pfade der Testsuite nicht mehr funktionieren.