2022-06-23

Laravel パッケージ開発でのテスト環境整備

Laravel パッケージ開発でテストを行うための環境整備手順を紹介します。

Orchestra Testbench の導入

Orchestra Testbench を利用して Laravel のテスト環境を整備します。

基本的に最新版を入れれば問題ありませんが、古いバージョンの Laravel をベースにテスト環境を構築したい場合、それに対応するバージョンを指定する必要があります。

Laravel と Testbench のバージョン対応についてはドキュメントを参照してください。

console

composer require --dev orchestra/testbench

モジュールの中身は以下のようになっています。testbench-core/laravel ディレクトリがテスト環境のベースとなります。

vendor/orchestra
├── testbench
└── testbench-core
    ├── composer.json
    ├── create-sqlite-db
    ├── drop-sqlite-db
    ├── laravel
    │   ├── app
    │   ├── bootstrap
    │   ├── composer.json
    │   ├── config
    │   ├── database
    │   ├── lang
    │   ├── migrations
    │   ├── public
    │   ├── resources
    │   ├── routes
    │   ├── server.php
    │   ├── storage
    │   └── vendor
    ├── src
    └── testbench

PHP Unit の導入

モジュールをインストールします。

console

composer require --dev phpunit/phpunit

以下の内容で phpunit.xml をディレクトリ直下に配置します。

<testsuites> および <php> タグの内容は必要に応じて書き換え・削除してください。

phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>
<phpunit
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  bootstrap="vendor/autoload.php"
  backupGlobals="false"
  backupStaticAttributes="false"
  colors="true"
  verbose="true"
  convertErrorsToExceptions="true"
  convertNoticesToExceptions="true"
  convertWarningsToExceptions="true"
  processIsolation="false"
  stopOnFailure="false"
  xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
>
  <coverage>
    <include>
      <directory suffix=".php">src/</directory>
    </include>
  </coverage>
  <testsuites>
    <testsuite name="Unit">
      <directory suffix="Test.php">./tests/Unit</directory>
    </testsuite>
    <testsuite name="Feature">
      <directory suffix="Test.php">./tests/Feature</directory>
    </testsuite>
  </testsuites>
  <php>
    <env name="DB_CONNECTION" value="testing"/>
    <env name="APP_KEY" value="base64:2fl+Ktvkfl+Fuz4Qp/A75G2RTiWVA/ZoKZvp6fiiM10="/>
  </php>
</phpunit>

テストディレクトリ・テストファイルの作成

ディレクトリ構成例

上述の phpunit.xml に沿って構成すると以下のようになります。

tests
├── Feature
├── Unit
└── TestCase.php
TestCase.php を記述

namespace およびロードする ServiceProvider は置き換えてください。

TestCase.php

<?php

namespace Ryoluo\SailSsl\Tests;

use Ryoluo\SailSsl\SailSslServiceProvider;

class TestCase extends \Orchestra\Testbench\TestCase
{
    public function setUp(): void
    {
        parent::setUp();
        // additional setup
    }

    protected function getPackageProviders($app)
    {
        return [
            SailSslServiceProvider::class,
        ];
    }

    protected function getEnvironmentSetUp($app)
    {
        // perform environment setup
    }

    protected function tearDown(): void
    {
        // do something before tear down
        parent::tearDown();
    }
}
  • getPackageProviders() メソッドでパッケージのサービスプロバイダを読み込ませます。
  • 各テストの実行前に操作を行いたい場合、setUp() メソッドを利用して任意の処理を実行することができます。
  • また、テスト終了後に処理を行いたい場合は tearDown() メソッドを利用できます。

TestCase.php の記述例

自身が開発した sail-ssl パッケージのテストでは Laravel Sail がインストールされた環境が必要であったため、テスト実行前に Laravel Sail のインストールコマンドを呼び出し、テスト実行後には生成されたファイルを削除する処理を記述しています。

任意のファサードを読み込んだり、$this->app でテスト用の Laravel アプリにアクセスしたりすることができます。

TestCase.php

<?php

namespace Ryoluo\SailSsl\Tests;

use Ryoluo\SailSsl\SailSslServiceProvider;
use Laravel\Sail\SailServiceProvider;
use Illuminate\Support\Facades\Artisan;

class TestCase extends \Orchestra\Testbench\TestCase
{

    public function setUp(): void
    {
        parent::setUp();
        $this->generateFiles();
        Artisan::call('sail:install', ['--with' => 'mysql']);
    }

    private function generateFiles()
    {
        $env = fopen($this->app->basePath('.env'), 'w');
        fclose($env);
        $phpunit = fopen($this->app->basePath('phpunit.xml'), 'w');
        fclose($phpunit);
    }

    protected function getPackageProviders($app)
    {
        return [
            SailServiceProvider::class,
            SailSslServiceProvider::class,
        ];
    }

    protected function tearDown(): void
    {
        $this->deleteFiles();
        parent::tearDown();
    }

    private function deleteFiles()
    {
        unlink($this->app->basePath('.env'));
        unlink($this->app->basePath('phpunit.xml'));
        if (file_exists($this->app->basePath('docker-compose.yml'))) {
            unlink($this->app->basePath('docker-compose.yml'));
        }
        if (file_exists($this->app->basePath('nginx/templates/default.conf.template'))) {
            unlink($this->app->basePath('nginx/templates/default.conf.template'));
            rmdir($this->app->basePath('nginx/templates'));
            rmdir($this->app->basePath('nginx'));
        }
    }
}

テストの記述

上述の TestCase.php を継承します。(Laravel でのテストの書き方については割愛します)

以下はサンプルですので、namespace 等は適宜置き換えてください。

InstallCommandTest.php

<?php

namespace Ryoluo\SailSsl\Tests\Feature;

use Ryoluo\SailSsl\Tests\TestCase;

class InstallCommandTest extends TestCase
{
    public function test_execute_install_command_successfully()
    {
        $this->artisan('sail-ssl:install')
            ->expectsOutput('Nginx container successfully installed in Docker Compose.')
            ->assertSuccessful();
        $dockerCompose = file_get_contents($this->app->basePath('docker-compose.yml'));
        $nginxStub = file_get_contents('stubs/nginx.stub');
        $volumeStub = file_get_contents('stubs/volume.stub');
        $this->assertTrue(str_contains($dockerCompose, $nginxStub));
        $this->assertTrue(str_contains($dockerCompose, $volumeStub));
    }
}

テストディレクトリのロード&テスト実行

最後にテストディレクトリをロードし、テストを実行します。

package.json

{
  ...,

  "autoload": {},

  "autoload-dev": {
    "psr-4": {
       "Ryoluo\\SailSsl\\Tests\\": "tests/"
    }
  }
}

console

composer dump-autoload
./vendor/bin/phpunit

パッケージを公開する場合

.gitattributes でテスト関連ファイルを出力対象外としましょう。

.gitattributes

/tests          export-ignore
phpunit.xml     export-ignore

参考