1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WordPress独自テーマファイルを対象にPHPUnitでテストしたい

Last updated at Posted at 2024-08-07

image.png

ユニットテストについて

近年のプログラミングの現場ではユニットテストは当たり前のように求められ、コードのデプロイのサイクルにも取り入れられているものと思います。

これはプログラミング言語を問わず言えることでしょう。

WordPressの場合はどうなのか

一方でWordPressを用いたWebサイト制作においては、ユニットテストの積極的にやっているケースは少ないように思います。
多くのWordpressサイト構築を引き継いできましたが、これまでの経験でテストコードが存在するようなものはありませんでした。

これまで社内でWordPressサイトを構築する場合もtheme-test-data-jaやE2Eテストのようなものは状況に応じて実施するものの、ユニットテストのコード作成は行ってきませんでした。

それでは、WordPressサイト構築においても、PHPUnitでテストしたいとなった場合はどのようにすれば良いでしょうか。
有名どころの言語やフレームワークなどにおいてはユニットテストについて十分な情報が提供されていますが、WordPress独自テーマ作成によるWebサイト構築においては、具体的な最適解が分かりにくい傾向にあります。

そもそも、WordPressサイトの場合は中核となる業務領域を担う(すなわち複雑な業務ロジックの実装が)必要がなく、あくまでプレゼンテーション層レベルの話や単純なCRUDで完結するため、ユニットテストの重要性が求められないということもあるかもしれません。

どちらかというと、機能面よりもデザイン通りに実装されているかの確認の方が関心の対象となるでしょう。

しかし、WordPressと言えどもプログラムの塊であることには違いありませんし、日々の運用においてテストの仕組みが整備されていないと常に既存の機能を壊す心配を抱えることになり、デプロイの心理的負荷は上がります。

この文書では、WordPressの独自テーマ開発においてのPHPUnitの導入と具体的なテストコードについて記載したいと思います。

使用技術

調べたところ、2024年8月現在の標準的なWordPressでのユニットテストの手法としては下記が良さそうです。

導入手順

@wordpress/env

まずは、@wordpress/envの導入します。

npm init
npm i @wordpress/env --save-dev	

.wp-env.jsonを設置します。
ここでは、WP_DEBUGtrueにするのと、マッピングの設定のみを行いました。
dataディレクトリは中にカバレッジレポートのHTMLを出力するために作成しています。)

{
  "config": {
    "WP_DEBUG": true
  },
  "mappings": {
    "../data": "./data",
    "wp-content/themes": "./themes"
  }
} 

wp-envを開始しますが、XDebugを有効にするには1下記のように--xdebugオプションを付加する必要があります。

npx wp-env start --xdebug

テーマファイルを作成

WP CLIを用いてUnderscoresを雛形としたテーマを作成します。

npx wp-env run cli wp scaffold _s sample-theme

作成したテーマへの切り替えとComposerで定義されたライブラリのインストールとPHPUnitの導入を行いましょう。

npx wp-env run cli wp theme activate sample-theme
npx wp-env run cli --env-cwd=wp-content/themes/sample-theme/ composer update
npx wp-env run cli --env-cwd=wp-content/themes/sample-theme/ composer require --dev yoast/wp-test-utils phpunit/phpunit --with-dependencies

PHPUnitの関連ファイル作成

themes/sample-theme/phpunit.xml.dist を設置します。

<?xml version="1.0"?>
<phpunit bootstrap="_tests/bootstrap.php" colors="true">
	<coverage>
		<include>
			<directory suffix=".php">./</directory>
		</include>
		<exclude>
			<directory suffix=".php">./vendor/</directory>
			<directory suffix=".php">./_tests/</directory>
		</exclude>
		<report>
			<text outputFile="php://stdout" showOnlySummary="true" />
		</report>
	</coverage>
	<testsuites>
		<testsuite name="testing">
			<directory suffix="-test.php">./_tests/</directory>
		</testsuite>
	</testsuites>
	<logging />
