WordPressのプラグインをPHPUnitでテストする

WordPressのプラグインをPHPUnitでテストする Wordpress

WordPressのプラグイン開発時にテストコードを書いてテストを行いながら開発することで、バグを見つけやすくなる。機能追加や更新時に手動でテストは行うけど、すべて網羅することは到底できないため、どうしてもバグが混入してそれに気が付けないことがある。その点、テストコードを書いておけば、テストを実行するだけでしっかり機能が保たれていることを確認できるので便利。

もちろん、テストコードを書いてそれを仕様に合わせてメンテナンスしていくのにもコストがかかるのだけど、ある程度プロジェクトが大きくなったらテストコードを書いたほうがおそらく良い。最近では生成AIなどを使えばテストコード作成の負担も減らせるし。

そんなわけで、私も自身が開発しているWordPressプラグインにPHPUnitによるテストができるようにしてみようと調べ始めたのだけど、情報が意外と少なく、テスト環境構築に苦労したのでここに備忘録として記すことにした。

環境

  • MacOS 15
  • MAMP 7.1
  • PHP 8.2.20 (Apache) / 8.4.3 (cli)
  • MySQL 8.0.35

事前準備

  • WP CLIのインストール
  • mysqlやmysqladminへのパスを通しておく
  • svnのインストール

MySQLへのパスを通す

MAMPのMySQLは/Applications/MAMP/Library/bin/mysql80/binに存在する(バージョン8系の場合)。~/.zshrcに以下のように追記する。(シェルがzshの場合)

export PATH=/Applications/MAMP/Library/bin/mysql80/bin:$PATH

私は当初、~/.bash_profileに追記しており、反映されないことに頭を悩ませていた。(~/.bash_profileはbashをシェルとして使用している場合)

サンプルプラグインのスカフォールドを作成

いきなり開発中のプラグインで試すのは怖いのと、テストに必要なファイル類のサンプルを取得するためにサンプルプラグインのスカフォールドを作成する。

//WordPressインストールディレクトリへ移動
cd wptest
// sample-pluginという名前でプラグインのスカフォールドを作成
wp scaffold plugin sample-plugin

このようなファイル群が作成される。

sample-plugin % tree
.
├── Gruntfile.js
├── bin
│   └── install-wp-tests.sh
├── package.json
├── phpunit.xml.dist
├── readme.txt
├── sample-plugin.php
└── tests
    ├── bootstrap.php
    └── test-sample.php

3 directories, 8 files

テスト用WordPressの作成

通常使用しているWordPressとは別に、テスト環境用のWordPressを作成する必要がある。そのためのスクリプトが./bin/install-wp-tests.shに用意されている。

デフォルトではtmpディレクトリへインストールされるが、何かの拍子に消えることがあるためテスト用WordPressをインストールする場所を今回は指定した。

MAMPのhtdocs配下のwordpress-tests-suitフォルダをテスト環境にする。このフォルダの中にインストールスクリプトを実行すると、テスト用WordPress(wptest-core)とPHPUnit用のファイルが格納されるフォルダ(wptest-tests)が作られるイメージ。

install-wp-tests.shの先頭部分を少し変更する。

  • 引数でDB接続のためのパラメーターを指定する代わりに直接ハードコートする。(設定値は各自の環境に合わせる)
  • 環境変数でWP_TESTS_DIRとWP_CORE_DIRを指定する
#!/usr/bin/env bash

#if [ $# -lt 3 ]; then
#	echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]"
#	exit 1
#fi

export WP_TESTS_DIR="/Applications/MAMP/htdocs/wordpress-tests-suit/wptest-tests"
export WP_CORE_DIR="/Applications/MAMP/htdocs/wordpress-tests-suit/wptest-core"

DB_NAME=${1-wordpress_test}
DB_USER=${2-root}
DB_PASS=${3-root}
DB_HOST=${4-127.0.0.1:8889}
WP_VERSION=${5-latest}
SKIP_DB_CREATE=${6-true}

修正したら、インストールスクリプトを実行。

sample-plugin % ./bin/install-wp-tests.sh

このような感じでフォルダが作成され、テスト用WordPressがインストールされればOK。

wordpress-tests-suit % tree
.
├── wptest-core
└── wptest-tests

