Ak ste niekedy videli plugin, ktorý „fungoval“ lokálne a potom sa po malej aktualizácii potichu pokazil WordPress Vo verzii 6.9.4 problém zriedka pramení z jedného riadku. Pramení z nedostatku reprodukovateľných testov a najmä z nedostatku oddelenia medzi čistou logikou a integráciou WordPressu.

Čo budeme stavať

Budete nastavovať moderné testovacie prostredie (apríl 2026) pre plugin WordPress kompatibilný s PHP 8.1+ a WordPress 6.9.4+ s dvoma úrovňami:

  • Jednotkové testy (rýchlo): otestujte si logiku PHP bez načítania WordPressu.
  • Integračné testy (realistické): načítanie WordPressu a overenie hookov, rolí/schopností, nonce a interakcií s databázou prostredníctvom WP_UnitTestCase.

Konečný výsledok: vzorový plugin „BPCAB Demo“ s testovateľnou architektúrou (služby + jednoduchá DI), balík PHPUnit a kanál akcií GitHub, ktorý spúšťa testy na matici PHP/WP.

Nakoniec budete vedieť:

  • Štruktúrovanie pluginu tak, aby bola logika testovateľná bez WordPressu.
  • Nainštalujte a nakonfigurujte testovaciu sadu WordPress správne pomocou nástroja Composer.
  • Píšte spoľahlivé testy (jednotkové + integračné), ktoré nezlyhávajú.
  • Automatizujte vykonávanie CI pomocou matice a vyrovnávacích pamätí.

Rýchle zhrnutie

  • Oddeľujeme sa doména (čisté PHP) a infraštruktúra (WP, DB, REST, administrátorské hooky).
  • Skladateľ riadi automatické načítanie a závislosti od vývoja (phpunit/phpunit).
  • Dva balíky PHPUnit: jednotka (bez WP) a integrácie (s WP).
  • Integračný bootstrap načíta WordPress cez Testovacia sada WP a nakonfiguruje testovaciu databázu.
  • Na CI vykonáme maticu PHP 8.1/8.2/8.3 + WP 6.9.4 (a možno nočné (ak máte radi nebezpečný život).

Kedy použiť toto riešenie

  • Udržiavate „kritický obchodný“ plugin (e-commerce, členstvo, SEO, formuláre, synchronizácia).
  • Počas aktualizácií WordPressu/PHP dochádza k regresiám.
  • Rozvíjate sa ako tím a chcete osobné rekordy, ktoré sa menej pokazia.
  • Používate zložité hooky (priority, kumulatívne filtre, shortcode, REST).
  • Zameriavaš sa na vklad WordPress.org A chcete minimálnu úroveň prísnosti.

Kedy toto riešenie NEPOUŽÍVAŤ

  • Váš „plugin“ je jednoduchý 15-riadkový úryvok kódu vložený do pluginu pre úryvky kódu (a aj vtedy: jednotkový test môže byť stále užitočný, ale investícia je neúmerná).
  • Pracujete na jednorazovom projekte bez údržby (vo WordPresse zriedkavé, ale existuje).
  • Odmietaš zaviesť Composer: bez automatického načítavania a bez vývojových závislostí budeš strácať čas hraním sa s ním.
  • Nemáte izolované prostredie (Docker/testovaciu databázu). Testovanie v produkcii bez záloh, videl som to a skončilo to zle.

Predtým, ako začnete (predpoklady)

Verzie a prostredie

  • WordPress : 6.9.4 (cieľ) alebo novšia.
  • PHP 8.1 minimum (odporúčané), 8.2/8.3 OK.
  • Skladať : 2.x.
  • MySQL / mariadb : databáza určená na testovanie (napr.: wp_tests).

Zálohovanie a izolácia

  • Neukazuj nikdy testovaciu konfiguráciu do vašej produkčnej databázy.
  • Ak je to možné, vytvorte obmedzeného používateľa databázy na testovacie účely.
  • Pracujte v Git repozitári: budete manipulovať s konfiguračnými súbormi a skriptmi.

Užitočné oficiálne zdroje


Krok 1: Vytvorenie testovateľného pluginu (skelet + automatické načítanie)

Skutočná výhoda pramení z tohto: ak je vaša logika zaseknutá v anonymných spätných volaniach, budete „testovať WordPress“ namiesto testovania vášho kódu. Zvyčajne oddeľujem:

  • zdroj/Doména čistá logika (jednotkový test).
  • src/Infraštruktúra WordPress (hooky, možnosti, REST, administrácia).
  • src/Plugin bootstrap a registrácia služieb.

1) Vytvorte priečinok s pluginom

