PHPUnit - PHP Unit Tesztelés
A PHPUnit a de facto standard PHP unit tesztelési keretrendszer. A WordPress hivatalos teszt keretrendszere is erre épül, így ideális választás bővítmények és témák teszteléséhez.
Miért PHPUnit?
- Standard - A legelterjedtebb PHP tesztelési keretrendszer
- WordPress integráció - Hivatalos WP tesztelési könyvtár
- Részletes assertions - Széles választék ellenőrzési metódusokból
- Mocking - Beépített mock és stub támogatás
- Coverage - Kód lefedettség mérés
- CI/CD - Kiváló integráció
Telepítés
Composer
composer require --dev phpunit/phpunit
WordPress Test Library
# WP-CLI-vel
wp scaffold plugin-tests my-plugin
# Vagy manuálisan
bash bin/install-wp-tests.sh wordpress_test root '' localhost latest
WordPress tesztelési környezet
Telepítési script
Hozz létre egy bin/install-wp-tests.sh fájlt:
#!/usr/bin/env bash
if [ $# -lt 3 ]; then
echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version]"
exit 1
fi
DB_NAME=$1
DB_USER=$2
DB_PASS=$3
DB_HOST=${4-localhost}
WP_VERSION=${5-latest}
WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib}
WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress}
download() {
if [ `which curl` ]; then
curl -s "$1" > "$2";
elif [ `which wget` ]; then
wget -nv -O "$2" "$1"
fi
}
# WordPress letöltése
if [ ! -d $WP_CORE_DIR ]; then
mkdir -p $WP_CORE_DIR
download https://wordpress.org/latest.tar.gz /tmp/wordpress.tar.gz
tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR
fi
# Test library letöltése
if [ ! -d $WP_TESTS_DIR ]; then
mkdir -p $WP_TESTS_DIR
svn co --quiet https://develop.svn.wordpress.org/trunk/tests/phpunit/includes/ $WP_TESTS_DIR/includes
svn co --quiet https://develop.svn.wordpress.org/trunk/tests/phpunit/data/ $WP_TESTS_DIR/data
fi
# wp-tests-config.php létrehozása
cat > $WP_TESTS_DIR/wp-tests-config.php <<EOF
<?php
define( 'ABSPATH', '$WP_CORE_DIR/' );
define( 'DB_NAME', '$DB_NAME' );
define( 'DB_USER', '$DB_USER' );
define( 'DB_PASSWORD', '$DB_PASS' );
define( 'DB_HOST', '$DB_HOST' );
define( 'DB_CHARSET', 'utf8' );
define( 'DB_COLLATE', '' );
\$table_prefix = 'wptests_';
define( 'WP_TESTS_DOMAIN', 'example.org' );
define( 'WP_TESTS_EMAIL', '[email protected]' );
define( 'WP_TESTS_TITLE', 'Test Blog' );
define( 'WP_PHP_BINARY', 'php' );
define( 'WPLANG', '' );
EOF
# Adatbázis létrehozása
mysql -u$DB_USER -p$DB_PASS -h$DB_HOST -e "CREATE DATABASE IF NOT EXISTS $DB_NAME"
Bootstrap fájl
tests/bootstrap.php:
<?php
/**
* PHPUnit bootstrap file
*/
$_tests_dir = getenv( 'WP_TESTS_DIR' ) ?: '/tmp/wordpress-tests-lib';
// WordPress test functions betöltése
require_once $_tests_dir . '/includes/functions.php';
/**
* Plugin betöltése
*/
function _manually_load_plugin() {
require dirname( __DIR__ ) . '/my-plugin.php';
}
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
// WordPress test bootstrap
require $_tests_dir . '/includes/bootstrap.php';
Konfigurációs fájl
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"
beStrictAboutOutputDuringTests="true"
beStrictAboutTestsThatDoNotTestAnything="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>
<coverage>
<include>
<directory suffix=".php">./src</directory>
<directory suffix=".php">./includes</directory>
</include>
<exclude>
<directory>./vendor</directory>
<directory>./tests</directory>
</exclude>
</coverage>
</phpunit>
Alapvető tesztelés
Első teszt
<?php
// tests/Unit/ExampleTest.php
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase {
public function test_addition() {
$this->assertEquals( 4, 2 + 2 );
}
public function test_string_contains() {
$this->assertStringContainsString( 'Hello', 'Hello World' );
}
}
WordPress Test Case
<?php
// tests/Integration/PluginTest.php
class PluginTest extends WP_UnitTestCase {
public function test_plugin_is_active() {
$this->assertTrue( is_plugin_active( 'my-plugin/my-plugin.php' ) );
}
public function test_shortcode_exists() {
$this->assertTrue( shortcode_exists( 'my_shortcode' ) );
}
}
Assertions
Alapvető assertions
// Egyenlőség
$this->assertEquals( $expected, $actual );
$this->assertSame( $expected, $actual ); // Strict
// Típus
$this->assertIsString( $value );
$this->assertIsInt( $value );
$this->assertIsArray( $value );
$this->assertIsBool( $value );
$this->assertIsObject( $value );
$this->assertNull( $value );
// Logikai
$this->assertTrue( $value );
$this->assertFalse( $value );
// Üresség
$this->assertEmpty( $value );
$this->assertNotEmpty( $value );
Tömb assertions
// Tömb tartalom
$this->assertContains( 'apple', $array );
$this->assertNotContains( 'banana', $array );
$this->assertCount( 3, $array );
$this->assertArrayHasKey( 'name', $array );
// Tömb egyenlőség
$this->assertEqualsCanonicalizing( $expected, $actual ); // Sorrend nem számít
String assertions
$this->assertStringContainsString( 'hello', $string );
$this->assertStringStartsWith( 'Hello', $string );
$this->assertStringEndsWith( 'World', $string );
$this->assertMatchesRegularExpression( '/^Hello/', $string );
Objektum assertions
$this->assertInstanceOf( User::class, $object );
$this->assertObjectHasProperty( 'name', $object );
Exception assertions
public function test_exception_is_thrown() {
$this->expectException( InvalidArgumentException::class );
$this->expectExceptionMessage( 'Invalid value' );
throw new InvalidArgumentException( 'Invalid value' );
}
WordPress specifikus tesztek
Post tesztelés
class PostTest extends WP_UnitTestCase {
public function test_can_create_post() {
$post_id = $this->factory->post->create( [
'post_title' => 'Test Post',
'post_content' => 'Test content',
'post_status' => 'publish',
] );
$this->assertIsInt( $post_id );
$this->assertGreaterThan( 0, $post_id );
$post = get_post( $post_id );
$this->assertEquals( 'Test Post', $post->post_title );
}
public function test_can_update_post_meta() {
$post_id = $this->factory->post->create();
update_post_meta( $post_id, 'custom_field', 'custom_value' );
$this->assertEquals(
'custom_value',
get_post_meta( $post_id, 'custom_field', true )
);
}
}
User tesztelés
class UserTest extends WP_UnitTestCase {
public function test_can_create_user() {
$user_id = $this->factory->user->create( [
'user_login' => 'testuser',
'user_email' => '[email protected]',
'role' => 'editor',
] );
$user = get_user_by( 'id', $user_id );
$this->assertEquals( 'testuser', $user->user_login );
$this->assertTrue( in_array( 'editor', $user->roles, true ) );
}
public function test_user_capabilities() {
$user_id = $this->factory->user->create( [ 'role' => 'editor' ] );
$user = new WP_User( $user_id );
$this->assertTrue( $user->has_cap( 'edit_posts' ) );
$this->assertFalse( $user->has_cap( 'manage_options' ) );
}
}
Taxonomy tesztelés
class TaxonomyTest extends WP_UnitTestCase {
public function test_can_create_term() {
$term = $this->factory->term->create_and_get( [
'taxonomy' => 'category',
'name' => 'Test Category',
] );
$this->assertInstanceOf( WP_Term::class, $term );
$this->assertEquals( 'Test Category', $term->name );
}
}
AJAX tesztelés
class AjaxTest extends WP_Ajax_UnitTestCase {
public function test_ajax_handler() {
// Admin bejelentkeztetése
$this->_setRole( 'administrator' );
// POST adatok beállítása
$_POST['action'] = 'my_ajax_action';
$_POST['data'] = 'test';
$_POST['nonce'] = wp_create_nonce( 'my_nonce' );
// AJAX hívás
try {
$this->_handleAjax( 'my_ajax_action' );
} catch ( WPAjaxDieContinueException $e ) {
// Normális viselkedés
}
// Válasz ellenőrzése
$response = json_decode( $this->_last_response );
$this->assertTrue( $response->success );
}
}
REST API tesztelés
class RestApiTest extends WP_UnitTestCase {
public function test_rest_endpoint() {
// Post létrehozása
$post_id = $this->factory->post->create( [
'post_status' => 'publish',
] );
// REST request
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id );
$response = rest_do_request( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( $post_id, $data['id'] );
}
public function test_custom_endpoint() {
$request = new WP_REST_Request( 'GET', '/my-plugin/v1/data' );
$response = rest_do_request( $request );
$this->assertEquals( 200, $response->get_status() );
$this->assertArrayHasKey( 'success', $response->get_data() );
}
}
Fixtures és Setup
setUp és tearDown
class MyPluginTest extends WP_UnitTestCase {
private $post_id;
public function setUp(): void {
parent::setUp();
// Teszt előkészítés
$this->post_id = $this->factory->post->create();
}
public function tearDown(): void {
// Takarítás
wp_delete_post( $this->post_id, true );
parent::tearDown();
}
public function test_post_exists() {
$this->assertNotNull( get_post( $this->post_id ) );
}
}
setUpBeforeClass
class DatabaseTest extends WP_UnitTestCase {
private static $table_name;
public static function setUpBeforeClass(): void {
parent::setUpBeforeClass();
global $wpdb;
self::$table_name = $wpdb->prefix . 'test_table';
// Tábla létrehozása egyszer
$wpdb->query( "CREATE TABLE " . self::$table_name . " (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
)" );
}
public static function tearDownAfterClass(): void {
global $wpdb;
$wpdb->query( "DROP TABLE IF EXISTS " . self::$table_name );
parent::tearDownAfterClass();
}
}
Data Providers
class ValidationTest extends WP_UnitTestCase {
/**
* @dataProvider emailProvider
*/
public function test_email_validation( string $email, bool $expected ) {
$this->assertEquals( $expected, is_email( $email ) !== false );
}
public function emailProvider(): array {
return [
'valid email' => [ '[email protected]', true ],
'invalid email' => [ 'invalid-email', false ],
'another valid' => [ '[email protected]', true ],
'empty string' => [ '', false ],
'with subdomain' => [ '[email protected]', true ],
];
}
}
Mocking
Mock objektumok
public function test_with_mock() {
$mock = $this->createMock( MyService::class );
$mock->expects( $this->once() )
->method( 'getData' )
->willReturn( [ 'key' => 'value' ] );
$result = $mock->getData();
$this->assertEquals( [ 'key' => 'value' ], $result );
}
Stub objektumok
public function test_with_stub() {
$stub = $this->createStub( Repository::class );
$stub->method( 'find' )
->willReturn( new User( [ 'name' => 'John' ] ) );
$service = new UserService( $stub );
$user = $service->getUser( 1 );
$this->assertEquals( 'John', $user->name );
}
WordPress függvények mocking
// WP_Mock könyvtár használatával
public function test_wp_function_mock() {
\WP_Mock::userFunction( 'get_option' )
->once()
->with( 'my_option' )
->andReturn( 'my_value' );
$result = get_option( 'my_option' );
$this->assertEquals( 'my_value', $result );
}
Hook tesztelés
class HooksTest extends WP_UnitTestCase {
public function test_filter_modifies_content() {
// Filter regisztrálása (pluginban)
add_filter( 'the_content', function( $content ) {
return $content . '<p>Added by plugin</p>';
} );
$result = apply_filters( 'the_content', 'Original' );
$this->assertStringContainsString( 'Added by plugin', $result );
}
public function test_action_is_fired() {
$fired = false;
add_action( 'my_plugin_action', function() use ( &$fired ) {
$fired = true;
} );
do_action( 'my_plugin_action' );
$this->assertTrue( $fired );
}
}
Coverage (lefedettség)
Coverage futtatás
# HTML report
./vendor/bin/phpunit --coverage-html coverage/
# Text output
./vendor/bin/phpunit --coverage-text
# Clover XML (CI-hez)
./vendor/bin/phpunit --coverage-clover coverage.xml
Coverage szűrés
<coverage>
<include>
<directory suffix=".php">./src</directory>
</include>
<exclude>
<file>./src/bootstrap.php</file>
<directory>./src/views</directory>
</exclude>
</coverage>
CI/CD integráció
GitHub Actions
name: Tests
on: [push, pull_request]
jobs:
test:
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
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mysqli
coverage: xdebug
- name: Install dependencies
run: composer install
- name: Install WP Test Suite
run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1
- name: Run tests
run: ./vendor/bin/phpunit --coverage-clover coverage.xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
Hasznos parancsok
# Összes teszt futtatása
./vendor/bin/phpunit
# Specifikus teszt fájl
./vendor/bin/phpunit tests/Unit/MyTest.php
# Specifikus teszt metódus
./vendor/bin/phpunit --filter test_my_method
# Testsuite futtatása
./vendor/bin/phpunit --testsuite Unit
# Verbose output
./vendor/bin/phpunit --verbose
# Stop on failure
./vendor/bin/phpunit --stop-on-failure