Automatyzacja testów w ekosystemie WordPressa przeszła długą drogę: od opcjonalnego dodatku do absolutnego standardu w projektach produkcyjnych. Przez wiele lat zespoły deweloperskie opierały weryfikację kodu na testach manualnych: odświeżaniu okna przeglądarki, klikaniu formularzy w panelu wp-admin i ręcznym sprawdzaniu bazy danych po wprowadzeniu poprawek. Taki model załamuje się w momencie, gdy system rośnie, integrując custom post types, niestandardowe taksonomie, endpointy REST API i rozbudowaną logikę biznesową. Drobny błąd w rzadko odwiedzanym filtrze potrafi umknąć uwadze aż do momentu, gdy zamówienia w sklepie przestają przechodzić.
Nowoczesna strategia testowania rozdziela kontrolę jakości na kilka komplementarnych poziomów. U podstawy leżą testy jednostkowe, które wykonują się w milisekundach bez ładowania środowiska WordPressa i bez połączenia z bazą danych. Wyżej znajdują się testy integracyjne, które sprawdzają interakcje z bazą MySQL i funkcjami rdzenia. Na samym szczycie działają testy akceptacyjne i przeglądarkowe. Zbudowanie niezawodnego potoku testowego w 2026 roku wymaga precyzyjnego rozgraniczenia tych warstw oraz wdrożenia takich narzędzi jak PHPUnit 10 i 11, Brain Monkey, Mockery, wp-browser oraz wp-env.
Fundamenty testów jednostkowych i zasada pełnej izolacji
Prawdziwy test jednostkowy bada pojedynczy element kodu, czyli funkcję lub metodę klasy, w pełnej izolacji od zależności zewnętrznych. W WordPressie izolacja jest wyzwaniem architektonicznym, ponieważ klasyczny kod bazuje na funkcjach globalnych, takich jak get_post(), get_option() czy apply_filters(). Wywołanie takich funkcji w zwykłym środowisku PHP bez załadowanego rdzenia WordPressa natychmiast kończy się błędem krytycznym o braku zdefiniowanej funkcji.
Uruchamianie testów jednostkowych poprzez ładowanie całej bazy danych WordPressa dla każdego przypadku testowego niszczy pętlę zwrotną. Kiedy zestaw testów wykonuje się dziesięć minut, programiści przestają uruchamiać go przed wysłaniem zmian do repozytorium. Prawdziwe testy jednostkowe muszą działać w kilka sekund. Aby to osiągnąć, logika biznesowa musi zostać odseparowana od bazy danych i zmiennych globalnych za pomocą atrap oraz mockowania funkcji.
Tworzenie testowalnego kodu w WordPressie zaczyna się od architektury. Logika domenowa nie powinna być bezpośrednio powiązana z API WordPressa. Zamiast wywoływać update_post_meta() wewnątrz skomplikowanych kalkulacji rabatowych, wartości należy przekazać jako argumenty czystej metody, a wynik zwrócić. Wywołania funkcji WordPressa należy wyizolować w adapterach lub serwisach, co pozwala frameworkom testowym na swobodne symulowanie ich zachowania.
Izolacja za pomocą Brain Monkey oraz asercje hooków i filtrów
Brain Monkey to podstawowe narzędzie do testowania kodu WordPressa w całkowitej izolacji. Zaprojektowane specjalnie z myślą o mockowaniu funkcji, akcji i filtrów bez konieczności ładowania środowiska WordPressa, pozwala na błyskawiczne wykonywanie testów.
Brain Monkey przejmuje wywołania add_action(), add_filter(), do_action() oraz apply_filters(). Dzięki temu testy mogą precyzyjnie weryfikować, czy dany hook został zarejestrowany z oczekiwanym priorytetem i liczbą argumentów, albo czy filtr poprawnie przetworzył przekazany zestaw danych.
<?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', 'PLN')
->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);
}
}W tym teście WordPress ani baza danych w ogóle nie istnieją. Brain Monkey zarządza cyklem życia hooków w pamięci procesu. Wywołanie Monkey\setUp() w metodzie setUp() oraz Monkey\tearDown() w tearDown() gwarantuje czysty stan mocków pomiędzy kolejnymi testami.
Mockery i modelowanie złożonych zależności obiektowych
Brain Monkey współpracuje z biblioteką Mockery, umożliwiając mockowanie obiektów obok mockowania funkcji proceduralnych. W rozbudowanych wtyczkach klasy komunikują się z repozytoriami, klientami HTTP czy zewnętrznymi bramkami płatności. Mockery pozwala definiować testowe atrapy, które naśladują interfejsy i weryfikują wywołania metod.
<?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());
}
}Dzięki traitowi MockeryPHPUnitIntegration asercje Mockery są automatycznie uwzględniane w liczniku asercji PHPUnit, a niespełnione oczekiwania natychmiast oznaczają test jako niezaliczony.
Zaawansowane wzorce Mockery w architekturze WordPressa
W architekturze korporacyjnej spotykamy wzorce takie jak repozytoria, kontenery wstrzykiwania zależności czy serwisy asynchroniczne. Gdy testujemy wtyczkę integrującą się z zewnętrznym API magazynowym, nie możemy polegać na wywołaniach sieciowych. Mockery umożliwia symulowanie sekwencji zdarzeń, ponawiania prób oraz wyjątków.
<?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');
}
}Taki test daje pewność, że obsługa błędów sieciowych i mechanizmy ponawiania zapytań działają bezbłędnie bez konieczności wysyłania rzeczywistych żądań HTTP.
Testy integracyjne z klasą WP_UnitTestCase
O ile testy jednostkowe weryfikują wyizolowaną logikę, o tyle kod wtyczek WordPressa ostatecznie wykonuje się w środowisku CMS. Testy integracyjne sprawdzają, jak niestandardowe typy wpisów, zapytania taksonomiczne, uprawnienia użytkowników i opcje współdziałają z silnikiem bazy danych.
Oficjalny pakiet testowy WordPressa dostarcza klasę WP_UnitTestCase, która rozszerza bazową klasę testową PHPUnit. WP_UnitTestCase uruchamia pełne środowisko WordPressa i łączy się z dedykowaną testową bazą danych. Udostępnia także fabryki obiektów (factories), które automatyzują tworzenie wpisów, użytkowników i terminów.
<?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);
}
}Dzięki $this->factory tworzenie danych testowych wymaga zaledwie kilku linii kodu, a wygenerowane obiekty posiadają kompletne, spójne relacje w bazie.
Wycofywanie transakcji w bazie danych podczas testów
Testy integracyjne wykonujące operacje zapisu w bazie danych mogą gwałtownie zwolnić, jeśli tabele muszą być czyszczone i migrowane przed każdym testem. We wczesnych wersjach środowisk testowych każda metoda uruchamiała operacje DROP TABLE, co wydłużało czas wykonywania do kilkunastu minut.
Klasa WP_UnitTestCase rozwiązuje ten problem poprzez zarządzanie transakcjami MySQL. Przed wykonaniem metody testowej runner rozpoczyna transakcję bazodanową. Test wykonuje zapytania, tworzy wpisy, dodaje metadane i aktualizuje opcje. Po zakończeniu asercji, w fazie sprzątania (tearDown), runner wykonuje instrukcję ROLLBACK, zamiast ręcznie usuwać pojedyncze rekordy.
Mechanizm wycofywania transakcji gwarantuje, że:
- Każdy test rozpoczyna pracę z czystym stanem bazy danych.
- Wykonanie testów pozostaje szybkie, ponieważ dane są odrzucane w pamięci serwera MySQL bez przebudowywania struktur tabel.
- Wartości auto-increment kluczy głównych rosną w sposób zgodny z pracą systemu produkcyjnego.
W przypadku silnika InnoDB na nowoczesnych instalacjach MySQL 8.0 i MariaDB 10.11 wycofywanie transakcji zapewnia stabilne i powtarzalne środowisko testowe.
Lokalne środowisko testowe oparte na wp-env
Konfiguracja lokalnego środowiska testowego dawniej wymagała ręcznych skryptów powłoki, instalowania lokalnego serwera MySQL i pobierania repozytorium WordPress Develop przez Subversion. Oficjalne narzędzie @wordpress/env (wp-env) standaryzuje ten proces przy użyciu Dockera.
Plik konfiguracyjny .wp-env.json w głównym katalogu wtyczki definiuje całe środowisko:
{
"core": "WordPress/WordPress#master",
"plugins": [ "." ],
"config": {
"WP_DEBUG": true,
"SCRIPT_DEBUG": true
},
"env": {
"tests": {
"config": {
"WP_DEBUG": true
}
}
}
}Polecenie wp-env start uruchamia kontenery Dockera dla serwera WWW i bazy danych MySQL. Aby wykonać testy integracyjne w dedykowanym kontenerze testowym, wystarczy uruchomić:
npx wp-env run tests-cli vendor/bin/phpunitNarzędzie tworzy niezależną instancję bazy danych przeznaczoną wyłącznie do testów, chroniąc lokalne dane deweloperskie przed przypadkową modyfikacją.
Testowanie punktów końcowych REST API i walidacja schematu
Punkty końcowe WordPress REST API stanowią fundament nowoczesnych aplikacji headless, aplikacji mobilnych oraz bloków edytora. Testy integracyjne dla kontrolerów WP_REST_Controller weryfikują rejestrację tras, uprawnienia dostępu, walidację schematu JSON i kody odpowiedzi HTTP.
Klasa WP_UnitTestCase współpracuje bezpośrednio z instancją WP_REST_Server. Zamiast wykonywać zapytania przez sieć, żądania są przekazywane bezpośrednio do wewnętrznego serwera, co drastycznie skraca czas wykonania.
<?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']);
}
}Testowanie kontrolerów REST gwarantuje, że nieprawidłowe dane wejściowe zostaną odrzucone zanim dotrą do bazy danych.
Weryfikacja migracji bazy danych i niestandardowych tabel
Zaawansowane wtyczki tworzą dedykowane tabele bazy danych do obsługi logów audytowych, kolejek zadań czy danych telemetrycznych. Weryfikacja, czy tworzenie tabel, modyfikacje kolumn i dodawanie indeksów przebiegają bez utraty danych, wymaga dedykowanych testów migracji.
W testach migracji sprawdzamy, czy funkcja dbDelta() wykonuje się poprawnie oraz czy niestandardowe skrypty SQL wprowadzają pożądane zmiany w strukturze.
<?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);
}
}Testy migracji schematu eliminują ryzyko awarii bazy danych podczas aktualizacji wtyczki na środowisku produkcyjnym.
Testy akceptacyjne i przeglądarkowe z użyciem wp-browser
Testy jednostkowe i integracyjne działają w wierszu poleceń, jednak pewne zachowania wymagają sprawdzenia w kontekście przeglądarki. Weryfikacja, czy ustawienia w panelu administracyjnym zapisują się poprawnie lub czy formularz zamówienia prawidłowo przetwarza dane po stronie klienta, wymaga testów akceptacyjnych.
Narzędzie wp-browser integruje framework Codeception z ekosystemem WordPressa. Oferuje moduły stworzone z myślą o platformie:
- WPDb: Umożliwia inspekcję i zasilanie bazy danych bez konieczności uruchamiania pełnego środowiska PHPUnit.
- WPLoader: Inicjalizuje WordPressa w testach funkcjonalnych.
- WPWebDriver: Automatyzuje przeglądarki Chrome i Firefox przy użyciu Selenium lub Chromedriver.
<?php
// Test akceptacyjny w 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',
]);Testy akceptacyjne badają cały stos technologiczny, dając najwyższy poziom pewności przed publikacją wdrożenia.
Testowanie bloków Gutenberg za pomocą Jest i Playwright
Współczesny WordPress łączy backend w PHP z interfejsem edytora opartym na bibliotece React. Kompleksowe testowanie bloków Gutenberg wymaga zastosowania narzędzi ze środowiska JavaScript.
Pakiet @wordpress/scripts dostarcza ujednoliconą konfigurację testową. Czysta logika i transformacje bloków są badane w środowisku Jest, natomiast interakcje użytkownika wewnątrz edytora weryfikuje Playwright.
Testowanie atrybutów i transformacji bloków w Jest
Jest uruchamia testy jednostkowe dla komponentów React oraz czystych funkcji pomocniczych. Sanitizacja atrybutów oraz mechanizmy deprecacji powinny być objęte testami 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);
});
});Testy end-to-end w edytorze bloków za pomocą Playwright
Playwright kontroluje bezgłową przeglądarkę, ładuje edytor Gutenberg, wstawia niestandardowe bloki i sprawdza stan drzewa DOM oraz opcje paska bocznego.
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');
});
});Testy Playwright wychwytują błędy renderowania i niespójności interfejsu, których nie wykryją testy jednostkowe w PHP.
Integracja PHPStan i analiza statyczna obok PHPUnit
Analiza statyczna stanowi naturalne dopełnienie testów jednostkowych. Podczas gdy testy badają konkretne scenariusze wejścia i wyjścia, narzędzia analizy statycznej, takie jak PHPStan, weryfikują spójność typów, martwy kod i poprawne sygnatury metod w całym projekcie jeszcze przed uruchomieniem testów.
W środowisku WordPressa rozszerzenie szepeviktor/phpstan-wordpress dostarcza kompletne definicje typów rdzenia. W połączeniu z PHPUnit analiza statyczna gwarantuje, że same pliki testowe nie zawierają błędów typowania czy odwołań do nieistniejących metod.
Przykładowa konfiguracja 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: falseUruchamianie vendor/bin/phpstan analyse w procesie CI eliminuje całe klasy błędów typowania przed rozpoczęciem testów dynamicznych.
Raportowanie pokrycia kodu i egzekwowanie progów jakości
Monitorowanie wskaźnika pokrycia kodu (code coverage) pozwala zidentyfikować nieprzetestowane ścieżki i gałęzie warunkowe. Dążenie do sztucznego wyniku 100% nie ma jednak sensu, jeśli prowadzi do testowania trywialnych metod zamiast skomplikowanych reguł biznesowych.
W nowoczesnych zespołach standardem jest ustalenie progu pokrycia na poziomie 80% do 85% dla logiki domenowej oraz egzekwowanie go w potoku CI przy użyciu Xdebug 3 lub rozszerzenia PCOV.
# Generowanie raportu pokrycia z rozszerzeniem PCOV
php -d pcov.enabled=1 vendor/bin/phpunit --testsuite=unit --coverage-text --coverage-html=coverage-reportOdpowiednia konfiguracja sekcji coverage w pliku XML gwarantuje, że raport uwzględnia wyłącznie właściwy kod aplikacji, pomijając biblioteki zewnętrzne i szablony widoków.
Potok Continuous Integration w GitHub Actions na PHP 8.2 i 8.3
Automatyczne testy przynoszą realną wartość tylko wtedy, gdy są uruchamiane przy każdym commicie i pull requeście. Potok ciągłej integracji gwarantuje, że żaden kod nie zostanie dołączony do głównej gałęzi bez zaliczenia testów na wszystkich wspieranych wersjach PHP.
Nowoczesny workflow GitHub Actions testuje kod na PHP 8.2 oraz PHP 8.3 z użyciem usług 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=integrationOddzielenie zadania testów jednostkowych od testów integracyjnych sprawia, że testy jednostkowe zwracają wynik już w kilkanaście sekund po wysłaniu commita.
Struktura katalogów zestawu testów w projekcie wtyczki
Przejrzysta struktura katalogów jednoznacznie rozdziela pliki testów jednostkowych, integracyjnych i funkcjonalnych, zapobiegając pomyłkom dotyczącym zależności od bazy danych.
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.jsonKonfiguracja pliku phpunit.xml.dist
Plik phpunit.xml.dist definiuje niezależne zestawy testów:
<?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>Deweloper może wygodnie uruchamiać szybkie testy jednostkowe podczas pisania kodu i pełny zestaw przed wysłaniem pull requesta.
Matryca testowa i lista kontrolna wdrożenia
Wdrożenie testów automatycznych wymaga konsekwencji. Zespoły inżynierskie powinny regularnie weryfikować architekturę testów według ustalonych kryteriów.
| Poziom testowania | Narzędzia | Szybkość wykonania | Wymóg bazy danych | Główny cel testu |
|---|---|---|---|---|
| Testy jednostkowe | PHPUnit 10/11, Brain Monkey, Mockery | Milisekundy | Brak (Pamięć podręczna) | Logika biznesowa, kalkulacje, transformacje filtrów |
| Testy integracyjne | WP_UnitTestCase, wp-env | Sekundy | Testowa baza MySQL | Zapytania WP_Query, zapis do bazy, priorytety hooków |
| Testy akceptacyjne | wp-browser, Codeception | Sekundy do minut | Przeglądarka headless + DB | Formularze administracyjne, uprawnienia, procesy zamówień |
| Testy bloków e2e | Playwright, @wordpress/scripts | Minuty | Środowisko Docker | Renderowanie bloków Gutenberg, opcje w panelu bocznym |
Dobre praktyki utrzymania zestawu testowego:
- Czystość testów jednostkowych: Nigdy nie wywołuj funkcji bazodanowych w katalogu testów jednostkowych.
- Wycofywanie transakcji: Zapewnij maksymalną szybkość testów integracyjnych poprzez rollback operacji SQL.
- Automatyzacja w CI: Uruchamiaj testy na PHP 8.2 i 8.3 przy każdym wypchnięciu kodu do repozytorium.
- Weryfikacja bloków: Sprawdzaj transformacje i deprecacje bloków Gutenberg przy pomocy Jest i Playwright.
- Refaktoryzacja kodu: Oddzielaj czystą logikę biznesową od szablonów renderowania WordPressa.
Więcej o standardach inżynieryjnych dowiesz się, sprawdzając programowanie WordPress w WPPoland.
Strategie testowania wielojęzyczności i kompatybilności rozszerzeń
W projektach międzynarodowych wtyczki często wchodzą w interakcje z systemami wielojęzycznymi, takimi jak Polylang czy WPML, oraz zaawansowanymi rozwiązaniami e-commerce opartymi na WooCommerce. Brak dedykowanych testów dla filtrów tłumaczeń potrafi prowadzić do sytuacji, w której teksty w koszyku lub wiadomościach transakcyjnych wyświetlają się w niewłaściwym języku.
Podczas pisania testów jednostkowych dla modułów lokalizacyjnych warto symulować funkcje tłumaczące za pomocą Brain Monkey, weryfikując czy zwracane łańcuchy znaków zachowują odpowiedni kontekst tekstowy.
<?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('Zamówienie #%d zrealizowane');
$translator = new Translator();
$message = $translator->formatOrderMessage(105);
$this->assertSame('Zamówienie #105 zrealizowane', $message);
}
}W testach integracyjnych weryfikujemy natomiast rejestrację łańcuchów w filtrach locale oraz sprawdzamy, czy przełączanie języka użytkownika w trakcie sesji nie powoduje zanieczyszczenia pamięci podręcznej zlokalizowanych obiektów.
Debugowanie testów i profilowanie wolnych przypadków testowych
Gdy pakiet testów rozrasta się do setek lub tysięcy plików, czas wykonania staje się czynnikiem decydującym o komforcie pracy programisty. PHPUnit udostępnia wbudowane mechanizmy raportowania najwolniejszych testów, które wskazują metody wymagające natychmiastowej optymalizacji.
Uruchomienie parametru --profile lub skonfigurowanie raportowania czasu w pliku konfiguracyjnym pozwala szybko wykryć przypadki testowe, które przypadkowo nawiązują połączenia sieciowe lub wykonują kosztowne operacje na dysku:
vendor/bin/phpunit --testsuite=integration --profileJeżeli dany test integracyjny wykonuje się dłużej niż 200 milisekund, najczęstszą przyczyną jest brak indeksu w tymczasowej tabeli MySQL, zbyt głębokie drzewo zagnieżdżonych hooków w rdzeniu WordPressa lub wielokrotne parsowanie plików XML bez wykorzystania pamięci podręcznej.