Dans wp-content/plugins/, vytvoriť:

  • bpcab-demo/
  • bpcab-demo/bpcab-demo.php
  • bpcab-demo/src/
  • bpcab-demo/tests/

2) Hlavný súbor pluginu

vytvoriť 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) Trieda pluginu + minikontajner

vytvoriť 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();
	}
}

vytvoriť 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) Príklad čistej logiky + WP hook

vytvoriť 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} !";
	}
}

vytvoriť 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']));
	}
}

Očakávaný výsledok

Aktivujte plugin v RozšírenieNainštalované rozšíreniaNa stránku pridajte:

[bpcab_hello name="Marie Curie"]

Musíte vidieť „Ahoj Marie Curie!“.


Krok 2: Inštalácia PHPUnit + testovacej sady WordPress (Composer)

Na WordPresse vzniká klasický zmätok: „Nainštaloval som si PHPUnit, takže môžem testovať.“ Nie. Na integračné testovanie potrebujete aj WP Test Suite (súbory includes/bootstrap.phptovárne atď.).

1) Inicializácia skladateľa

V priečinku s pluginom:

cd wp-content/plugins/bpcab-demo
composer init

Odporúčam vám zadať typ názvu balíka vendor/bpcab-demoa definovať src/ ako zdroj.

2) Pridajte automatické načítavanie PHPUnit a PSR-4

Vytvoriť/upraviť 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"
  }
}

Potom:

composer install
composer dump-autoload

3) Získajte testovaciu sadu WordPressu

Máte niekoľko metód. V roku 2026 zostáva najjednoduchšia:

  • clone vývoj na WordPresse niekde na vašom počítači,
  • použiť jeho súbor tests/phpunit ako následný test.

Príklad (upraviť):

mkdir -p ~/wp-tests
cd ~/wp-tests
git clone --depth=1 https://github.com/WordPress/wordpress-develop.git

Potom budete mať:

  • ~/wp-tests/wordpress-develop/tests/phpunit
  • ~/wp-tests/wordpress-develop/src („jadro WP je „rozvíjateľné“)

Oficiálny zdroj informácií o jadrovom prístupe PHPUnit: Základná príručka: Automatizované testovanie / PHPUnit.


Krok 3: Napíšte testovací bootstrap a izolujte prostredie

Ideme vytvoriť dve konfigurácie PHPUnit:

  • jednotka žiadny WordPress, rýchly, žiadna databáza.
  • integrácie Načítanie WordPressu + testovanie databázy.

1) Konfigurácia PHPUnit „jednotka“

vytvoriť phpunit.unit.xml v koreňovom adresári pluginu:

<?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>

vytvoriť 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) Nakonfigurujte „integráciu“ PHPUnit

vytvoriť 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>

vytvoriť 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';

Očakávaný výsledok

V tejto fáze spustenie testov stále neprinesie nič (žiadne testovacie súbory), ale:

composer test:unit

musí byť aspoň spustený PHPUnit.


Krok 4: Napíšte skutočné jednotkové testy (bez načítania WordPressu)

Testujeme BpcabDemoDomainGreetingTento test je rýchly, deterministický a nezávisí od jadra WP.

1) Vytvorte priečinok a súbor test

vytvoriť 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) Spustite jednotkové testy

composer test:unit

Očakávaný výsledok

Mali by ste dostať zelenú postupnosť. Ak sa zobrazí chyba typu „Trieda sa nenašla“, je to takmer vždy:

  • nesprávne automatické nabíjanie PSR-4 v composer.json,
  • zabudol si composer dump-autoload,
  • Vložili ste súbor do nesprávneho priečinka.

