Automatisierte Tests haben sich in der modernen WordPress-Entwicklung von einer freiwilligen Fleißaufgabe zu einem grundlegenden Qualitätsstandard für produktive Unternehmenssysteme entwickelt. Über viele Jahre verließen sich Entwicklerteams auf manuelle Sichtprüfungen: Browserfenster aktualisieren, Formulare im Administrationsbereich durchklicken und die Datenbank nach Code-Anpassungen stichprobenartig kontrollieren. Dieser Ansatz scheitert unausweichlich, sobald ein Projekt wächst und komplexe Custom Post Types, REST-API-Endpunkte, mehrsprachige Taxonomien und individuelle Geschäftslogik ineinandergreifen. Eine kleine Regression in einem selten genutzten Filter-Callback bleibt bei manueller Prüfung oft unentdeckt, bis Transaktionen im Live-Betrieb abbrechen.
Eine professionelle Teststrategie unterteilt die Qualitätssicherung in klar getrennte Schichten. Auf der untersten Ebene stehen Unit-Tests, die in wenigen Millisekunden ohne WordPress-Laufzeitumgebung und ohne Datenbankverbindung ausgeführt werden. Darüber prüfen Integrationstests das Zusammenspiel mit dem echten Datenbankschema und den Core-Funktionen von WordPress. Auf der obersten Ebene validieren End-to-End- und Akzeptanztests die Abläufe im Browser. Ein robustes Test-Setup im Jahr 2026 erfordert eine saubere Trennung dieser Ebenen sowie den gezielten Einsatz moderner Werkzeuge wie PHPUnit 10 und 11, Brain Monkey, Mockery, wp-browser und wp-env.
Grundlagen von Unit-Tests und das Prinzip vollständiger Isolation
Ein echter Unit-Test isoliert eine einzelne Funktion oder Klassenmethode von sämtlichen externen Abhängigkeiten. In der WordPress-Entwicklung stellt diese Isolation eine architektonische Herausforderung dar, weil herkömmlicher Code stark auf globalen Funktionen wie get_post(), get_option() und apply_filters() aufbaut. Werden solche Funktionen innerhalb von Domänenlogik aufgerufen, führt die Ausführung in einer isolierten PHP-Umgebung ohne geladenen WordPress-Core unmittelbar zu fatalen Fehlern wegen nicht definierter Funktionen.
Der Versuch, Unit-Tests dadurch zu ermöglichen, dass für jeden einzelnen Testfall die gesamte WordPress-Umgebung samt Datenbank hochgefahren wird, zerstört die Feedback-Schleife der Entwickler. Dauert ein Testlauf zehn Minuten, wird er vor dem Commit schlicht nicht mehr ausgeführt. Reine Unit-Tests müssen innerhalb weniger Sekunden abschließen. Um dieses Tempo zu erreichen, muss die Geschäftslogik durch Test-Doubles und Funktions-Mocks vollständig von globalen Zuständen und der Datenbank entkoppelt werden.
Testbarer WordPress-Code beginnt mit der Softwarearchitektur. Klassen sollten reine Geschäftslogik strikt von WordPress-APIs trennen. Anstatt update_post_meta() mitten in einer Preisberechnung aufzurufen, übergibt man die Eingabedaten an eine zustandslose Methode und liefert das Ergebnis zurück. Unvermeidbare Interaktionen mit WordPress-Funktionen werden in Adaptern gekapselt, sodass Test-Frameworks ihr Verhalten kontrolliert simulieren können.
Brain Monkey für Hook- und Filter-Zusicherungen
Brain Monkey ist das maßgebliche Werkzeug für isolierte Unit-Tests im WordPress-Ökosystem. Speziell entwickelt, um WordPress-Funktionen, Actions und Filter ohne Laden der WordPress-Dateien abzubilden, ermöglicht Brain Monkey Testausführungen im Millisekundenbereich.
Brain Monkey fängt Aufrufe von add_action(), add_filter(), do_action() und apply_filters() zuverlässig ab. Dadurch können Tests präzise prüfen, ob ein Hook mit der erwarteten Priorität und Argumentanzahl registriert wurde oder ob ein benutzerdefinierter Filter Eingabedaten korrekt transformiert hat.
<?php
declare(strict_types=1);
namespace WPPoland\Tests\Unit;
use Brain\Monkey;
use Brain\Monkey\Actions;
use Brain\Monkey\Filters;
use Brain\Monkey\Functions;
use PHPUnit\Framework\TestCase;
use WPPoland\Billing\InvoiceManager;
final class InvoiceManagerTest extends TestCase {
protected function setUp(): void {
parent::setUp();
Monkey\setUp();
}
protected function tearDown(): void {
Monkey\tearDown();
parent::tearDown();
}
public function test_invoice_creation_triggers_expected_action(): void {
Actions\expectDone('wppoland_invoice_generated')
->once()
->with(1042, 599.00);
Functions\expect('get_option')
->with('currency_code', 'EUR')
->andReturn('EUR');
$manager = new InvoiceManager();
$invoiceId = $manager->createInvoice(1042, 599.00);
$this->assertSame(1042, $invoiceId);
}
public function test_filter_modifies_invoice_total(): void {
Filters\expectApplied('wppoland_invoice_discount')
->once()
->with(500.00, 1042)
->andReturn(450.00);
$manager = new InvoiceManager();
$finalAmount = $manager->applyDiscounts(500.00, 1042);
$this->assertSame(450.00, $finalAmount);
}
}In diesem Test existieren weder WordPress noch eine Datenbank. Brain Monkey verwaltet den Lebenszyklus der Hooks vollständig im Arbeitsspeicher. Durch den Aufruf von Monkey\setUp() in setUp() und Monkey\tearDown() in tearDown() wird der Mock-Zustand zwischen den einzelnen Tests zuverlässig zurückgesetzt.
Mockery für komplexe Objekt-Abhängigkeiten
Brain Monkey arbeitet nahtlos mit Mockery zusammen, um neben prozeduralen Funktionen auch komplexe Klasseninstanzen zu mocken. In anspruchsvollen Enterprise-Plugins interagieren Klassen mit Repositories, HTTP-Clients und externen Payment-Gateways. Mockery erlaubt es Entwicklern, Test-Doubles zu definieren, die Interfaces exakt nachbilden und Methodenaufrufe verifizieren.
<?php
declare(strict_types=1);
namespace WPPoland\Tests\Unit;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use PHPUnit\Framework\TestCase;
use WPPoland\Gateways\PaymentClientInterface;
use WPPoland\Orders\OrderProcessor;
final class OrderProcessorTest extends TestCase {
use MockeryPHPUnitIntegration;
public function test_order_processes_payment_successfully(): void {
$paymentClient = Mockery::mock(PaymentClientInterface::class);
$paymentClient->shouldReceive('charge')
->once()
->with(120.50, 'EUR')
->andReturn((object) ['status' => 'success', 'transaction_id' => 'tx_9981']);
$processor = new OrderProcessor($paymentClient);
$result = $processor->process(101, 120.50, 'EUR');
$this->assertTrue($result->isSuccessful());
$this->assertSame('tx_9981', $result->getTransactionId());
}
}Dank des Traits MockeryPHPUnitIntegration fließen die Erwartungen von Mockery automatisch in die Assertion-Zählung von PHPUnit ein, und nicht erfüllte Erwartungen führen sofort zum Fehlschlagen des Tests.
Fortgeschrittene Mockery-Muster in der WordPress-Architektur
Enterprise-Lösungen setzen auf strukturierte Entwurfsmuster wie Service-Container und domänenspezifische Schnittstellen. Wenn ein Plugin mit einer externen Bestandsverwaltung synchronisiert wird, dürfen Tests keine realen Netzwerkanfragen absenden. Mockery bietet ausdrucksstarke Methoden zur Modellierung von Wiederholungsversuchen und Ausnahmezuständen.
<?php
declare(strict_types=1);
namespace WPPoland\Tests\Unit;
use Mockery;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use PHPUnit\Framework\TestCase;
use WPPoland\Http\ApiClientInterface;
use WPPoland\Sync\InventorySynchronizer;
use RuntimeException;
final class InventorySynchronizerTest extends TestCase {
use MockeryPHPUnitIntegration;
public function test_sync_retries_on_network_timeout(): void {
$apiClient = Mockery::mock(ApiClientInterface::class);
$apiClient->shouldReceive('fetchProductStock')
->twice()
->with('SKU-9901')
->andReturnValues([
new RuntimeException('Connection timed out'),
['sku' => 'SKU-9901', 'stock' => 45],
]);
$synchronizer = new InventorySynchronizer($apiClient);
$stock = $synchronizer->syncProduct('SKU-9901');
$this->assertSame(45, $stock);
}
public function test_sync_throws_exception_after_exhausting_retries(): void {
$apiClient = Mockery::mock(ApiClientInterface::class);
$apiClient->shouldReceive('fetchProductStock')
->times(3)
->with('SKU-9901')
->andThrow(new RuntimeException('Fatal gateway outage'));
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Fatal gateway outage');
$synchronizer = new InventorySynchronizer($apiClient, 3);
$synchronizer->syncProduct('SKU-9901');
}
}Damit lassen sich Timeouts, fehlerhafte API-Antworten und Ratenbegrenzungen zuverlässig testen, ohne eine echte Internetverbindung vorauszusetzen.
Integrationstests mit der Klasse WP_UnitTestCase
Während Unit-Tests isolierte Logik prüfen, wird der Code am Ende innerhalb von WordPress ausgeführt. Integrationstests verifizieren, wie Custom Post Types, Taxonomie-Abfragen, Benutzerrollen und Datenbankoptionen mit der tatsächlichen MySQL-Datenbank interagieren.
Die offizielle Test-Suite von WordPress stellt die Klasse WP_UnitTestCase bereit, die die Basisklasse von PHPUnit erweitert. Sie initialisiert die vollständige WordPress-Umgebung und stellt Verbindung zu einer Testdatenbank her. Ein entscheidender Vorteil sind die integrierten Objekt-Fabriken (factories), die das Erzeugen von Testbeiträgen, Begriffen und Benutzern automatisieren.
<?php
declare(strict_types=1);
namespace WPPoland\Tests\Integration;
use WP_UnitTestCase;
use WP_Query;
final class CustomQueryTest extends WP_UnitTestCase {
public function test_query_returns_published_enterprise_projects(): void {
$postId = $this->factory->post->create([
'post_type' => 'portfolio',
'post_status' => 'publish',
'post_title' => 'Enterprise Migration',
]);
$draftId = $this->factory->post->create([
'post_type' => 'portfolio',
'post_status' => 'draft',
'post_title' => 'Draft Migration',
]);
$query = new WP_Query([
'post_type' => 'portfolio',
'post_status' => 'publish',
'posts_per_page' => 10,
]);
$postIds = wp_list_pluck($query->posts, 'ID');
$this->assertContains($postId, $postIds);
$this->assertNotContains($draftId, $postIds);
}
}Dank $this->factory entfällt aufwendiges SQL-Seeding im Vorfeld der Tests, und Testdaten weisen stets konsistente relationale Verknüpfungen auf.
Datenbank-Transaktions-Rollbacks in Integrationstests
Integrationstests mit vielen Schreiboperationen verlangsamen den Testlauf erheblich, wenn Tabellen nach jedem Testfall geleert und neu migriert werden müssen. In frühen WordPress-Test-Setups führte das wiederholte Ausführen von DROP TABLE zu langen Laufzeiten von vielen Minuten.
WP_UnitTestCase löst dieses Problem durch systematisches Transaktionsmanagement. Vor jedem Testfall startet der Test-Runner eine MySQL-Transaktion. Der Test führt seine Schreiboperationen aus, erzeugt Beiträge und passt Optionen an. In der Tear-Down-Phase führt der Runner ein ROLLBACK aus, anstatt Datensätze einzeln zu löschen.
Dieses Rollback-Verfahren bietet klare Vorteile:
- Jeder Testfall startet mit einem sauberen, unberührten Datenbankzustand.
- Die Ausführungsgeschwindigkeit bleibt hoch, da Daten im Arbeitsspeicher verworfen werden.
- Auto-Increment-Werte für Primärschlüssel wachsen wie im Produktivbetrieb kontinuierlich weiter.
Auf modernen Installationen mit MySQL 8.0 oder MariaDB 10.11 und InnoDB-Tabellen stellen Transaktions-Rollbacks eine performante und robuste Testbasis dar.
Lokale Testumgebungen mit wp-env
Früher erforderte das Einrichten einer lokalen Testumgebung komplexe Shell-Skripte, lokale Datenbankserver und SVN-Checkouts von WordPress Develop. Das offizielle Paket @wordpress/env (wp-env) standardisiert diesen Vorgang vollständig auf Docker-Basis.
Die Konfigurationsdatei .wp-env.json im Plugin-Stammverzeichnis steuert die Umgebung:
{
"core": "WordPress/WordPress#master",
"plugins": [ "." ],
"config": {
"WP_DEBUG": true,
"SCRIPT_DEBUG": true
},
"env": {
"tests": {
"config": {
"WP_DEBUG": true
}
}
}
}Mit dem Befehl wp-env start werden Webserver und MySQL-Container gestartet. Integrationstests lassen sich anschließend direkt im dedizierten Testcontainer ausführen:
npx wp-env run tests-cli vendor/bin/phpunitwp-env erzeugt eine isolierte Testdatenbank, sodass Entwicklungsdaten bei Testdurchläufen niemals manipuliert werden.
Testen von REST-API-Endpunkten und Schemavalidierung
Benutzerdefinierte REST-API-Endpunkte bilden das Rückgrat moderner Architekturen, mobiler Apps und dynamischer Gutenberg-Blöcke. Integrationstests für WP_REST_Controller stellen sicher, dass Routen, Zugriffsberechtigungen, Eingabevalidierungen und HTTP-Statuscodes wie vorgesehen funktionieren.
WP_UnitTestCase bindet den internen WP_REST_Server direkt ein. Anfragen werden ohne Netzwerk-Overhead prozessintern verarbeitet.
<?php
declare(strict_types=1);
namespace WPPoland\Tests\Integration;
use WP_UnitTestCase;
use WP_REST_Request;
use WP_REST_Server;
final class OrdersEndpointTest extends WP_UnitTestCase {
private WP_REST_Server $server;
protected function setUp(): void {
parent::setUp();
global $wp_rest_server;
$this->server = $wp_rest_server = new WP_REST_Server();
do_action('rest_api_init');
}
public function test_unauthenticated_request_is_rejected(): void {
$request = new WP_REST_Request('POST', '/wppoland/v1/orders');
$request->set_header('content-type', 'application/json');
$request->set_body(wp_json_encode(['item_id' => 42]));
$response = $this->server->dispatch($request);
$this->assertSame(401, $response->get_status());
$data = $response->get_data();
$this->assertSame('rest_cannot_create', $data['code']);
}
public function test_authorized_user_creates_order_successfully(): void {
$adminId = $this->factory->user->create(['role' => 'administrator']);
wp_set_current_user($adminId);
$request = new WP_REST_Request('POST', '/wppoland/v1/orders');
$request->set_header('content-type', 'application/json');
$request->set_body(wp_json_encode([
'item_id' => 42,
'quantity' => 2,
'amount' => 199.50,
]));
$response = $this->server->dispatch($request);
$this->assertSame(201, $response->get_status());
$data = $response->get_data();
$this->assertArrayHasKey('order_id', $data);
$this->assertSame(199.50, $data['amount']);
}
}Durch das Testen von REST-Controllern wird sichergestellt, dass fehlerhafte Nutzdaten abgewiesen werden, bevor sie Datenbankschichten erreichen.
Validierung von Datenbankschemata und Migrationen
Komplexe Plugins erstellen oft eigene Datenbanktabellen für Logs, Warteschlangen oder Caching-Prozesse. Um sicherzustellen, dass Tabellenerstellungen, Spaltenanpassungen und Indexaktualisierungen ohne Datenverlust ablaufen, sind dedizierte Migrationstests unerlässlich.
In diesen Tests wird geprüft, ob dbDelta() reibungslos durchläuft und ob benutzerdefinierte SQL-Anpassungen atomar ausgeführt werden.
<?php
declare(strict_types=1);
namespace WPPoland\Tests\Integration;
use WP_UnitTestCase;
use WPPoland\Database\MigrationManager;
final class MigrationManagerTest extends WP_UnitTestCase {
public function test_migration_creates_custom_audit_table(): void {
global $wpdb;
$tableName = $wpdb->prefix . 'wppoland_audit_log';
$wpdb->query("DROP TABLE IF EXISTS {$tableName}");
$manager = new MigrationManager();
$manager->runMigrations();
$tableExists = $wpdb->get_var(
$wpdb->prepare("SHOW TABLES LIKE %s", $tableName)
);
$this->assertSame($tableName, $tableExists);
$indices = $wpdb->get_results("SHOW INDEX FROM {$tableName}", ARRAY_A);
$indexNames = wp_list_pluck($indices, 'Key_name');
$this->assertContains('idx_created_at', $indexNames);
}
}Strukturierte Migrationstests verhindern Ausfälle bei Versions-Upgrades in der Produktionsumgebung.
Akzeptanztests und Browser-Automatisierung mit wp-browser
Unit- und Integrationstests laufen über die Befehlszeile, doch manche Funktionen erfordern eine Überprüfung im tatsächlichen Browserkontext. Ob Optionen im Administrationsbereich nach dem Klick auf Speichern intakt bleiben oder ob Validierungsmeldungen im Checkout korrekt erscheinen, prüfen Akzeptanztests.
wp-browser bringt das Codeception-Framework in das WordPress-Ökosystem. Es bietet maßgeschneiderte Module:
- WPDb: Ermöglicht direkte Datenbankabfragen und Daten-Seeding ohne PHPUnit-Bootstrapping.
- WPLoader: Lädt die WordPress-Laufzeitumgebung für funktionale Modultests.
- WPWebDriver: Steuert Chrome oder Firefox über Selenium oder Chromedriver für echte Benutzerinteraktionen.
<?php
// Akzeptanztest mit wp-browser
$I = new AcceptanceTester($scenario);
$I->wantTo('verify admin settings save correctly');
$I->loginAsAdmin();
$I->amOnAdminPage('admin.php?page=wppoland-settings');
$I->fillField('#api_key', 'live_secret_key_12345');
$I->click('Save Changes');
$I->see('Settings saved successfully', '.notice-success');
$I->seeInDatabase('wp_options', [
'option_name' => 'wppoland_api_key',
'option_value' => 'live_secret_key_12345',
]);Akzeptanztests prüfen den vollständigen Technologie-Stack und schaffen maximale Verlässlichkeit vor dem Deployment.
Gutenberg-Blöcke testen mit Jest und Playwright
Moderne WordPress-Lösungen vereinen ein PHP-Backend mit einer React-basierten Editor-Oberfläche. Daher umfasst eine umfassende Qualitätssicherung auch JavaScript-Testwerkzeuge.
Das Paket @wordpress/scripts liefert vorkonfigurierte Testumgebungen. Zustandslose JavaScript-Hilfsfunktionen und Blockattribute werden mit Jest getestet, während End-to-End-Abläufe im Block-Editor mit Playwright automatisiert werden.
Blockattribute und Transformationen mit Jest prüfen
Jest führt schnelle Unit-Tests für React-Komponenten und reine Hilfsfunktionen aus. Die Validierung von Blockattributen und Abwärtskompatibilitäten gehört zu den Kernaufgaben von Jest.
import { cleanInvoiceAttributes } from '../src/utils';
describe('Invoice block attribute sanitization', () => {
it('sets default tax rate when invalid rate is provided', () => {
const input = { taxRate: -5, clientName: 'WPPoland' };
const result = cleanInvoiceAttributes(input);
expect(result.taxRate).toBe(23);
expect(result.clientName).toBe('WPPoland');
});
it('preserves valid vat rates', () => {
const input = { taxRate: 8, clientName: 'WPPoland' };
const result = cleanInvoiceAttributes(input);
expect(result.taxRate).toBe(8);
});
});End-to-End-Blocktests mit Playwright
Playwright steuert einen Headless-Browser, öffnet den Gutenberg-Editor, fügt benutzerdefinierte Blöcke ein und prüft DOM-Elemente sowie Sidebar-Einstellungen.
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
test.describe('Invoice summary block', () => {
test.beforeEach(async ({ admin }) => {
await admin.createNewPost();
});
test('inserts block and updates total in sidebar', async ({ editor, page }) => {
await editor.insertBlock({ name: 'wppoland/invoice-summary' });
const block = page.locator('[data-type="wppoland/invoice-summary"]');
await expect(block).toBeVisible();
await page.fill('input[aria-label="Invoice total"]', '1250');
await expect(block.locator('.invoice-formatted-amount')).toHaveText('1,250.00 EUR');
});
});Playwright-Tests fangen visuelle Fehler und UI-Brüche ab, die von Backend-Tests nicht erkannt werden können.
Statische Codeanalyse mit PHPStan neben PHPUnit
Statische Codeanalyse ergänzt dynamische Unit-Tests. Während Tests überprüfen, ob konkrete Eingaben die erwarteten Ausgaben liefern, prüft PHPStan die gesamte Codebasis auf Typinkonsistenzen, toten Code und ungültige Methodensignaturen, bevor überhaupt ein Testfall gestartet wird.
Mit szepeviktor/phpstan-wordpress stehen vollständige Typdefinitionen für den WordPress-Core bereit. In Kombination mit PHPUnit wird sichergestellt, dass auch die Testdateien selbst typensicher aufgebaut sind.
Konfiguration in phpstan.neon.dist:
includes:
- vendor/szepeviktor/phpstan-wordpress/extension.neon
parameters:
level: 8
paths:
- src
- tests
excludePaths:
- tests/Fixtures/*
bootstrapFiles:
- tests/bootstrap-unit.php
checkMissingIterableValueType: falseDer Aufruf vendor/bin/phpstan analyse in der CI-Pipeline verhindert typische Laufzeitfehler bereits vor dem Testdurchlauf.
Testabdeckung und Qualitätsschwellen
Das Erfassen der Testabdeckung gibt Aufschluss über ungetestete Pfade. Das Erreichen einer theoretischen 100-Prozent-Quote ist jedoch selten sinnvoll, wenn dadurch triviale Getter getestet werden, anstatt komplexe Domänenregeln abzusichern.
In modernen Entwicklungsteams liegt der Zielwert für geschäftskritische Logik meist zwischen 80 und 85 Prozent. Dieser Wert wird in GitHub Actions mithilfe von PCOV oder Xdebug 3 überwacht.
php -d pcov.enabled=1 vendor/bin/phpunit --testsuite=unit --coverage-text --coverage-html=coverage-reportDie gezielte Konfiguration des Coverage-Blocks in der PHPUnit-Konfiguration stellt sicher, dass externe Bibliotheken und View-Templates nicht in die Berechnung einfließen.
CI-Pipeline mit GitHub Actions auf PHP 8.2 und 8.3
Automatisierte Tests entfalten ihren vollen Nutzen, wenn sie kontinuierlich bei jedem Commit und Pull Request ausgeführt werden. Eine automatisierte Pipeline garantiert, dass fehlerhafter Code niemals in den Hauptzweig gelangt.
Ein zeitgemäßer GitHub Actions Workflow prüft den Code unter PHP 8.2 und 8.3 mit MySQL 8.0:
name: Test Suite
on:
push:
branches: [ master, main ]
pull_request:
branches: [ master, main ]
jobs:
unit-tests:
name: PHPUnit on PHP ${{ matrix.php }}
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.2', '8.3']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
tools: composer:v2
- name: Install Composer dependencies
run: composer install --prefer-dist --no-progress
- name: Run unit test suite
run: vendor/bin/phpunit --testsuite=unit
integration-tests:
name: Integration Tests on PHP ${{ matrix.php }}
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress_test
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
matrix:
php: ['8.2', '8.3']
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mysqli, pdo_mysql
coverage: none
tools: composer:v2
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run integration tests
env:
WP_DB_HOST: 127.0.0.1
WP_DB_NAME: wordpress_test
WP_DB_USER: root
WP_DB_PASS: root
run: vendor/bin/phpunit --testsuite=integrationWeil Unit-Tests getrennt von Integrationstests laufen, erhalten Entwickler bereits nach wenigen Sekunden Rückmeldung zu ihren Änderungen.
Verzeichnisstruktur für professionelle Test-Suites
Eine strukturierte Ablage trennt Unit-, Integrations- und Akzeptanztests sauber voneinander.
my-enterprise-plugin/
├── .github/
│ └── workflows/
│ └── tests.yml
├── .wp-env.json
├── composer.json
├── phpunit.xml.dist
├── src/
│ ├── Billing/
│ ├── Gateways/
│ └── Plugin.php
└── tests/
├── bootstrap-unit.php
├── bootstrap-integration.php
├── Unit/
│ ├── Billing/
│ └── Gateways/
├── Integration/
│ ├── DatabaseQueryTest.php
│ └── HookRegistrationTest.php
└── Fixtures/
└── sample-invoice.jsonKonfiguration von phpunit.xml.dist
Die zentrale XML-Konfigurationsdatei trennt die Testsuiten:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="tests/bootstrap-unit.php"
colors="true"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
requireCoverageMetadata="false"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
failOnWarning="true">
<testsuites>
<testsuite name="unit">
<directory suffix="Test.php">tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory suffix="Test.php">tests/Integration</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
</phpunit>Entwickler können so blitzschnell reine Unit-Tests im lokalen Arbeitsablauf ausführen.
Enterprise-Testmatrix und Wartungs-Checkliste
Eine nachhaltige Testkultur erfordert klare organisatorische Leitplanken.
| Testschicht | Werkzeuge | Ausführungszeit | Datenbankanforderung | Hauptziel |
|---|---|---|---|---|
| Unit-Tests | PHPUnit 10/11, Brain Monkey, Mockery | Millisekunden | Keine (Nur RAM) | Reine Domänenlogik, Berechnungen, Filter |
| Integrationstests | WP_UnitTestCase, wp-env | Sekunden | MySQL-Testdatenbank | WP_Query-Abfragen, Datenbankschreibvorgänge, Hook-Prioritäten |
| Akzeptanztests | wp-browser, Codeception | Sekunden bis Minuten | Headless-Browser + DB | Administrationsformulare, Benutzerrechte, Bestellprozesse |
| Block-Tests (e2e) | Playwright, @wordpress/scripts | Minuten | Docker-Umgebung | Gutenberg-Editor, Sidebar-Steuerung, Block-Markup |
Empfehlungen für die laufende Pflege:
- Isolation einhalten: Niemals Datenbankfunktionen in Unit-Tests aufrufen.
- Transaktions-Rollbacks nutzen: Schnelle Integrationstests durch Rückrollen von Schreiboperationen sicherstellen.
- Automatisierung im CI-Workflow: Jeden Commit unter PHP 8.2 und 8.3 prüfen.
- Block-Transformationen testen: Gutenberg-Updates mit Playwright gegen unerwartete Layout-Änderungen absichern.
- Refactoring für Testbarkeit: Logik konsequent von Template-Dateien entkoppeln.
Erfahren Sie mehr über moderne Architekturstandards und professionelle WordPress-Entwicklung bei WPPoland.
Strategien für Mehrsprachigkeit und Third-Party-Kompatibilität
In internationalen Projekten interagieren individuelle Plugins oft mit Mehrsprachigkeitslösungen wie Polylang oder WPML sowie komplexen E-Commerce-Modulen auf Basis von WooCommerce. Ohne gezielte Tests für Übersetzungsfilter kann es passieren, dass Texte im Warenkorb oder in transaktionalen E-Mails plötzlich in der falschen Sprache ausgeliefert werden.
Bei Unit-Tests für Lokalisierungsmodule empfiehlt es sich, Übersetzungsfunktionen über Brain Monkey zu simulieren und sicherzustellen, dass Platzhalter und Kontextwerte erhalten bleiben:
<?php
declare(strict_types=1);
namespace WPPoland\Tests\Unit;
use Brain\Monkey\Functions;
use PHPUnit\Framework\TestCase;
use WPPoland\I18n\Translator;
final class TranslatorTest extends TestCase {
public function test_translates_message_with_placeholder(): void {
Functions\expect('__')
->once()
->with('Order #%d completed', 'wppoland')
->andReturn('Bestellung #%d abgeschlossen');
$translator = new Translator();
$message = $translator->formatOrderMessage(105);
$this->assertSame('Bestellung #105 abgeschlossen', $message);
}
}In Integrationstests validieren wir zusätzlich die Filter für locale und stellen sicher, dass ein dynamischer Sprachwechsel während einer Benutzersitzung keine veralteten Übersetzungs-Caches hinterlässt.
Performance-Profiling und Erkennung langsamer Testfälle
Mit wachsender Codebasis wächst auch die Anzahl der Testdateien. Eine schleichende Verlangsamung der Testläufe beeinträchtigt den Entwicklungsfluss spürbar. PHPUnit bietet integrierte Profiling-Funktionen, um zeitintensive Testmethoden gezielt aufzuspüren.
Durch den Aufruf von --profile lässt sich schnell ermitteln, welche Tests unerwartet lange Blockierzeiten aufweisen:
vendor/bin/phpunit --testsuite=integration --profileBenötigt ein Integrationstest mehr als 200 Millisekunden, liegt das meist an fehlenden Indizes in temporären MySQL-Tabellen, unzähligen rekursiven Core-Hooks oder mehrfachem ungedeckeltem Parsen von XML-Dateien ohne Cache.
Fazit und praktische Empfehlungen für Entwicklungsteams
Automatisierte Tests sind im modernen WordPress-Ökosystem das Fundament für planbare Wartung und fehlerfreie Releases. Indem Entwicklerteams Unit-Tests für schnelle Feedbackschleifen und Integrationstests für datenbanknahe Workflows kombinieren, minimieren sie kostspielige Produktionsausfälle nachhaltig.
Beginnen Sie die Implementierung schrittweise: Isolieren Sie zuerst kritische Berechnungslogik mit Brain Monkey und Mockery, führen Sie strukturierte WP_UnitTestCase-Klassen für Datenbankabfragen ein und verankern Sie die Testausführung fest in Ihrer GitHub Actions Pipeline.







