私たち、楽天グループ コマース&マーケティングカンパニー レジャープロダクト部( LPD)「楽天toto」開発チームでは、「効率的で安定したシステムを作る」ことを常に意識して開発を進めています。今回は「単体テストの並列実行により実行時間の短縮」を試みましたので、ご紹介します。
「単体テストの実行時間を短縮したい」「PHPのparatestライブラリに興味がある」という方は、参考にしてください。
私たちのチームでは、単体テストの実装を積極的に行っています。具体的には「新規で追加・変更を行った部分」「ゴールデンパス」に関してはカバレッジ(網羅率)100%にするなどのルールを設定しています。これにより問題の事前の検知が可能となり、既存の機能を担保しつつ新たな機能を開発することができるようになりました。
しかし、一方で、単体テストが増えたことにより、実行時間が1回につき約20分かかるようになってしまいました。テスト結果が出るまでに毎回20分も待ってから修正、マージをするとなると、開発に集中できず生産的とはいえません。そこで、この問題を解決するため、並列実行の実装を行いました。
「Paratest」について
現在私たちのチームでは、PHP Unitを単体テストに使用しています。それを考慮して並列実行について調査したところ、以下の「paratest」というライブラリが使用できそうという結論になりました。
「paratest」のインストール、実行は以下の様に簡単なコマンドで行えます。
インストール↓
実行↓
テストスイート(testsuite)の並列実行
具体的な実装としては、各テストスイート(testsuite)を並列実行することにしました。「paratest」はparallel-suiteというオプションがあり、下記のようなコマンドを使用することで簡単にテストスイート(testsuite)を並列実行できます。
before/afterは以下の様なイメージになります。
php artisan コマンドも使用可能
Laravelのphp artisanコマンドでも、「paratest」をラップしたコマンドを使用できます。
上記のコマンドのように--parallelオプションをつけるだけで並列実行できます。
Laravelを使用していればシンプルなコマンドで実行できるので便利なのですが、今回は不要なトラブルを避けるために、元々のライブラリのコマンドをそのまま使用することにしました。
問題
「paratest」を実行するところまでは、スムーズに行えました。しかし、実行時にほとんどのテストケースで、失敗していることに気づきました。調べてみると、原因はDBのコンフリクトでした。
通常のPHP Unitであれば、1プロセスに対して1DBを参照するという形になります。しかし、「paratest」の実行では複数プロセスで、1つのDBを参照してしまうため、他のプロセスのテストとデータが干渉してしまい、エラーが起きていました。
解決策
「paratest」では、各プロセスでTEST_TOKENという環境変数が設定される仕様です。今回はこのTEST_TOKENを使用して、DBを切り替えることにしました。変更点は以下の通りです。
- dbname_test_1, dbname_test_2, ...のような形でテスト用のDBをプロセス数用意する。
- 各プロセスを実行する前に行われるPHP artisan migrateコマンドの前にTEST_TOKENを使用して、DB名を書き換える。
- test setup部分で、database接続のconfigをと同様の方法で書き換える。
これらの変更を行うことでDB干渉の問題は無事解決し、「paratest」を実行できました。
「paratest」を実行した結果、今まで20分程度かかっていたテスト実行を約10分に短縮できました。
一番時間がかかっていたtestsuiteが10分ほどだったので、こちらも期待通りです。
「paratest」を実行するだけで、単体テスト実行時間を短縮できました。testsuiteの分け方をより工夫することで、さらに実行時間を短縮できると考えます。
次は各testsuiteを見直して細分化することで、より実行時間を短縮したいと思います。
私たちと一緒に働きませんか?
コマース & マーケティングカンパニー レジャープロダクト部(LPD)では、新たなサービス開発から日々の運用・改善まで、一緒に働く仲間を募集中!エンジニアやプロダクトマネージャーなど、幅広い職種で募集しています。ご応募をお待ちしております。