Krok 5: Integračné testy WordPressu (WP_UnitTestCase)

Teraz overíme, či je shortcode správne zaregistrovaný a či je vykresľovanie správne. V tomto bode načítame WordPress, takže je pomalší, ale zachytáva „skutočné“ regresie.

1) Vytvorte integračný test

vytvoriť 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) Spustite integračné testy

composer test:integration

Očakávaný výsledok

Ak je testovacia databáza prístupná a že WP_DEVELOP_DIR je správne, dostanete zelenú postupnosť.

Ak sa zobrazí chyba „Chyba pri nadväzovaní pripojenia k databáze“, neopravujte ju náhodne. Najprv skontrolujte, či:

  • základňa wp_tests existuje,
  • Používateľ databázy má práva,
  • nedal si localhost zatiaľ čo váš MySQL počúva 127.0.0.1 (alebo naopak, v závislosti od konfigurácie).

Krok 6: Testovanie hookov, filtrov, schopností a jednorazových čísel

Testy, ktoré odhalia najviac chýb v produkcii: tie, ktoré overujú, či ste zapojili svoje hooky na správne miesto, so správnou prioritou a či je rešpektovaná bezpečnosť (nonce/capabilities).

1) Pridajte zabezpečenú akciu správcu (príklad)

Pridáme veľmi jednoduchý koncový bod akcie správcu: aktualizuje možnosť, ale iba pre správcov a s nonce.

vytvoriť 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;
	}
}

Zapojte ho 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) Testovacia kapacita + jednorazový kód

vytvoriť 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));
	}
}

Tento test ilustruje bod, s ktorým sa často stretávam: Administrátorské obslužné rutiny nakoniec exit()Pri testovaní sa to dá preložiť do wp_die alebo výnimku v závislosti od postupnosti. Vzor: kontrolujete stav (možnosť, meta príspevku atď.) a nie presný výstupný stream.

Užitočné oficiálne zdroje:


Krok 7: Továrne, príslušenstvo, databáza a čistenie

Integračné testy, ktoré „chybujú“, často pramenia zo zle vyčisteného stavu databázy alebo z testu, ktorý je závislý od iného. Testovacia sada WP poskytuje továrne, ale musíte zostať disciplinovaní:

  • Vytvorte si v teste svoje príspevky/používateľov/výrazy.
  • vyčistiť manuálne možnosti/prechodné javy v tearDown(),
  • Nepoužívajte pevne zakódované ID.

Príklad: vytvorenie príspevku a otestovanie filtra

Pridajme filter, ktorý upraví názov príspevku podľa zadanej možnosti.

vytvoriť 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;
	}
}

Zapojte ho Plugin.php ako pri iných službách.

vytvoriť 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));
	}
}

Tento test vás chráni pred:

  • zlý háčik (zámena medzi akciou a filtrom),
  • priorita, ktorá zmení výsledok (ak ju predpona pridá aj iný plugin),
  • nespravovaný administrátorský/front-end kontext.

Krok 8: Automatizácia pomocou akcií GitHub (matica WP/PHP)

Bez CI zostáva testovanie „voliteľné“. S CI sa regresia stáva PR zlyhaním. Tam sa to začína vyplácať.

1) Pridajte pracovný postup akcií GitHub

