EC CUBEのPHPUnitテストをインメモリDBのSQLiteで実行する
タイトルの通りですが、インメモリのデータベースを使ってテストを実行します。
最初、ググった通りにやっても上手くいかなくて苦戦したので、備忘録がてら手順をまとめていきます。
対象者
- 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:'
でインメモリのデータベースを使うことを指定しています。
詳しくは公式サイトを見ていただくのがいいと思いますので、以下を参考にしてください。
- https://symfony.com/doc/current/the-fast-track/ja/17-tests.html
- https://symfony.com/doc/current/reference/configuration/doctrine.html
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の標準機能を使ってデータベースの初期化を行っています。
それぞれの機能については、公式ドキュメントを読むのが早いと思うので、詳しく知りたい方は以下のサイトを参考にしてください。
- https://www.doctrine-project.org/projects/doctrine-orm/en/2.15/reference/tools.html#database-schema-generation
- https://symfony.com/doc/current/console/command_in_controller.html
公式ドキュメントではないですが、以下のサイトも参考になったので紹介しておきます。
「詳細はいいからとりあえず動くとこまでやりたい」という方は、一旦このまま先に進んでもらって大丈夫です。
このメソッドを、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を使っています。
参考にしたサイト
- https://www.doctrine-project.org/projects/doctrine-orm/en/2.15/reference/tools.html#database-schema-generation
- https://www.doctrine-project.org/projects/doctrine-data-fixtures/en/latest/how-to/loading-fixtures.html
- https://symfony.com/doc/current/the-fast-track/ja/17-tests.html#yunittotesutowo-shuku
- https://dev.to/vikbert/tips-for-testing-the-database-in-symfony-application-1pd5
- https://symfony.com/doc/current/console/command_in_controller.html
株式会社 カラビナテクノロジーは「命綱や支点を素早く確実に繋ぐカラビナ。そんなカラビナのような役割をテクノロジーで実現したい」という想いのもと、福岡で設立。 主にシステム開発・アプリ開発・ Webサイト制作を行っています。採用情報→karabiner.tech/recruit/requirements/
Discussion
以前、EC-CUBE本体でもインメモリで実行しようと取り組んだのですが、性能差がほとんど無かったのと、 PostgreSQL/MySQLと同一のテストコード動作しないため、現在の実装になっています。
もしインメモリの方が速いようであれば採用していきたいのですが、実際のところいかがでしょうか?
このテストを使っていた環境が現在手元にないため記憶ベースにはなるんですが、仰る通り性能差はほぼなかったと思います。
GAでのテスト実行が少しでも早くならないかと試しにインメモリでやってみた、という背景があるんですが、気持ち早くなったかな、くらいで大きな変化はなかったと記憶しています。