EC CUBEのPHPUnitテストをインメモリDBのSQLiteで実行する

この記事は、Zennに投稿した以下の記事の再投稿になります。

はじめに

タイトルの通りですが、インメモリのデータベースを使ってテストを実行します。
最初、ググった通りにやっても上手くいかなくて苦戦したので、備忘録がてら手順をまとめていきます。

対象者

  • EC CUBEのPHPUnitテストを、インメモリのデータベースを使って実行したい方
  • 一部、EC CUBE固有のファイルが出てきますが、Symfonyを使っていれば大体同じ手順でできると思います

動作環境・前提

バージョン
Mac 12.x
PHP 7.4.33
SQLite 3.37.0
Symfony 5.4.21
EC CUBE 4.x

早速実装していく

doctrine.yaml

まず、今回最も重要になるdoctrine.yamlファイルを編集していきます。
ここでは、ドライバーやデータベースのURLなどの設定を行います。
ここが間違っていたら何もかも上手くいかないです。

doctrine:
    dbal:
        connections:
            default:
                driver: 'pdo_sqlite'
                charset: utf8
                url: 'sqlite:///:memory:'
                dbname: 'test_db'

細かく話すとめちゃくちゃ長くなるのでざっくり説明すると、driver: 'pdo_sqlite'でSQLiteを使うこと、url: 'sqlite:///:memory:'でインメモリのデータベースを使うことを指定しています。
詳しくは公式サイトを見ていただくのがいいと思いますので、以下を参考にしてください。

phpunit.xml

続いて、phpunit.xmlを編集します。
編集するのは以下の部分です。

<!-- 省略 -->

    <php>
        <ini name="error_reporting" value="-1" />
        <env name="KERNEL_CLASS" value="Eccube\Kernel" />
        <env name="APP_ENV" value="test" force="true"/>
        <env name="APP_DEBUG" value="1" />
        <env name="SHELL_VERBOSITY" value="-1" />
        <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
        <!-- 以下を追加 -->
        <env name="DATABASE_URL" value="sqlite:///:memory:"/>
        <!-- define your env variables for the test env here -->
    </php>

<!-- 省略 -->

ここでもdoctrine.yamlと同じくDBのURLを記述しておく必要があります。
他はデフォルトのままでも大丈夫だと思います。

また、上記の例でenvとしている部分が、serverとなっている場合もあるようです。
こちらは、ご自身の環境に合わせて書き換えてください。

ここまでできたら、テストでインメモリのDBを使うことができるようになっています。
DBを使うテストが実装済みの方は、このタイミングで一度実行してみてください。

...実行できたでしょうか?
おそらくこの時点だとエラーまみれになりますが、それで正常です。
(逆にエラーにならなかった場合、もしかしたらここまでの手順にミスがあるかもしれません。)

EccubeTestCase.php

先ほどテストを実行した際、エラーメッセージのほとんどが「DBがありません」とか「テーブルがありません」みたいな内容だったのではないでしょうか?
それもそのはず。まだデータベースを作成するコードを書いていませんからね。

ということで、テストの前にデータベースを作成し、スキーマを更新するコードを書いていきます。

ここで注意してほしいのは、ターミナルでデータベースを作成するコマンドを実行しても無駄、ということです。
なぜなら、インメモリのデータベースは、プロセスが終了すると削除されてしまうからです。

$ symfony console doctrine:database:create --env=test
# ↓この時点でプロセスは終了している(DBが作られた次の瞬間、そのDBが削除される)
Created database "test_eccubedb" for connection named default

$ vendor/bin/phpunit --testsuite TestSuite
# 新しいプロセスになるので、エラーになる
EEEEEE

ではどうするのかというと、テストのコードを実行する直前(=同じプロセスの中)で、テスト用のデータベースを初期化するコードをPHPで書くことにします。

