【PHPUnit】TestRunner拡張で前処理と後処理を追加

PHP

PHPUnit12 / 11 のTestRunnerを拡張して、テスト実行時の全体の前処理と後処理を追加していきます。(制作協力:ChatGPT-4o)

※PHPUnitの公式ドキュメントだけでは判りにくかったため、ChatGPTに手伝ってもらいました。

10. Extending PHPUnit — PHPUnit 12.2 Manual

が、ハルシネーションとの闘いですよね。。嘘ついたり汚いコード提示してきたり、コーディング規約無視したり。その辺は人間と同じだなあと思ったり。。

結局、人間様がレビューやテストをしないとダメなんですよね。その為のスキルアップです。

どこまでやるのか

  • 前処理:「.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に切り替わっていることが判ります。

一応、これで要件は満たしています。

コメント

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