
Автотесты плагина WordPress & WooCommerce через WP CLI + Pest как в Laravel
24 апреля 2026
В Laravel тесты запускаются удобно. Захотелось того же для WordPress-плагинов — с Pest, WP CLI и живым окружением. Разобрался, собрал стек, делюсь.
Зачем тестировать WordPress-плагин
Пока плагин маленький, хватает ручной проверки: установил, кликнул, посмотрел. Но когда внутри появляется бизнес-логика, синхронизация данных, настройки WooCommerce, внешние API и несколько режимов работы — ручное тестирование перестаёт работать.
Регрессии проникают в релизы не из-за невнимательности разработчика. Причина — слишком много состояний, которые нужно держать в голове:
- настройки плагина;
- версии WordPress, PHP и WooCommerce;
- товары, вариации, категории, атрибуты и meta-поля;
- ответы внешнего API;
- старые данные в базе;
- edge cases, которые редко проверяются руками.
Автотесты решают это системно: после каждого изменения в коде вы знаете, что ключевые сценарии не сломались.

Стек
| Инструмент | Роль |
|---|---|
@wordpress/env |
Изолированное Docker-окружение с WordPress |
| Docker | Контейнеры для WordPress, PHP и базы данных |
| Composer | PHP-зависимости проекта |
| Pest PHP | Лаконичный синтаксис тестов поверх PHPUnit |
| WP-CLI | Единая точка входа для запуска тестов внутри WordPress |
Makefile |
Короткие команды для повседневной работы |
Тесты запускаются в живом WordPress-окружении, но остаются воспроизводимыми и управляемыми.
1. Локальное окружение: wp-env
@wordpress/env — официальный инструмент WordPress для локальной разработки. Под капотом Docker: поднимает WordPress без ручной настройки PHP, MySQL, nginx.
Минимальный конфиг:
{
"core": null,
"phpVersion": "8.3",
"plugins": [
".",
"<https://downloads.wordpress.org/plugin/woocommerce.zip>"
],
"port": 8888,
"config": {
"WP_DEBUG": true,
"WP_DEBUG_LOG": true,
"WP_DEBUG_DISPLAY": false,
"SCRIPT_DEBUG": true
}
}
Детали конфига:
"core": null— актуальная стабильная версия WordPress;"phpVersion": "8.3"— версия PHP зафиксирована явно;"."вplugins— текущий плагин монтируется прямо из рабочей директории;- WooCommerce подключается как зависимость, если плагин работает с магазином;
WP_DEBUG_LOGпишет ошибки в лог, а не показывает их в браузере.
Запуск:
npx wp-env start
После запуска WordPress доступен локально, плагин уже в окружении.
2. Composer-зависимости
Composer нужен не только для production-зависимостей, но и для тестового инструментария: Pest, отладка, пакеты для работы с WordPress.
{
"require-dev": {
"pestphp/pest": "^4.0",
"roots/wordpress": "^6.7",
"wp-cli/wp-cli": "*",
"brain/monkey": "^2.4",
"symfony/var-dumper": "^7.4"
},
"extra": {
"wordpress-install-dir": "vendor/wordpress"
}
}
| Пакет | Зачем |
|---|---|
pestphp/pest |
Фреймворк для написания и запуска тестов |
roots/wordpress |
WordPress core как Composer-зависимость |
wp-cli/wp-cli |
CLI-инфраструктура для команд внутри WordPress |
brain/monkey |
Моки WordPress-хуков и функций для unit-тестов |
symfony/var-dumper |
Удобная отладка во время разработки |
Установка внутри контейнера:
npx wp-env run cli --env-cwd=wp-content/plugins/wooms composer install
Если директория плагина другая — подставьте свой путь.
3. Pest PHP вместо PHPUnit
Pest — современный тестовый фреймворк для PHP, работает поверх PHPUnit, но с более коротким и читаемым синтаксисом.
Простой тест:
<?php
it('loads wordpress core functions', function (): void {
expect(function_exists('get_post'))->toBeTrue();
$posts = get_posts();
expect($posts)->toBeArray();
});
Тесты читаются почти как обычное описание поведения:
it('does something', function () { ... });expect($value)->toBeArray();beforeEach()иafterEach()для подготовки и очистки;- датасеты для проверки нескольких вариантов входных данных.
4. phpunit.xml: конфигурация тест-сьюта
Поскольку Pest работает поверх PHPUnit, общая конфигурация задаётся через phpunit.xml.
<?xml version="1.0"?>
<phpunit
bootstrap="tests/bootstrap.php"
backupGlobals="false"
colors="true"
cacheDirectory=".phpunit.cache"
>
<testsuites>
<testsuite name="tdd">
<directory suffix=".php">./tests/includes/</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
</php>
</phpunit>
Детали конфига:
bootstrap="tests/bootstrap.php"— файл, загружаемый перед тестами;./tests/includes/— директория с тестами;APP_ENV=testing— явный тестовый режим.
5. bootstrap.php: подключаем WordPress
Чтобы тестировать плагин внутри реального WordPress, перед запуском тестов загружаем wp-load.php.
<?php
declare(strict_types=1);
$wpLoadPath = '/var/www/html/wp-load.php';
if (! file_exists($wpLoadPath)) {
throw new RuntimeException("WordPress bootstrap file not found at: {$wpLoadPath}");
}
require_once $wpLoadPath;
После этого в тестах доступны:
- функции WordPress:
get_posts(),get_option(),update_option(); - глобальный
$wpdb; - функции WooCommerce:
wc_get_product(),wc_get_order(); - функции и классы самого плагина.
Это уже не чистые unit-тесты, а integration-тесты: они проверяют, как код работает внутри настоящего WordPress-окружения.
6. WP-CLI: единая точка входа для тестов
Запускать Pest напрямую внутри контейнера можно, но команды быстро становятся длинными. Удобнее — WP-CLI команда wp test:wooms.
Регистрация команды:
<?php
if (defined('WP_CLI') && WP_CLI && class_exists('WP_CLI')) {
WP_CLI::add_command('test:wooms', RunWoomsTestsCommand::class, [
'shortdesc' => 'Run plugin tests using Pest.',
]);
}
Класс команды:
<?php
class RunWoomsTestsCommand
{
public function __invoke($args, $assoc_args)
{
$plugin_path = dirname(__DIR__.'..');
$pest_binary = $plugin_path.'/vendor/bin/pest';
if (! file_exists($pest_binary)) {
WP_CLI::error(sprintf('Pest binary was not found at %s.', $pest_binary));
}
$php_binary = defined('PHP_BINARY') ? PHP_BINARY : 'php';
$forwarded_args = $args;
foreach ($assoc_args as $key => $value) {
$forwarded_args[] = true === $value
? sprintf('--%s', $key)
: sprintf('--%s=%s', $key, (string) $value);
}
$command_parts = array_merge(
[
escapeshellarg($php_binary),
escapeshellarg($pest_binary),
'--colors=always',
],
array_map('escapeshellarg', $forwarded_args)
);
$command = sprintf(
'cd %s && %s',
escapeshellarg($plugin_path),
implode(' ', $command_parts)
);
passthru($command, $exit_code);
WP_CLI::halt($exit_code);
}
}
Примеры запуска:
# Полный прогон
npx wp-env run cli wp test:wooms
# Фильтр по имени теста
npx wp-env run cli wp test:wooms --filter="loads wordpress core functions"
# Конкретный файл
npx wp-env run cli wp test:wooms tests/includes/ProductsTests.php
7. Makefile: короткие команды
Когда в проекте Docker, wp-env, Composer, WP-CLI и тесты, Makefile не добавляет магии, но экономит время на наборе длинных команд.
start:
npx wp-env start
start-update:
npx wp-env stop
npx wp-env start --update
stop:
npx wp-env stop
destroy:
npx wp-env destroy
cli:
npx wp-env run cli sh
test:
npx wp-env run cli wp test:wooms
Использование:
make start— поднять окружение;make stop— остановить контейнеры;make cli— shell внутри контейнера;make test— запустить тесты.
8. Фикстуры вместо реального API
Если плагин синхронизирует данные с внешним сервисом (например, МойСклад), не стоит ходить в API при каждом прогоне тестов. Это замедляет тесты, делает их нестабильными и зависимыми от сети.
Сохраните реальные JSON-ответы в фикстуры:
tests/data/fixtures-v1/
├── products/
├── categories/
└── variants/
Теперь тесты проверяют бизнес-логику на стабильных входных данных:
- парсинг ответа API;
- создание или обновление товаров;
- работа с категориями;
- синхронизация атрибутов;
- обработка edge cases.
Проверка фикстуры:
<?php
it('has available and valid fixtures v1', function (): void {
$productsFixture = __DIR__.'/../data/fixtures-v1/products/first-100.json';
expect(file_exists($productsFixture))->toBeTrue();
$payload = json_decode((string) file_get_contents($productsFixture), true);
expect($payload)->toBeArray();
expect($payload['rows'] ?? [])->not->toBeEmpty();
$firstRow = $payload['rows'][0] ?? [];
expect($firstRow['id'] ?? '')->not->toBe('');
expect($firstRow['meta']['type'] ?? '')->toBe('product');
expect($firstRow['salePrices'] ?? [])->not->toBeEmpty();
});
9. Примеры тестов
Smoke-тест: WordPress, WooCommerce и плагин загрузились
<?php
it('loads WooMS plugin and WooCommerce', function (): void {
expect(function_exists('WooMS\\request'))->toBeTrue();
expect(function_exists('wc_get_product'))->toBeTrue();
expect(class_exists('WooCommerce'))->toBeTrue();
});
Быстрая проверка: окружение поднялось, WordPress загрузился, WooCommerce доступен, плагин активен.
Интеграционный тест синхронизации продукта
<?php
it('syncs products with attributes when sync option is enabled', function (): void {
\WooMS\Settings::setValue('wooms_attributes_sync_enabled', 1);
$rows = getProductsWithAttributesFixtureRows();
$row = $rows[0];
$productId = \WooMS\Products\product_update($row, []);
expect($productId)->toBeInt()->toBeGreaterThan(0);
$product = wc_get_product($productId);
expect($product)->not->toBeFalse();
expect($product->get_meta('wooms_id'))->toBe((string) $row['id']);
$productAttributes = $product->get_attributes();
expect($productAttributes)->toBeArray()->not->toBeEmpty();
});
Тест проверяет реальное поведение плагина: включает настройку синхронизации атрибутов, берёт продукт из фикстуры, запускает логику обновления товара, проверяет созданный WooCommerce-продукт, связь с исходным ID и атрибуты товара.
Проверка настроек WooCommerce
<?php
it('uses ruble currency in seeded WooCommerce settings', function (): void {
expect((string) get_option('woocommerce_currency'))->toBe('RUB');
expect((string) get_option('woocommerce_default_country'))->toBe('RU');
expect((string) get_option('woocommerce_price_num_decimals'))->toBe('2');
});
Полезно, если перед тестами выполняется seeding базовых настроек магазина: быстрая проверка, что окружение в ожидаемом состоянии.
10. Транзакции: тесты без загрязнения базы
Интеграционные тесты создают товары, категории, meta-поля, настройки и связи в базе. Без очистки следующие тесты начинают зависеть от результатов предыдущих.
Способ сохранить базу чистой — оборачивать каждый тест в транзакцию и откатывать изменения после выполнения:
<?php
beforeEach(function (): void {
global $wpdb;
$wpdb->query('START TRANSACTION');
});
afterEach(function (): void {
global $wpdb;
$wpdb->query('ROLLBACK');
});
Особенно полезно, когда тест создаёт реальные записи в WordPress и WooCommerce. После теста база возвращается в исходное состояние.
Транзакции работают не во всех сценариях одинаково. Если код делает собственные commit, использует отдельные соединения или внешние побочные эффекты, нужна дополнительная очистка данных.
11. Структура тестов в проекте
tests/
├── bootstrap.php # Подключение WordPress
├── add-wp-cli.php # Регистрация WP-CLI команд
├── data/
│ └── fixtures-v1/ # JSON-фикстуры из внешнего API
│ ├── products/
│ ├── categories/
│ └── variants/
└── includes/ # Активные Pest-тесты
├── BaseTests.php
├── ProductsTests.php
└── ProductsAndAttrubutesTests.php
Структура разделяет инфраструктуру, фикстуры и тестовые сценарии.
Что это даёт
| Проблема | Решение |
|---|---|
| Регрессии при изменении логики синхронизации | Тесты ловят ошибки до релиза |
| Зависимость от реального API | Фикстуры дают стабильные входные данные |
| Ручная настройка WordPress | wp-env поднимает окружение одной командой |
| Сложный запуск тестов внутри контейнера | WP-CLI команда и Makefile дают короткий интерфейс |
| Загрязнение базы после тестов | Транзакции и rollback возвращают БД в исходное состояние |
| Тесты не видят реальный WordPress-контекст | wp-load.php загружает WordPress, WooCommerce и сам плагин |
Итог
Связка @wordpress/env + Docker + Composer + Pest PHP + WP-CLI даёт понятный и воспроизводимый workflow для разработки WordPress-плагина.
Для простого плагина это может показаться избыточным. Но если внутри есть синхронизация данных, WooCommerce, внешние API и бизнес-логика, такой стек быстро окупается:
- разработчик быстрее проверяет изменения;
- новые участники быстрее поднимают проект;
- регрессии ловятся до релиза;
- тестовые данные не зависят от внешнего API;
- окружение можно воспроизвести локально и в CI.
Если делаете похожий проект, забирайте подход целиком. Ни одной строчки кода настраивать вручную не нужно — всё уже собрано и работает.