svnがインストールされていなかったり、mysqlやmysqladminへのパスが通っていないと失敗する。その場合は原因を解消してから、wordpress-tests-suitディレクトリを手動で削除し、再度実行する。

PHPUnitのインストール

composerでPHPUnitをプラグインの依存関係としてインストールする。この時のポイントがPHPUnitのバージョン。最新バージョンだと動作しないことがあるので、PHPやWordPressに合ったものをインストールする必要がある。今回は9.0を指定してインストールする。

composer require --dev phpunit/phpunit "^9.0"
No composer.json in current directory, do you want to use the one at /Applications/MAMP/htdocs/wptest? [Y,n]? n
./composer.json has been created
Running composer update phpunit/phpunit
Loading composer repositories with package information

composer.jsonが無い場合、親フォルダのを使用するか聞かれる。その場合はnを入力し、カレントディレクトリに作成する。

polyfillsのインストール

composer require --dev yoast/phpunit-polyfills "^3.1"

autoloadの設定

composer.jsonにオートロード設定を追加

{
    "require-dev": {
        "phpunit/phpunit": "^9.0",
        "yoast/phpunit-polyfills": "^3.1"
    },
    "autoload-dev": {
        "classmap": [
            "tests"
        ]
    }
}

phpunit.xmlの作成

phpunit.xml.distphpunit.xmlにリネームし、サンプルテストを除外している部分をコメントアウトする。

<?xml version="1.0"?>
<phpunit
	bootstrap="tests/bootstrap.php"
	backupGlobals="false"
	colors="true"
	convertErrorsToExceptions="true"
	convertNoticesToExceptions="true"
	convertWarningsToExceptions="true"
	>
	<testsuites>
		<testsuite name="testing">
			<directory prefix="test-" suffix=".php">./tests/</directory>
			<!--<exclude>./tests/test-sample.php</exclude>-->
		</testsuite>
	</testsuites>
</phpunit>

環境変数のセット

tests/bootstrap.phpWP_TESTS_DIR環境変数を読み取っているので、WP_TESTS_DIR環境変数に、install-wp-tests.shで指定したWP_TESTS_DIRの値(/Applications/MAMP/htdocs/wordpress-tests-suit/wptest-tests)をセットする。

% export WP_TESTS_DIR=/Applications/MAMP/htdocs/wordpress-tests-suit/wptest-tests

もしくはbootstrap.phpを書き換えて、環境変数から取得するのではなく、変数に直接ディレクトリパスを代入する。

<?php
/**
 * PHPUnit bootstrap file.
 *
 * @package Sample_Plugin
 */

// $_tests_dir = getenv( 'WP_TESTS_DIR' );
$_tests_dir = "/Applications/MAMP/htdocs/wordpress-tests-suit/wptest-tests";

テスト実行

これで準備ができたのでテストを実行してみる。

./vendor/bin/phpunit
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 9.6.22 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 00:00.007, Memory: 38.50 MB

OK (1 test, 1 assertion)

このように、「OK (1 test, 1 assertion)」と表示されれば成功。

WordPressの機能を使ったテストもしてみる。(テストコードはClaudeに作成してもらった)

test-wp-functions.phpをtestsディレクトリへ作成する。

<?php
/**
 * Class WP_Functions_Test
 *
 * @package Sample_Plugin
 */

/**
 * WordPress functions test cases.
 */
class WP_Functions_Test extends WP_UnitTestCase {

    /**
     * 投稿の作成と取得をテスト
     */
    public function test_create_and_get_post() {
        $post_data = array(
            'post_title'   => 'テスト投稿',
            'post_content' => 'これはテスト投稿の本文です。',
            'post_status'  => 'publish',
            'post_author'  => 1
        );

        // 投稿を作成
        $post_id = wp_insert_post($post_data);

        // 投稿が正常に作成されたか確認
        $this->assertNotEquals(0, $post_id);
        $this->assertGreaterThan(0, $post_id);

        // 作成した投稿を取得
        $post = get_post($post_id);

        // 投稿の内容を検証
        $this->assertEquals('テスト投稿', $post->post_title);
        $this->assertEquals('これはテスト投稿の本文です。', $post->post_content);
    }

