PHPUnit12 / 11 のTestRunnerを拡張して、テスト実行時の全体の前処理と後処理を追加していきます。(制作協力:ChatGPT-4o)
※PHPUnitの公式ドキュメントだけでは判りにくかったため、ChatGPTに手伝ってもらいました。
が、ハルシネーションとの闘いですよね。。嘘ついたり汚いコード提示してきたり、コーディング規約無視したり。その辺は人間と同じだなあと思ったり。。
結局、人間様がレビューやテストをしないとダメなんですよね。その為のスキルアップです。
どこまでやるのか
- 前処理:「.env」を「.env.org」に退避。「.env.testing」を「.env」にコピー
- 後処理:「.env.org」を「.env」に戻す
※Laravelの「artisan test」でやっているような処理です。
前提条件
- PHP8.2以降(PHPUnit 12の場合はPHP8.3以降)
- PHPUnit 11 / 12
- Composer v2
- PHPUnit以外のフレームワークや外部ライブラリは使わない
ファイル構成
[プロジェクト]
├─ tests/
│ ├─ Env/EnvTest.php ・・・.envがtestingに切り替わっているかのテスト
│ └─ Extensions/ ・・・PHPUnit拡張ファイル置き場
│ ├─ Subscribers/
│ │ ├─ TestRunnerStartedSubscriber.php ・・・前処理
│ │ └─ TestRunnerFinishedSubscriber.php ・・・後処理
│ └─ TestRunnerExtension.php ・・・TestRunner拡張
├─ .env ・・・環境ファイル(コピー先)
├─ .env.develop ・・・万が一用の予備
├─ .env.testing ・・・テスト環境用(コピー元)
├─ composer.json ・・・Composer設定ファイル
└─ phpunit.xml ・・・PHPUnit設定
TestRunner拡張を作成
▼「tests/Extensions/TestRunnerExtension.php」:TestRunner拡張本体
<?php
declare(strict_types=1);
namespace Tests\Extensions;
use PHPUnit\Runner\Extension\Extension;
use PHPUnit\Runner\Extension\Facade;
use PHPUnit\Runner\Extension\ParameterCollection;
use PHPUnit\TextUI\Configuration\Configuration;
use Tests\Extensions\Subscribers\TestRunnerFinishedSubscriber;
use Tests\Extensions\Subscribers\TestRunnerStartedSubscriber;
final class TestRunnerExtension implements Extension
{
public function bootstrap(Configuration $configuration, Facade $facade, ParameterCollection $parameters): void
{
$facade->registerSubscriber(new TestRunnerStartedSubscriber);
$facade->registerSubscriber(new TestRunnerFinishedSubscriber);
}
}
▼「tests/Extensions/Subscribers/TestRunnerStartedSubscriber.php」:前処理
<?php
declare(strict_types=1);
namespace Tests\Extensions\Subscribers;
use PHPUnit\Event\TestRunner\ExecutionStarted;
use PHPUnit\Event\TestRunner\ExecutionStartedSubscriber;
class TestRunnerStartedSubscriber implements ExecutionStartedSubscriber
{
public const HTTP_TEST_ENV_ACTIVE_PATH = __DIR__ . "/../../../.env";
public const HTTP_TEST_ENV_ORG_PATH = __DIR__ . "/../../../.env.org";
public const HTTP_TEST_ENV_TEST_PATH = __DIR__ . "/../../../.env.testing";
public function notify(ExecutionStarted $event): void
{
echo "[RUNNER START] PHPUnit 実行開始\n";
// .env の退避
rename(static::HTTP_TEST_ENV_ACTIVE_PATH, static::HTTP_TEST_ENV_ORG_PATH);
// テスト用 .env ファイルの設置
copy(static::HTTP_TEST_ENV_TEST_PATH, static::HTTP_TEST_ENV_ACTIVE_PATH);
}
public function subscribedTo(): string
{
return ExecutionStarted::class;
}
}
▼「tests/Extensions/Subscribers/TestRunnerFinishedSubscriber.php」:後処理
<?php
declare(strict_types=1);
namespace Tests\Extensions\Subscribers;
use PHPUnit\Event\TestRunner\ExecutionFinished;
use PHPUnit\Event\TestRunner\ExecutionFinishedSubscriber;
class TestRunnerFinishedSubscriber implements ExecutionFinishedSubscriber
{
public const HTTP_TEST_ENV_ACTIVE_PATH = __DIR__ . "/../../../.env";
public const HTTP_TEST_ENV_ORG_PATH = __DIR__ . "/../../../.env.org";
public function notify(ExecutionFinished $event): void
{
echo "[RUNNER END] PHPUnit 実行終了\n";
// 元の .env に戻す
rename(static::HTTP_TEST_ENV_ORG_PATH, static::HTTP_TEST_ENV_ACTIVE_PATH);
}
public function subscribedTo(): string
{
return ExecutionFinished::class;
}
}
XML設定
▼「phpunit.xml」:上記のTestRunner拡張を適用します。
※「<extensions></extensions>」の子要素「<bootstrap />」の「class」属性にクラス名を記述します。
<phpunit
colors="true"
testdox="true"
testdoxSummary="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
displayDetailsOnTestsThatTriggerErrors="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnPhpunitDeprecations="true"
>
<testsuites>
<testsuite name="All">
<directory>tests</directory>
</testsuite>
</testsuites>
<extensions>
<bootstrap class="Tests\Extensions\TestRunnerExtension"/>
</extensions>
</phpunit>
autoload設定
▼「composer.json」:autoload-devの設定を追記します。
{
"require-dev": {
"phpunit/phpunit": "^12"
},
"autoload-dev": {
"psr-4": {
"Tests\\Extensions\\": "tests/Extensions/"
}
}
}
.envファイル
▼「.env」
APP_ENV=develop
APP_ADMINISTRATOR=developer@example.com
▼「.env.develop」
APP_ENV=develop
APP_ADMINISTRATOR=developer@example.com
▼「.env.testing」
APP_ENV=testing
APP_ADMINISTRATOR=test@example.com
テストコード
▼「tests/Env/EnvTest.php」:「.env」を読み込んで、「.env.testing」の内容かを判定
<?php
declare(strict_types=1);
namespace Tests\Env;
use PHPUnit\Framework\TestCase;
final class EnvTest extends TestCase {
public const ENV_ACTIVE_PATH = __DIR__ . "/../../.env";
public function testEnvFileIsForTesting(): void {
$env = file_get_contents(static::ENV_ACTIVE_PATH);
$this->assertStringContainsString("APP_ENV=testing", $env);
$this->assertStringContainsString("APP_ADMINISTRATOR=test@example.com", $env);
}
}
テスト実行
では、テストを実行してみます。
vendor/bin/phpunit

「EnvTest」実行前に「実行開始」が出力されて、「EnvTest」実行後に「実行終了」が出力されています。
「EnvTest」もパスしているので、「.env」の内容がtestingに切り替わっていることが判ります。
一応、これで要件は満たしています。
コメント