Testy jednostkowe w WordPress: Przewodnik dewelopera na rok 2026

Testy jednostkowe w WordPress: Przewodnik dewelopera na rok 2026

Ostatnio zweryfikowano: 12 października 2026
16 min czytania
Poradnik
Full-stack developer

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/phpunit

Narzę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: false

Uruchamianie 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-report

Odpowiednia 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=integration

Oddzielenie 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.json

#Konfiguracja 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 testowaniaNarzędziaSzybkość wykonaniaWymóg bazy danychGłówny cel testu
Testy jednostkowePHPUnit 10/11, Brain Monkey, MockeryMilisekundyBrak (Pamięć podręczna)Logika biznesowa, kalkulacje, transformacje filtrów
Testy integracyjneWP_UnitTestCase, wp-envSekundyTestowa baza MySQLZapytania WP_Query, zapis do bazy, priorytety hooków
Testy akceptacyjnewp-browser, CodeceptionSekundy do minutPrzeglądarka headless + DBFormularze administracyjne, uprawnienia, procesy zamówień
Testy bloków e2ePlaywright, @wordpress/scriptsMinutyŚrodowisko DockerRenderowanie bloków Gutenberg, opcje w panelu bocznym

Dobre praktyki utrzymania zestawu testowego:

  1. Czystość testów jednostkowych: Nigdy nie wywołuj funkcji bazodanowych w katalogu testów jednostkowych.
  2. Wycofywanie transakcji: Zapewnij maksymalną szybkość testów integracyjnych poprzez rollback operacji SQL.
  3. Automatyzacja w CI: Uruchamiaj testy na PHP 8.2 i 8.3 przy każdym wypchnięciu kodu do repozytorium.
  4. Weryfikacja bloków: Sprawdzaj transformacje i deprecacje bloków Gutenberg przy pomocy Jest i Playwright.
  5. 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 --profile

Jeż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.

Następny krok

Przekuj artykuł w realne wdrożenie

Pod tym wpisem dokładam linki, które domykają intencję użytkownika i prowadzą dalej w strukturze serwisu.

Chcesz wdrożyć ten temat na swojej stronie?

Jeśli chcesz przełożyć wiedzę z artykułu na działającą stronę, sklep albo przebudowę serwisu, przygotuję konkretny zakres prac.

Powiązany klaster

Sprawdź inne usługi WordPress i bazę wiedzy

Wzmocnij swój biznes dzięki profesjonalnemu wsparciu technicznemu w kluczowych obszarach ekosystemu WordPress.

FAQ do artykułu

Często zadawane pytania

Najważniejsze odpowiedzi, które pomagają wdrożyć temat w praktyce.

SEO-readyGEO-readyAEO-ready4 Q&A
Po co testować, skoro mogę po prostu odświeżyć stronę?#
Testowanie ręczne jest wolne i podatne na błędy. Testy jednostkowe trwają milisekundy i weryfikują tysiące ścieżek logicznych natychmiast, dając pewność, że mała zmiana nie popsuła czegoś w innej części systemu.
Czy potrzebuję bazy danych do testów jednostkowych?#
Czyste testy jednostkowe powinny być niezależne od bazy danych dzięki makieta (mocks). Testy integracyjne używają jednak tymczasowej bazy (często w Dockerze) do weryfikacji wyników WP_Query.
Czy testowanie jest warte dodatkowego czasu?#
Tak. W dłuższej perspektywie oszczędza setki godzin na debugowaniu i zapobiega kosztownym błędom na produkcji.
Czy mogę testować bloki Gutenberg?#
Oczywiście. W 2026 roku używamy Jest wraz z oficjalnym pakietem skryptów WordPressa do testowania renderowania bloków i ich atrybutów.

Potrzebujesz FAQ dopasowanego do branży i rynku? Przygotujemy wersję pod Twoje cele biznesowe.

Porozmawiajmy

Polecane artykuły