CircleCI で PHPUnit を並列実行する
CircleCI のテストの並列実行は、テストファイルをインスタンス毎に振り分けることで実現します。しかしながら、PHPUnit のコマンドライン (phpunit
) は複数のファイル名を受け取って実行するということができません。
circleci tests glob "tests/**/*Test.php" | circleci tests split
# => インスタンスに割り当てられたテストファイル名のリスト (スペース区切)
# => そのままでは phpunit で実行できない...
そこで、ファイル名のリストを引数にとって、そのテストを実行するための phpunit.xml
(phpunit-partial.xml
) を生成するツールを書いて対応しました。
phpunit-xml-gen.php
phpunit-xml-gen.php
<?php
$files = array_slice($argv, 1);
$xml = new DOMDocument();
$xml->load(__DIR__ . '/phpunit.xml');
$testsuite = $xml->createElement('testsuite');
$testsuite->setAttribute('name', 'partial');
foreach ($files as $file) {
$testsuite->appendChild($xml->createElement('file', $file));
}
$testsuites = $xml->createElement('testsuites');
$testsuites->appendChild($testsuite);
$phpunit = $xml->getElementsByTagName('phpunit')->item(0);
$phpunit->replaceChild($testsuites, $phpunit->getElementsByTagName('testsuites')->item(0));
$xml->save(__DIR__ . '/phpunit-partial.xml');
exit(0);
- 同じディレクトリ内にある
phpunit.xml
の <testsuites />
要素内を、渡されたファイル名を実行する <testsuite />
に置き換えて、phpunit-partial.xml
を生成する。
- プロジェクトの
phpunit.xml
から生成するので、実行ファイル以外の諸々の設定を引き継げる。
- そのまま
phpunit
を実行するスクリプトにしても良かったが、実行時にオプションを渡したいのでそれはやめた。
<?php
$files = array_slice($argv, 1);
$xml = new DOMDocument();
$xml->load(__DIR__ . '/phpunit.xml');
$testsuite = $xml->createElement('testsuite');
$testsuite->setAttribute('name', 'partial');
foreach ($files as $file) {
$testsuite->appendChild($xml->createElement('file', $file));
}
$testsuites = $xml->createElement('testsuites');
$testsuites->appendChild($testsuite);
$phpunit = $xml->getElementsByTagName('phpunit')->item(0);
$phpunit->replaceChild($testsuites, $phpunit->getElementsByTagName('testsuites')->item(0));
$xml->save(__DIR__ . '/phpunit-partial.xml');
exit(0);
phpunit.xml
の <testsuites />
要素内を、渡されたファイル名を実行する <testsuite />
に置き換えて、phpunit-partial.xml
を生成する。phpunit.xml
から生成するので、実行ファイル以外の諸々の設定を引き継げる。phpunit
を実行するスクリプトにしても良かったが、実行時にオプションを渡したいのでそれはやめた。これを以下のような感じで実行すると(要 phpunit.xml
)、
$ echo tests/FooTest.php tests/BarTest.php | xargs php ./phpunit-xml-gen.php
こんな感じの phpunit-partial.xml
が生成されます。このファイルを phpunit
の -c
(--configuration
) オプションに渡してやれば、任意のテストファイルだけ実行できます。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="partial">
<file>tests/FooTest.php</file>
<file>tests/BarTest.php</file>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
</whitelist>
</filter>
<php>
<env name="APP_ENV" value="testing"/>
</php>
</phpunit>
CircleCI の設定
.circleci/config.yml
jobs:
phpunit:
parallelism: 4
steps:
- run:
command: |
circleci tests glob "tests/Feature/**/*Test.php" "tests/Unit/**/*Test.php" | circleci tests split --split-by=timings | xargs php ./phpunit-xml-gen.php
php ./vendor/bin/phpunit \
--verbose \
--log-junit tmp/test-reports/phpunit/logfile.xml \
--coverage-clover tmp/coverage-${CIRCLE_NODE_INDEX}.xml \
--configuration phpunit-partial.xml
- store_test_results:
path: tmp/test-reports
- persist_to_workspace:
root: /var/www/html
paths:
- tmp/coverage-*.xml
store_test_results
jobs:
phpunit:
parallelism: 4
steps:
- run:
command: |
circleci tests glob "tests/Feature/**/*Test.php" "tests/Unit/**/*Test.php" | circleci tests split --split-by=timings | xargs php ./phpunit-xml-gen.php
php ./vendor/bin/phpunit \
--verbose \
--log-junit tmp/test-reports/phpunit/logfile.xml \
--coverage-clover tmp/coverage-${CIRCLE_NODE_INDEX}.xml \
--configuration phpunit-partial.xml
- store_test_results:
path: tmp/test-reports
- persist_to_workspace:
root: /var/www/html
paths:
- tmp/coverage-*.xml
JUnit XML 形式のファイルを CircleCI に保存するように設定すると、テストファイルの実行時間から良い感じに各インスタンスに振り分けてくれるようになるそうです。設定できると、上記のように Test Summary が表示されるようになります。
PHPUnit の場合は、--log-junit
オプションで生成できるので、それを store_test_results
で保存するようにした上で、テスト振り分けのコマンドの引数に --split-by=timings
を渡してやります。
php ./vendor/bin/phpunit \
--log-junit tmp/test-reports/phpunit/logfile.xml # ← これ
circleci tests split --split-by=timings
カバレッジ
テストを実行したインスタンス内で生成されるカバレッジの結果は、そのインスタンスで実行したテストの分しか反映されません。
Coveralls などのサービスを利用する場合、すべてのインスタンスのテストの実行が完了するのを待って、別の job で各インスタンスのカバレッジをまとめて送ってやる必要があります。
PHPUnit の場合は、phpunit
の実行時に生成するカバレッジのファイル名をインスタンス毎に分かれるようにして、persist_to_workspace
/ attach_workspace
で良い感じに集めてやります。
php ./vendor/bin/phpunit \
--coverage-clover tmp/coverage-${CIRCLE_NODE_INDEX}.xml # ← これ
jobs:
report:
steps:
- attach_workspace:
at: /var/www/html
- run:
# NOTE: require $COVERALLS_REPO_TOKEN
command: |
php ./vendor/bin/php-coveralls \
--verbose \
--json_path=tmp/coveralls-upload.json \
--coverage_clover=tmp/coverage-*.xml
workflow はこんな感じ。
workflows:
version: 2
build-test-deploy:
jobs:
- build
- phpunit:
requires:
- build
- report:
requires:
- phpunit
参考
Author And Source
この問題について(CircleCI で PHPUnit を並列実行する), 我々は、より多くの情報をここで見つけました https://qiita.com/takeru0757/items/2bf3f06e210e450488be著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .