だいぶ以前に Qiita に ParaTest で PHPUnit を並列実行する記事を書いていたのですが、
よく考えたら別に Docker なんて必要なくて、ひとつの MySQL インスタンスに複数のデータベースを作ればいいだけでした。なんとなく Docker を使ってみたかっただけじゃないかな、この時期。
また ParaTest の TEST_TOKEN
を使えばそもそも変なハックしなくても普通に使用するデータベース切り替えられます。ParaTest の README にそのまんま書かれています。
<?php if (getenv('TEST_TOKEN') !== false) { // Using paratest $dbname = 'testdb_' . getenv('TEST_TOKEN'); } else { $dbname = 'testdb'; }
TEST_TOKEN
が追加されたのが下記のリリースなので、記事を書いた当初はまだ TEST_TOKEN
は実装されてなかったようです。
さっそく試してみました。使用したコード類は https://github.com/ngyuki-sandbox/php-paratest-with-db にあります。
普通にテストを実行すると10秒ぐらいかかります。
$ vendor/bin/phpunit PHPUnit 9.5.4 by Sebastian Bergmann and contributors. .......... 10 / 10 (100%) Time: 00:10.232, Memory: 6.00 MB OK (10 tests, 10 assertions)
普通に ParaTest を実行するとテストケースごとにデータベースのフィクスチャをざっくざく入れてるとめちゃめちゃ競合します。
$ vendor/bin/paratest -p 5 Running phpunit in 5 processes with /work/vendor/phpunit/phpunit/phpunit Configuration read from /work/phpunit.xml.dist EE.EE.EEEE 10 / 10 (100%) Time: 00:02.230, Memory: 6.00 MB There were 8 errors: 1) Test\Sample0Test::test PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1' for key 't.PRIMARY' ...snip... FAILURES! Tests: 10, Assertions: 2, Errors: 8.
接続するデータベース名について、環境変数 TEST_TOKEN
をデータベース名のサフィックスに追加します。
<?php $host = getenv('MYSQL_HOST'); $port = getenv('MYSQL_PORT'); $username = getenv('MYSQL_USER'); $password = getenv('MYSQL_PASSWORD'); $dbname = getenv('MYSQL_DATABASE'); $charset = 'utf8mb4'; $token = getenv('TEST_TOKEN'); if ($token !== false) { $dbname .= $token; }
TEST_TOKEN
には ParaTest での実行時に並列実行されるプロセスごとに1から始まる連番が設定されます。
並列数の分だけデータベースを作成します。マイグレーション適用済のデータベースからダンプ→リストアが手っ取り早です。
# マイグレーションを実行 ... 使用するツールに応じて適切なコマンドに置き換え cat database/*.sql | mysql -v "$MYSQL_DATABASE" # データベースをダンプ mysqldump -h "$MYSQL_HOST" "$MYSQL_DATABASE" -r dump.sql # 連番サフィックスのデータベースへの権限を付与 mysql -v -e "grant all on \`${MYSQL_DATABASE}%\`.* to $MYSQL_USER@'%'" # 連番サフィックスのデータベースを作成 seq 5 | xargs -P0 -i mysql -v -e 'create database if not exists test{}' # 連番サフィックスのデータベースへダンプを流し込み seq 5 | xargs -P0 -i mysql -e 'source dump.sql' 'test{}' # テストを実行 vendor/bin/paratest -p 5
10秒かかっていたテストが2秒で終わるようになりました。
Running phpunit in 5 processes with /work/vendor/phpunit/phpunit/phpunit Configuration read from /work/phpunit.xml.dist .......... 10 / 10 (100%) Time: 00:02.216, Memory: 6.00 MB OK (10 tests, 10 assertions)
さいごに
かなり極端な例なので、実際の効果の程は実行環境や並列数によりけりです。手元の実案件で試してみたところ、DBを使うテストが4並列で半分ぐらいの時間で終わるようになりました。