Pest - Modern PHP Tesztelés
A Pest egy modern, elegáns PHP tesztelési keretrendszer, amely a PHPUnit-ra épül. Tiszta, olvasható szintaxist és kiváló fejlesztői élményt biztosít WordPress bővítmények és témák teszteléséhez.
Miért Pest?
- Elegáns szintaxis - Olvasható, kifejező tesztek
- PHPUnit kompatibilis - Meglévő tesztek működnek
- Gyors - Párhuzamos teszt futtatás
- Modern - PHP 8+ funkciók támogatása
- Plugin rendszer - Bővíthető funkcionalitás
- Kiváló DX - Watch mode, coverage, snapshot tesztek
Telepítés
Composer
composer require pestphp/pest --dev --with-all-dependencies
Inicializálás
./vendor/bin/pest --init
Ez létrehozza a tests/ mappát és a szükséges konfigurációs fájlokat.
Alapvető szintaxis
Első teszt
<?php
// tests/Unit/ExampleTest.php
test('example', function () {
expect(true)->toBeTrue();
});
test('addition works', function () {
expect(1 + 1)->toBe(2);
});
it() szintaxis
it('can add numbers', function () {
$result = 1 + 1;
expect($result)->toBe(2);
});
it('can concatenate strings', function () {
$result = 'Hello' . ' ' . 'World';
expect($result)->toBe('Hello World');
});
describe() csoportok
describe('Calculator', function () {
it('can add', function () {
expect(add(1, 2))->toBe(3);
});
it('can subtract', function () {
expect(subtract(5, 3))->toBe(2);
});
it('can multiply', function () {
expect(multiply(3, 4))->toBe(12);
});
});
Expectations (elvárások)
Alapvető assertions
// Egyenlőség
expect($value)->toBe(5); // Strict (===)
expect($value)->toEqual(5); // Loose (==)
// Típus ellenőrzés
expect($value)->toBeString();
expect($value)->toBeInt();
expect($value)->toBeFloat();
expect($value)->toBeBool();
expect($value)->toBeArray();
expect($value)->toBeObject();
expect($value)->toBeNull();
// Logikai értékek
expect($value)->toBeTrue();
expect($value)->toBeFalse();
expect($value)->toBeTruthy();
expect($value)->toBeFalsy();
// Üresség
expect($value)->toBeEmpty();
expect($value)->not->toBeEmpty();
Tömbök
// Tömb tartalom
expect($array)->toContain('apple');
expect($array)->toContainEqual(['name' => 'John']);
expect($array)->toHaveKey('name');
expect($array)->toHaveKeys(['name', 'email']);
expect($array)->toHaveCount(3);
// Tömb struktúra
expect($array)->toMatchArray([
'name' => 'John',
'email' => '[email protected]',
]);
Stringek
expect($string)->toContain('hello');
expect($string)->toStartWith('Hello');
expect($string)->toEndWith('World');
expect($string)->toMatch('/^Hello/');
expect($string)->toHaveLength(11);
Objektumok
expect($object)->toBeInstanceOf(User::class);
expect($object)->toHaveProperty('name');
expect($object)->toHaveProperty('name', 'John');
expect($object)->toHaveMethod('save');
Exceptions
expect(fn() => throwException())
->toThrow(Exception::class);
expect(fn() => throwException())
->toThrow(Exception::class, 'Error message');
expect(fn() => throwException())
->toThrow(InvalidArgumentException::class);
JSON
expect($json)->toBeJson();
expect($json)->json()->toHaveKey('data');
expect($json)->json()->data->toBeArray();
WordPress tesztelés
WordPress test bootstrap
tests/Pest.php:
<?php
// WordPress test bootstrap betöltése
require_once dirname(__DIR__) . '/vendor/autoload.php';
// WordPress test library
define('WP_TESTS_DIR', '/tmp/wordpress-tests-lib');
require_once WP_TESTS_DIR . '/includes/functions.php';
// Plugin aktiválása teszteléshez
tests_add_filter('muplugins_loaded', function () {
require dirname(__DIR__) . '/my-plugin.php';
});
require_once WP_TESTS_DIR . '/includes/bootstrap.php';
Plugin funkcionalitás tesztelése
<?php
// tests/Unit/PluginTest.php
test('plugin is active', function () {
expect(is_plugin_active('my-plugin/my-plugin.php'))->toBeTrue();
});
test('shortcode is registered', function () {
expect(shortcode_exists('my_shortcode'))->toBeTrue();
});
test('custom post type is registered', function () {
expect(post_type_exists('book'))->toBeTrue();
});
Post tesztelése
test('can create post', function () {
$post_id = wp_insert_post([
'post_title' => 'Test Post',
'post_content' => 'Test content',
'post_status' => 'publish',
]);
expect($post_id)->toBeInt();
expect($post_id)->toBeGreaterThan(0);
$post = get_post($post_id);
expect($post->post_title)->toBe('Test Post');
});
test('can update post meta', function () {
$post_id = wp_insert_post([
'post_title' => 'Test Post',
'post_status' => 'publish',
]);
update_post_meta($post_id, 'custom_field', 'custom_value');
expect(get_post_meta($post_id, 'custom_field', true))
->toBe('custom_value');
});
User tesztelése
test('can create user', function () {
$user_id = wp_create_user('testuser', 'password', '[email protected]');
expect($user_id)->toBeInt();
expect($user_id)->toBeGreaterThan(0);
$user = get_user_by('id', $user_id);
expect($user->user_email)->toBe('[email protected]');
});
test('user has correct role', function () {
$user_id = wp_create_user('editor', 'password', '[email protected]');
$user = new WP_User($user_id);
$user->set_role('editor');
expect($user->has_cap('edit_posts'))->toBeTrue();
expect($user->has_cap('manage_options'))->toBeFalse();
});
REST API tesztelése
test('REST endpoint returns posts', function () {
// Post létrehozása
wp_insert_post([
'post_title' => 'Test Post',
'post_status' => 'publish',
]);
// REST request
$request = new WP_REST_Request('GET', '/wp/v2/posts');
$response = rest_do_request($request);
expect($response->get_status())->toBe(200);
expect($response->get_data())->toBeArray();
expect($response->get_data())->not->toBeEmpty();
});
test('custom REST endpoint works', function () {
$request = new WP_REST_Request('GET', '/my-plugin/v1/data');
$response = rest_do_request($request);
expect($response->get_status())->toBe(200);
expect($response->get_data())->toHaveKey('success');
});
Hooks és Filters tesztelése
test('filter modifies content', function () {
// Filter callback hozzáadása a pluginban
add_filter('the_content', function ($content) {
return $content . '<p>Added by plugin</p>';
});
$result = apply_filters('the_content', 'Original content');
expect($result)->toContain('Added by plugin');
});
test('action is triggered', function () {
$triggered = false;
add_action('my_plugin_action', function () use (&$triggered) {
$triggered = true;
});
do_action('my_plugin_action');
expect($triggered)->toBeTrue();
});
Datasets (adatkészletek)
Egyszerű dataset
test('validates email', function (string $email, bool $expected) {
expect(is_email($email))->toBe($expected);
})->with([
['[email protected]', true],
['invalid-email', false],
['[email protected]', true],
['', false],
]);
Nevesített dataset
dataset('valid emails', [
'simple' => ['[email protected]'],
'with subdomain' => ['[email protected]'],
'with plus' => ['[email protected]'],
]);
dataset('invalid emails', [
'no at' => ['testexample.com'],
'no domain' => ['test@'],
'empty' => [''],
]);
test('accepts valid email', function (string $email) {
expect(is_email($email))->toBeTruthy();
})->with('valid emails');
test('rejects invalid email', function (string $email) {
expect(is_email($email))->toBeFalsy();
})->with('invalid emails');
Kombinált datasets
test('multiplication', function (int $a, int $b, int $expected) {
expect($a * $b)->toBe($expected);
})->with([
[2, 3, 6],
[4, 5, 20],
[0, 10, 0],
[-2, 3, -6],
]);
Mocking
Mock objektumok
test('service calls repository', function () {
$repository = mock(UserRepository::class)
->expect(
find: fn($id) => new User(['id' => $id, 'name' => 'John'])
);
$service = new UserService($repository);
$user = $service->getUser(1);
expect($user->name)->toBe('John');
});
Függvény mocking
test('function is called', function () {
$mock = mock('wp_mail')
->andReturn(true);
$result = send_notification('[email protected]', 'Subject', 'Body');
$mock->shouldHaveBeenCalled();
expect($result)->toBeTrue();
});
Partial mock
test('partial mock', function () {
$user = mock(User::class)->makePartial();
$user->shouldReceive('save')->andReturn(true);
$user->name = 'John';
$result = $user->save();
expect($user->name)->toBe('John');
expect($result)->toBeTrue();
});
Fixtures és Factories
beforeEach / afterEach
beforeEach(function () {
$this->user = wp_create_user('testuser', 'password', '[email protected]');
});
afterEach(function () {
wp_delete_user($this->user);
});
test('user exists', function () {
expect(get_user_by('id', $this->user))->not->toBeFalse();
});
Factory pattern
// tests/Factories/PostFactory.php
function createPost(array $attributes = []): int {
return wp_insert_post(array_merge([
'post_title' => 'Test Post',
'post_content' => 'Test content',
'post_status' => 'publish',
], $attributes));
}
// Teszt
test('post factory creates post', function () {
$post_id = createPost(['post_title' => 'Custom Title']);
expect(get_post($post_id)->post_title)->toBe('Custom Title');
});
Coverage (lefedettség)
Coverage futtatás
# HTML report
./vendor/bin/pest --coverage --coverage-html coverage
# Text output
./vendor/bin/pest --coverage
# Minimum coverage ellenőrzés
./vendor/bin/pest --coverage --min=80
Coverage konfiguráció
phpunit.xml:
<phpunit>
<coverage>
<include>
<directory suffix=".php">./src</directory>
</include>
<exclude>
<directory>./src/views</directory>
</exclude>
</coverage>
</phpunit>
Watch mode
# Fájl változások figyelése
./vendor/bin/pest --watch
# Specifikus mappa figyelése
./vendor/bin/pest --watch --filter=Unit
Parallel tesztelés
# Párhuzamos futtatás
./vendor/bin/pest --parallel
# CPU szálak megadása
./vendor/bin/pest --parallel --processes=4
Snapshot tesztelés
# Plugin telepítése
composer require spatie/pest-plugin-snapshots --dev
test('json output matches snapshot', function () {
$data = get_plugin_settings();
expect($data)->toMatchJsonSnapshot();
});
test('html output matches snapshot', function () {
$html = render_template('widget');
expect($html)->toMatchSnapshot();
});
Konfigurációs fájlok
pest.php
<?php
// tests/Pest.php
uses(Tests\TestCase::class)->in('Feature');
uses(Tests\Unit\TestCase::class)->in('Unit');
// Globális függvények
function createUser(array $attributes = []): WP_User {
$user_id = wp_create_user(
$attributes['login'] ?? 'testuser' . uniqid(),
$attributes['password'] ?? 'password',
$attributes['email'] ?? 'test' . uniqid() . '@example.com'
);
return new WP_User($user_id);
}
// Globális expect kiegészítések
expect()->extend('toBePublished', function () {
return $this->toHaveProperty('post_status', 'publish');
});
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
</phpunit>
CI/CD integráció
GitHub Actions
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
coverage: xdebug
- name: Install dependencies
run: composer install
- name: Run tests
run: ./vendor/bin/pest --coverage --min=80