    /**
     * カスタムフィールド(メタデータ)のテスト
     */
    public function test_post_meta() {
        // テスト用の投稿を作成
        $post_id = $this->factory->post->create();

        // メタデータを追加
        $meta_key = 'test_meta_key';
        $meta_value = 'テストメタ値';
        add_post_meta($post_id, $meta_key, $meta_value);

        // メタデータを取得して検証
        $saved_value = get_post_meta($post_id, $meta_key, true);
        $this->assertEquals($meta_value, $saved_value);

        // メタデータを更新
        $new_value = '新しいメタ値';
        update_post_meta($post_id, $meta_key, $new_value);

        // 更新されたメタデータを検証
        $updated_value = get_post_meta($post_id, $meta_key, true);
        $this->assertEquals($new_value, $updated_value);
    }

    /**
     * カスタム投稿タイプの登録と検証をテスト
     */
    public function test_custom_post_type() {
        // カスタム投稿タイプを登録
        register_post_type('test_cpt', array(
            'public' => true,
            'label'  => 'テストCPT'
        ));

        // カスタム投稿タイプが正しく登録されているか確認
        $post_type_object = get_post_type_object('test_cpt');
        $this->assertNotNull($post_type_object);
        $this->assertEquals('テストCPT', $post_type_object->label);

        // カスタム投稿タイプの投稿を作成
        $post_id = $this->factory->post->create(array(
            'post_type' => 'test_cpt'
        ));

        // 作成した投稿を取得して検証
        $post = get_post($post_id);
        $this->assertEquals('test_cpt', $post->post_type);
    }

    /**
     * タクソノミーとタームのテスト
     */
    public function test_taxonomy_and_terms() {
        // カスタムタクソノミーを登録
        register_taxonomy('test_tax', 'post', array(
            'hierarchical' => true,
            'label' => 'テストタクソノミー'
        ));

        // タームを作成
        $term_data = wp_insert_term('テストターム', 'test_tax');
        $this->assertNotWPError($term_data);
        $term_id = $term_data['term_id'];

        // 投稿を作成
        $post_id = $this->factory->post->create();

        // 投稿にタームを設定
        wp_set_object_terms($post_id, $term_id, 'test_tax');

        // 投稿に設定されたタームを取得して検証
        $terms = wp_get_object_terms($post_id, 'test_tax');
        $this->assertCount(1, $terms);
        $this->assertEquals('テストターム', $terms[0]->name);
    }
}

再度テストを実行してみて、テストの数が増えていれば成功。

./vendor/bin/phpunit
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 9.6.22 by Sebastian Bergmann and contributors.

.....                                                               5 / 5 (100%)

Time: 00:00.045, Memory: 38.50 MB

OK (5 tests, 13 assertions)

特定のファイルだけテスト

現在の状態だと、ファイル名を指定して、特定のファイル内だけのテストができない。

% ./vendor/bin/phpunit tests/test-sample.php
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
Class test-sample could not be found in /Applications/MAMP/htdocs/wptest/wp-content/plugins/sample-plugin/tests/test-sample.php

これはテストファイルの命名規則が合っていないためと思われる(PHPUnitに関する知識が浅い為間違っていたらすみません)

そこで、phpunit.xmlでのdirectoryprefixsuffixを指定を削除し、各テストファイル名をクラス名.phpにする。

<?xml version="1.0"?>
<phpunit
	bootstrap="tests/bootstrap.php"
	backupGlobals="false"
	colors="true"
	convertErrorsToExceptions="true"
	convertNoticesToExceptions="true"
	convertWarningsToExceptions="true"
	>
	<testsuites>
		<testsuite name="testing">
			<directory>./tests/</directory>
		</testsuite>
	</testsuites>
</phpunit>
sample-plugin % tree -L 1 tests
tests
├── SampleTest.php
├── WP_Functions_Test.php
└── bootstrap.php

テストファイル名を指定してテストを実行してみる

% ./vendor/bin/phpunit tests/SampleTest.php 
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 9.6.22 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 00:00.009, Memory: 38.50 MB
OK (1 test, 1 assertion)
この記事を書いた人

PHPが好物な個人開発プログラマ。フリーランスエンジニアとしてWebサービス作ったりしてます。15年の経験を生かしてMENTAでメンターもやってます。WordPressやPHPでお困りのことがあればご相談に乗りますのでDMください。

Follow on SNS
Wordpress
SOHO MIND

Comments

タイトルとURLをコピーしました