グレンジでサーバー・インフラを担当している村田です。
グレンジ Advent Calendar 2019の1日目の記事です。
グレンジでPHPUnitを使ってテストを行っていますが、
テストが増えるにつれてテストに時間がかかることが問題になりました。
テストをテストファイル単位やディレクトリ単位などで分け、PHPUnitを並列実行すればそれだけ早く終わらせることができます。
しかし、その結果ファイルのマージはPHPUnit単体では不可能です。
テスト結果のマージする方法をまとめた記事になります。
前提条件
JUnit XMLファイルとカバレッジファイルを.build/junitと.build/phpcovに出力していることを前提としています。
% cd /home/username/src/tests/
% phpunit a_test.php \
--log-junit=/home/username/src/.build/junit/a.xml \
--coverage-php=/home/username/src/.build/phpcov/a.cov
% phpunit b_test.php \
--log-junit=/home/username/src/.build/junit/b.xml \
--coverage-php=/home/username/src/.build/phpcov/b.cov
% tree /home/username/src/.build
/home/username/src/.build
├── junit
│ ├── a.xml
│ └── b.xml
└── phpcov
├── a.cov
└── b.cov
JUnit XMLファイルのマージ
PHPUnit作者のSebastian Bergmannさんがmerge-phpunit-xml.phpという、JUnit XMLファイルをマージするスクリプトを公開しています。
https://gist.github.com/sebastianbergmann/4405658
しかし、このコードは
- マージするとテスト結果が重複される
- ライブラリをrequireしていないのでこのままでは実行できない
という優しくない状態です。
なので、結果を重複しないように修正して、ライブラリはcomposerで取得できるように手を加えたGitHubリポジトリがこちら。
https://github.com/muratahiroshi/merge-phpunit-xml
そのDockerイメージも作成。
https://hub.docker.com/r/hiroshimurata/merge-phpunit-xml
次のように実行すると.build/junitのJUnit XMLファイルたちがマージされて、junit_merge.xmlが出来上がります。
% docker run -v /home/username/src:/data \
hiroshimurata/merge-phpunit-xml \
/data/.build/junit \
/data/junit_merge.xml
カバレッジファイルをマージしてCloverとHTMLで書き出す
PHPのカバレッジファイルはphpcovでマージできます。これもSebastian氏作。
https://github.com/sebastianbergmann/phpcov
Pharでも配布されていますが、GCPのCloud Buildでさくっと使えるようにこれもDockerイメージ作成。
https://hub.docker.com/r/hiroshimurata/phpcov
ただし、Dockerで実行するときは要注意。
phpcovのマージ処理はソースコードを参照するので、Dockerから参照できるようマウントしてあげます。
また、Dockerにマウントするソースコードのパスは、PHPUnit実行時と同じパスにしないといけません。
前提条件のパスに合わせて、Dockerのphpcovを実行する例。Clover形式のXMLやHTMLで書き出せたりします。
% docker run -v /home/username/src:/home/username/src hiroshimurata/phpcov:6.0.0 \
merge /home/username/src/.build/phpcov/ \
--clover /home/username/src/.build/clover.xml \
--html /home/username/src/.build/html
できたindex.htmlに何一つ結果が表示されていないようなら、ソースコードの参照、パスの位置関係に失敗しています。
covファイルの中身を見るとパスを確認できるので、それに合わせると良いです。
<?php
$coverage = new SebastianBergmann\CodeCoverage\CodeCoverage;
$coverage->setData(array (
'/home/username/src/app/library/A.php' => # このパス
array (
Cloud BuildでPHPUnitの実行からphpcovまでまとめて行うなら、パスの問題は生じないと思います。
まとめ
一時期、2時間近くかかっていた全テストも並列分散させて数十分に抑えることができました。
CIは手早く回せるだけ快適&緊急対応時の変な汗も減らせて快適。
おまけ boxで作るPhar
Dockerのmerge-phpunit-xmlはboxを使ってPharを作成しています。
FROM composer:1.8.5
WORKDIR /tmp/merge-phpunit-xml
ADD merge-phpunit-xml.php composer.json composer.lock box.json /tmp/merge-phpunit-xml/
RUN /usr/bin/composer install --no-dev
ADD https://github.com/humbug/box/releases/download/3.7.0/box.phar /usr/bin/box
RUN php /usr/bin/box compile
FROM php:7.2.15
COPY --from=0 /tmp/merge-phpunit-xml/merge-phpunit-xml.phar /usr/bin/merge-phpunit-xml
ENTRYPOINT ["/usr/bin/merge-phpunit-xml"]
box.jsonを書いてコンパイルすれば、1ファイルにまとめられるのでスッキリ。
{
"alias": "merge-phpunit-xml.phar",
"files": [
"merge-phpunit-xml.php",
"vendor/autoload.php"
],
"main": "merge-phpunit-xml.php",
"output": "merge-phpunit-xml.phar",
"compression": "GZ"
}
とても重要なおまけ告知
本の執筆に参加しました。
ゲーム開発が変わる!GCPゲームインフラ実践ガイド (NextPublishing)