vytvoriť .github/workflows/tests.yml (v koreňovom adresári repozitára, nie v wp-content (ak je váš plugin vyhradeným repozitárom):

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

Realistické poznámky:

  • Značka 6.9.4 v wordpress-develop existuje, ak sa zhoduje vetva/značka. V opačnom prípade použite --branch 6.9 alebo commit. Prispôsobte sa stratégii vydania jadra v čase, keď toto čítate.
  • Ak pridávate WP každú noc, očakávajte dočasné zlyhania. Často ho používam v režime „povolené zlyhanie“ pre kritické pluginy.

Referencia: oficiálny vklad jadra vývoj na WordPresse.


Kompletný výsledok

Ak chcete skopírovať všetko naraz, tu je minimálna zostava (okrem už zobrazených súborov). Dvakrát skontrolujte cesty.

Stromová štruktúra

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

Rýchle prispôsobenie

  • Pridať apartmán zmluva (testy kompatibility), ak váš doplnok sprístupňuje verejné filtre.
  • pridať phpstan vo vývoji, ak chcete typy uzamknúť (veľmi efektívne s architektúrou „Doména/Infraštruktúra“).
  • Presunúť vytváranie služieb do Poskytovateľ služieb ak váš plugin rastie.

Prispôsobiť sa pre Divi 5 / Elementor / Avada

Nástroje na tvorbu stránok vám nebránia v testovaní. Kľúčový bod: otestujte si logika a vaše Integrácie WordPressu, nie používateľské rozhranie tvorcu (ktoré sa viac týka testovania e2e).

Divi 5

  • Ak zverejníte krátky kód ako napríklad [bpcab_hello]Divi ho jednoducho používa prostredníctvom modulu „Kód“ alebo „Text“.
  • Odporúčaný integračný test: do_shortcode() (už hotové) a ak pridáte aktíva, otestujte wp_enqueue_scripts (háčik + závislosti).

Elementor

  • Pre a widget zvyk ElementorOddeľujem triedu widgetu (infraštruktúru) a službu „renderer“ (doménu). Renderer je jednotka, widget sa testuje v integrácii (overením, či sa volá registračný hook).
  • Bežným okrajovým prípadom, ktorý vidím, je, že registračný kód widgetu sa vykoná príliš skoro. Otestujte prioritu na elementor/widgets/register (alebo ekvivalentný háčik v závislosti od verzie Elementoru).

Avada (výrobca fúzií)

  • Platí rovnaká logika: krátke kódy/prvky Avada môžu zalamovať váš výstup. Užitočný test: overte, či váš krátky kód vracia stabilný reťazec s únikom.
  • Ak poskytujete „Fusion Element“, izolujte generovanie možností (polí) a otestujte ho v režime jednotky.

Záverečná kontrola

  1. V rámci pluginu: Rozšírenie → aktivujte „BPCAB Demo (testovateľné)“.
  2. Vytvorte stránku s [bpcab_hello name="Test"] mali by ste vidieť „Hello Test!“.
  3. V rozhraní príkazového riadka, z priečinka s pluginom:
    • composer test:unit zelený apartmán, veľmi rýchle prevedenie.
    • composer test:integration zelený balík, pomalší, použitá databáza.
  4. Na GitHub: push → pracovný postup „Testy“ prepne na PHP verzie matice.

Ak výsledok nie je taký, ako sa očakávalo

symptóm Príčina pravdepodobná overenie Riešenie
PHPUnit: „Trieda sa nenašla“ Automatické načítanie PSR-4 je nesprávne nakonfigurované Regardez composer.json a menný priestor súborov composer dump-autoload, korektné BpcabDemo\src/
Integrácia: „Testovací priečinok WordPressu sa nenašiel“ WP_DEVELOP_DIR nesprávny echo $WP_DEVELOP_DIR / skontrolujte cestu Vydajte sa správnou cestou k wordpress-develop
Integrácia: „Chyba pri nadväzovaní pripojenia k databáze“ Testovacia databáza neexistuje alebo sú prihlasovacie údaje nesprávne Prihláste sa do MySQL pomocou prihlasovacích údajov vytvoriť wp_testssprávny hostiteľ/používateľ/heslo
Skrátený kód sa v testovacom režime neuloží. Plugin sa nenačítal muplugins_loaded skontrolovať tests/bootstrap-integration.php Opravte require .../bpcab-demo.php (cesta)
Chyba PHP „Volanie nedefinovanej funkcie add_action()“ v jednotke Jednotkový test načíta kód závislý od WP Zobraziť stopu zásobníka Presuňte logiku do Domainzoskenovanie alebo testovanie v integrácii

Problémy, s ktorými sa často stretávam pri riešení problémov:

  • Kód je skopírovaný na nesprávne miesto (napr.: tests/bootstrap-integration.php vložiť src/).
  • Chýbajúca bodkočiarka v súbore načítanom bootstrapom: PHPUnit sa zastaví ešte pred zobrazením testu.
  • Zmätok medzi akciou a filtrom: používate add_action au Lieu de add_filter (alebo naopak) a test „nič nevidí“.
  • Testujete v produkcii „len aby ste videli“: testovacia databáza prepisuje možnosti. Nerobte to.
  • Váš plugin sa načítava príliš skoro/príliš neskoro: hooky nie sú k dispozícii alebo závislosti nie sú načítané.

Časté úskalia a chyby

Chyba Spôsobiť Riešenie
Nestabilné (nestále) integračné testy Celkový stav WP nebol vyčistený (pridané možnosti, prechodné javy, hooky) Čistiť tearDown()vyhnúť sa globálnym singletonom, obmedziť vedľajšie účinky
„Hlavičky už boli odoslané“ Test spustí presmerovanie/ukončenie pred ukladaním do vyrovnávacej pamäte Otestujte konečný stav, zachytte wp_redirectVyhnite sa tvrdeniam v hlavičkách
„Nie je možné upraviť informácie v hlavičke“ iba v CI Rozdiely v rozšíreniach PHP / ukladaní výstupu do vyrovnávacej pamäte Nespoliehajte sa na HTTP stream v PHPUnit; na to použite end-to-end testy.
Starý tutoriál odporúča zastarané skripty. Vývoj testovacej sady PHPUnit / WP Zosúladiť s aktuálnou základnou dokumentáciou a s wordpress-develop (apríl 2026)
Plugin snippets prelomí testy Runtime kód načítaný do testovacieho prostredia Otestujte si plugin v minimalistickom prostredí; vypnite nepotrebné mu-pluginy
Chyba súvisiaca so zastaraným PHP CI alebo lokálny počítač na PHP 7.x/8.0 Aktualizujte na PHP 8.1+ (pozri Podporované verzie PHP)

Variant / alternatíva

Možnosť „bez testovacej sady WordPress“ (iba jednotka)

Ak je váš plugin primárne čisto logický (výpočty, parsovanie, generovanie užitočného zaťaženia API), môžete sa obmedziť na:

  • Skladateľ + PHPUnit,
  • jednotkové testy na src/Domain,
  • Integračné testy nahradené malou batériou manuálnych „dymových“ testov.

Toto je prijateľné pre jednoduchý interný plugin, ale strácate pokrytie WP hookov, rolí, nonce a správania.

„Pokročilejšia“ možnosť: integračné testovanie s dočasným prostredím

Pri komplexných pluginoch často uprednostňujem spustenie integrácie v jednorazovom prostredí:

  • Docker Compose (MySQL + WordPress + CLI),
  • Inštalácia WordPressu na Pobreží Slonoviny,
  • PHPUnit testy + prípadne Playwright/Cypress pre e2e.

Je ťažší, ale znižuje prekvapenia s komínmi blízko produkcie.


Tipy na bezpečnosť, výkon a údržbu

  • Zabezpečenie Vždy testujte „zamietnuté“ cesty (neplatné možnosti, nonce). Bezpečnostné chyby často pramenia z dôvodu „funguje to, keď som administrátor“.
  • Izolácia Nikdy nezdieľajte testovaciu databázu s nikým iným. Ani lokálne.
  • výkon 80 % testov by malo byť v jednotkovom testovaní. Integračné testy by mali overovať zostavu, nie prehrávať každý prípad.
  • údržba Keď sa WordPress 6.9.x vyvinie, chcete, aby vám CI oznámilo, že „je nefunkčný“, skôr ako vašim používateľom.
  • Compat Ak podporujete viacero verzií WP, vytvorte maticu CI (napr. 6.7, 6.8, 6.9.4) a akceptujte niektoré podmienené úpravy.

Ak chcete ísť ešte ďalej

  • Pridať testy na REST API (trasy, oprávnenia spätného volania, schémy).
  • Otestujte migrácie databáz (vytvorenie tabuliek, dbDelta()) v integrácii.
  • Zriadiť a Poskytovateľ služieb (nádoba na čistiaci prostriedok) a otestujte zapojenie.
  • Pridať krok statická analýza (PHPStan/Žalm) + kódovacie štandardy (PHPCS WordPress) na CI.
  • Ak máte JS (bloky), pridajte Jest/Vitest cez @wordpress/skripty a oddeľte potrubia.

zdroje


Často kladené otázky

Prečo oddeľovať „jednotku“ a „integráciu“ namiesto testovania všetkého s načítaným WordPressom?

Pretože načítavanie WordPressu spôsobuje, že testy sú pomalé, nespoľahlivé a ťažšie diagnostikovateľné. WordPress si ponechávam na overenie zostavy (hooky, role, databáza) a obchodnú logiku testujem v čistom PHP.

Je PHPUnit 11 povinný?

Nie, ale v PHP 8.1+ je to konzistentná a udržiavaná voľba. Ak je váš stack obmedzený, prispôsobte verziu podľa toho. composer.jsonPozrite si časť o kompatibilite v dokumentácii k PHPUnit.

Prečo klonovať wordpress-develop namiesto „normálneho“ WordPressu?

Pretože testovacia sada WP žije v wordpress-develop/tests/phpunitMôžete si s tým pohrať aj inak, ale budete pritom nanovo vymýšľať skripty, ktoré už jadro udržiava.

Moje integračné testy sú veľmi pomalé. Čo mám robiť?

Znížte ich počet, zamerajte ich na integračné body a zvyšok presuňte do jednotkových testov. Na CI používajte vyrovnávacie pamäte Composera a vyhýbajte sa klonovaniu príliš veľkého množstva histórie (teda --depth=1).

Ako otestujem háčik, ktorý závisí od poradia/priority?

Otestujte konečný efekt (výstup/stav) a pridajte test, ktorý kontroluje prítomnosť spätného volania pomocou has_action() / has_filter() Ak je to relevantné. Poznámka: tieto funkcie overujú registráciu, nie vykonanie.

Ako otestovať kód, ktorý to robí exit ?

Vo všeobecnosti testujete stav (možnosť aktualizácie, vytvorený príspevok) a zachytávate, čo sa dá (filtrujete). wp_redirectNebojujte o „presadenie si úniku“.

V jednotkovom teste vidím „Volanie nedefinovanej funkcie esc_html()“. Je to normálne?

Áno : esc_html() je funkcia WordPressu. Ak chcete jednotkový test, izolujte logiku v čistej PHP triede a otestujte escapovanie v integrácii alebo vložte stratégiu escapovania (pokročilejšie).

Môžem otestovať Elementor/Divi/Avada pomocou PHPUnit?

Môžete otestovať svoj integračný kód (napr. „môj widget sa zaregistruje na správnom hooku“), ale nie celé používateľské rozhranie. Pre používateľské rozhranie použite end-to-end testy (Playwright/Cypress) na testovacej stránke.

Mám sa zaviazať? vendor/ v mojom plugine?

V prípade pluginu distribuovaného mimo Composera mnoho pluginov obsahuje vendor/Pre interný plugin spravovaný cez Composer nie. Vo všetkých prípadoch inštalujete závislosti na CI.

Ako spravovať kompatibilitu viacerých verzií vo WordPresse?

Pridajte maticu CI naprieč viacerými verziami WP a udržte svoje jednotkové testy nezávislé. Pre základné zmeny postupujte podľa tiketov na tréma a PR na GitHub.

Čo robiť, ak starý úryvok tutoriálu už nefunguje na WordPresse 6.9.4?

Nenúťte to. Zosúlaďte sa s aktuálnou testovacou sadou WP (wordpress-develop) a prispôsobte si Bootstrap. Staré skripty na inštaláciu testov, ktoré sa nachádzajú na zastaraných blogoch, často kazia svoje funkcie kvôli PHPUnit (zmenenému API) a cestám k sadám.