Ugrás a fő tartalomhoz

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

Források