</phpunit>

themes/sample-theme/_tests/bootstrap.php を設置します。
active_pluginsでテスト時に有効となっている必要があるプラグインを定義します。
ここではAdvanced Custom Fieldsを追加しています。

<?php
use Yoast\WPTestUtils\WPIntegration;

$GLOBALS['wp_tests_options'] = array(
	'active_plugins' => array(
		'advanced-custom-fields-pro/acf.php',
	),
);

require_once dirname( __DIR__ ) . '/vendor/yoast/wp-test-utils/src/WPIntegration/bootstrap-functions.php';

WPIntegration\bootstrap_it();

switch_theme( 'sample-theme' );

簡単にテストを実行できるようにcomposer.jsonscriptsに下記を追加します。

"test": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-html /var/www/data/_coverage-html"

これにより、下記のようにテストを実行することができます。

npx wp-env run tests-cli --env-cwd=wp-content/themes/sample-theme/ composer test

まだコマンドが長すぎますか?
composer.jsonscriptsで定義したことで、tests-cliインスタンス上のwp-content/themes/sample-theme/へ移動してcomposer testするだけに簡略化できましたが、この処理を一つのコマンドにするとまだ長すぎると感じるかもしれません。
その場合は、package.jsonscriptsも併せて定義すると良いでしょう。

"test": "wp-env run tests-cli --env-cwd=wp-content/themes/sample-theme/ composer test"

これでテストの実行はnpm run testで呼び出せるようになりました。

テストコード例

このようなテストコードとなります。

themes/sample-theme/_tests/class-basic-test.php

<?php
use Yoast\WPTestUtils\BrainMonkey\TestCase;

class BasicTest extends TestCase {
	protected function set_up() {
		parent::set_up();
	}

	public function testThemeName() {
		$my_theme = wp_get_theme();
		$this->assertEquals( 'Sample-theme', $my_theme->get( 'Name' ) );
	}
}

もし、functions.phpでカスタマイズされた機能や関数をテストする場合は、set_upの中で読み込むと良いでしょう。

例えば、下記のようなコードの場合です。

テスト対象となるコード:

// Yoast SEOのパンくずをカスタマイズ
function my_wpseo_breadcrumb_links( $breadcrumbs ) {
	// 具体的な実装は省略
}
add_filter( 'wpseo_breadcrumb_links', 'my_wpseo_breadcrumb_links', 10, 1 );

テストコード:

<?php

use Yoast\WPTestUtils\BrainMonkey\TestCase;

class WpseoTest extends TestCase {
	protected function set_up() {
		parent::set_up();
		require_once get_template_directory() . '/functions.php';
	}
 
	public function testWpseo() {
		// ここでは簡易的な例示のため配列かどうかのみをテストしていますが
		// 実際にはもう少し複雑なテストになるでしょう。
		$after_breadcrumbs = my_wpseo_breadcrumb_links( array() );
		$this->assertIsArray( $after_breadcrumbs );
	}
}

テンプレートファイルのテスト

functions.phpで定義されているような関数のテストについては上記のやり方で十分でしょう。一方で、archive.phpsingle.phpのようなテーマファイルのテストも自動化したくなります。
(厳密にいうと、機能単体のテストではなくなるので、統合テストに近いものとなりますが)

Underscoresのarchive.phpテンプレートを例にテストを記載してみましょう。

テストコード:

themes/sample-theme/_tests/class-archive-test.php

<?php
use Yoast\WPTestUtils\BrainMonkey\TestCase;

class ArchiveTest extends TestCase {
	protected function set_up() {
		parent::set_up();
		require_once get_template_directory() . '/functions.php';
	}

	public function testEmptyPostsArchivePage() {
		global $wp_query;

		$wp_query = new WP_Query( array( 'post_type' => 'post' ) );

		ob_start();
		include get_template_directory() . '/archive.php';
		$output = ob_get_clean();
		$this->assertDoesNotMatchRegularExpression( '/error/i', $output );
	}