以下は、EccubeTestCase.php(名前の通りですが、こちらがEC CUBE固有のファイルです)の一部です。
データベースを扱うテスト全てにこのクラスを継承させることを前提として、setUp()メソッドの中でデータベースを初期化するコードを追記していきます。
先述した2つのファイルでインメモリのデータベースを使うように設定してあるので、ここではその辺りの設定をする必要はありません。勝手にテスト用のデータベースを使ってくれます。

では、まずデータベースを初期化するメソッドを実装します。
こんな感じ。

use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Bundle\FrameworkBundle\Console\Application;

private function initDatabase(KernelInterface $kernel): void
{
    // スキーマの更新
    $entityManager = $kernel->getContainer()->get('doctrine.orm.entity_manager');
    $metaData = $entityManager->getMetadataFactory()->getAllMetadata();
    $schemaTool = new SchemaTool($entityManager);
    $schemaTool->updateSchema($metaData);

    $application = new Application($kernel);
    $application->setAutoExit(false);

    // フィクスチャーをロード
    $input = new ArrayInput(['command' => 'eccube:fixtures:load']);
    $output = new BufferedOutput();
    $application->run($input, $output);
}

Symfony、Doctrineの標準機能を使ってデータベースの初期化を行っています。
それぞれの機能については、公式ドキュメントを読むのが早いと思うので、詳しく知りたい方は以下のサイトを参考にしてください。

公式ドキュメントではないですが、以下のサイトも参考になったので紹介しておきます。

「詳細はいいからとりあえず動くとこまでやりたい」という方は、一旦このまま先に進んでもらって大丈夫です。

このメソッドを、setUp()メソッドで呼び出します。

public function setUp()
{
    parent::setUp();
    $this->client = static::createClient();

    $kernel = self::bootKernel();
    // ここで呼び出す
    $this->initDatabase($kernel);

    $this->entityManager = self::$container->get('doctrine')->getManager();
    $this->eccubeConfig = self::$container->get(EccubeConfig::class);
}

これでそれぞれのテストが実行される直前にデータベースが作れられるようになりました。
早速、このクラスを継承したテストを実装していきましょう。

use Eccube\Tests\EccubeTestCase;

class DoctrineTest extends EccubeTestCase
{
    public function setUp()
    {
        parent::setUp();
    }

    public function test(): void
    {
        // 任意のテストコード
    }
}

任意のテストコードの部分には、データベースとのやり取りを含むコードを実装してみてください(データベース使わないテストだと上手くいっているか判断できないので)。

では、再度ターミナルなどを起動して、このテストを実行してみてください。
今度は「DBがありません」や「テーブルがありません」のようなエラーは出なくなっているはずです。
もしエラーが出てしまった方は、doctrine.yamlにミスがないか、initDatabase()の実装にミスがないか、setUp()メソッドで呼び出せているか、継承するクラスを間違えていないか等々、確認してみてください。

テストが無事にパスしたら成功です!

おまけ

ちなみに、このテストはGithubActionsでもちゃんとパスします。

以下は、私が実際に書いたGithubActionsのコードの一部です。

# 省略
steps:
    - name: Setup PHP 7.4.3
    uses: shivammathur/setup-php@master
    with:
        php-version: '7.4.3'
        tools: phpunit
    
    - name: Checkout
    uses: actions/checkout@master

    - name: Composer cache clear
    id: composer-cache
    run: |
        echo "::set-output name=dir::$(composer config cache-files-dir)"
    - uses: actions/cache@v1
    with:
        path: ${{ steps.composer-cache.outputs.dir }}
        key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
        restore-keys: |
        ${{ runner.os }}-composer-

    - name: Composer Install
    run: composer update --dev --no-interaction -o --apcu-autoloader

    - name: Run test suite
    run: vendor/bin/phpunit --testsuite TestSuite

ブログ用に簡略化して書きましたが、こんな感じのGAを使っています。

参考にしたサイト

Romy(ろみぃ)

歌とゲームと本が好きな人間。ドラゴンになりたい。
不定期で記事を更新していきます。
今後、ブログ以外にもコンテンツ追加していく予定。

© Romy 2024