	public function testHasPostsArchivePage() {
		global $wp_query;

		// Generate dummy data.
		for ( $i = 1; $i <= 5; $i++ ) {
			wp_insert_post(
				array(
					'post_title'   => 'SamplePost' . $i,
					'post_type'    => 'post',
					'post_status'  => 'publish',
				)
			);
		}

		$wp_query = new WP_Query( array( 'post_type' => 'post' ) );

		ob_start();
		include get_template_directory() . '/archive.php';
		$output = ob_get_clean();
		$this->assertDoesNotMatchRegularExpression( '/error/i', $output );
	}
}

カバレッジレポート:

data/_coverage-html/archive.php.html

image.png

WPTestUtilsは実行時にデータがリセットされるので必要に応じてwp_insert_postなどでデータを追加してからテストを行っているのと、必要に応じて$wp_queryなどのグローバル変数の書き換えがポイントとなります。

testEmptyPostsArchivePageではif ( have_posts() ) :falseとなるテスト、testHasPostsArchivePageではtrueとなるテストを記載しています。

ここでは単純に出力HTMLにerrorという文字列が含まれていないか確認していますが、実際には想定した数の要素が出ているかなどをテストすると良いでしょう。

次に、single.phpテンプレートのテスト例を記載します。

themes/sample-theme/_tests/class-single-test.php

<?php
use Yoast\WPTestUtils\BrainMonkey\TestCase;

class SingleTest extends TestCase {
	protected function set_up() {
		parent::set_up();
		require_once get_template_directory() . '/functions.php';
	}

	public function testSinglePost() {
		global $wp_query;

		$insert_data = array(
			'post_title'   => 'SamplePost',
			'post_type'    => 'post',
			'post_status'  => 'publish',
		);
		$wp_insert_post = wp_insert_post( $insert_data );

		$wp_query = new WP_Query( array( 'post_id' => $wp_insert_post ) );

		ob_start();
		include get_template_directory() . '/single.php';
		$output = ob_get_clean();
		$this->assertDoesNotMatchRegularExpression( '/error/i', $output );

		$removed_line_breaks = str_replace( array( "\r\n", "\r", "\n" ), '', $output );
		$this->assertMatchesRegularExpression( '/<h2[^>]*>.*>' . $insert_data['post_title'] . '<.*<\/h2>/', $removed_line_breaks );
	}

	public function testSinglePage() {
		global $wp_query;

		$insert_data = array(
			'post_title'   => 'SamplePage',
			'post_type'    => 'page',
			'post_status'  => 'publish',
		);
		$wp_insert_post = wp_insert_post( $insert_data );

		$wp_query = new WP_Query( array( 'page_id' => $wp_insert_post ) );

		ob_start();
		include get_template_directory() . '/single.php';
		$output = ob_get_clean();
		$this->assertDoesNotMatchRegularExpression( '/error/i', $output );

		$removed_line_breaks = str_replace( array( "\r\n", "\r", "\n" ), '', $output );
		$this->assertMatchesRegularExpression( '/<h1[^>]*>\s*' . $insert_data['post_title'] . '\s*<\/h1>/', $removed_line_breaks );
	}
}

このテストでは、投稿と固定ページのダミーデータを作成し、HTML上にerrorという文字列が含まれていないかと、見出しタグに想定どおりのタイトルが入っているかを確認しています。

まとめ

Symfonyなどのフレームワークでのユニットテストに慣れていると、グローバス変数を上書きしたり、投入データがフィクスチャとしてうまく分離できていなかったりと少し違和感がありますが、色々と手を動かした結果このような落としどころになりました。
もう少し良い方法が見つかればまた記事としてまとめたいと思います。

参考情報

  1. XDebugを有効にしているのはコードカバレッジレポートを出力するためです。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?