PHPUnitのコードカバレッジが遅いからってあきらめずに、並列実行ためしてみる


## はじめに
PHPUnitには[コードカバレッジ解析](https://phpunit.de/manual/current/ja/code-coverage-analysis.html)の機能があります。
このカバレッジの出力には、かなり時間がかかります。
チームメンバーからCIのテストが遅いので何とかしてほしいと、issuesがあがってきたので、スピード改善できないか検討し、最終的には並列実行することで、大幅に改善することができましたので、紹介します。

## コードカバレッジの出力
カバレッジを出力するには、phpunitコマンド実行時のオプションに`--coverage-*`を指定します。
phpunitのヘルプに詳しい説明があります。

```bash
$ bin/phpunit --help
PHPUnit 4.8.6 by Sebastian Bergmann and contributors.

Usage: phpunit [options] UnitTest [UnitTest.php]
       phpunit [options] <directory>

Code Coverage Options:

  --coverage-clover <file>  Generate code coverage report in Clover XML format.
  --coverage-crap4j <file>  Generate code coverage report in Crap4J XML format.
  --coverage-html <dir>     Generate code coverage report in HTML format.
  --coverage-php <file>     Export PHP_CodeCoverage object to file.
  --coverage-text=<file>    Generate code coverage report in text format.
                            Default: Standard output.
  --coverage-xml <dir>      Generate code coverage report in PHPUnit XML format.
```

## phpunitを分割して実行し、最後にカバレッジファイルをマージする
phpunitを並列で実行させるためには、複数に分割して実行する必要があります。
サブディレクトリなど、何らかの単位で分割して実行するようにし、最後に出力されたそれぞれのカバレッジファイルをマージします。

### phpcovのインストール
カバレッジファイルのマージには、[phpcov](https://github.com/sebastianbergmann/phpcov)が必要になりますので、composerからインストールします。

```bash
$ composer require --dev 'phpunit/phpcov'
```

### 分割して実行
下記のようにディレクトリ別にテストを実行し、最後にカバレッジファイルをマージします。

```bash
bin/phpunit --coverage-php var/report/coverage_foo.cov tests/Foo
bin/phpunit --coverage-php var/report/coverage_bar.cov tests/Bar
bin/phpunit --coverage-php var/report/coverage_baz.cov tests/Baz
bin/phpcov merge --clover var/report/coverage.xml var/report/
```

## 並列でテストを実行
phpcovの[README](https://github.com/sebastianbergmann/phpcov#merging-exported-php_codecoverage-objects-stored-in-cov-files)に、[GNU parallel](http://www.gnu.org/software/parallel/)で並列実行するサンプルが記載されているので、試してみます。

### GNU parallelのインストール
事前準備として、GNU parallelをパッケージ経由もしくはソースをビルドしてインストールします。

- パッケージからインストール

  ```bash
  $ apt-get install parallel
  ```

- ソースからインストール

  ```bash
  $ wget http://ftp.gnu.org/gnu/parallel/parallel-20160722.tar.bz2
  $ tar -jxvf parallel-20160722.tar.bz2
  $ cd parallel-20160722
  $ ./configure
  $ make && sudo make install
  ```

### 並列で実行
READMEに従い、並列で実行するスクリプトを準備します。

```bash
parallel --gnu --halt-on-error=now,fail=1 ::: \
  bin/phpunit --coverage-php var/report/coverage_foo.cov tests/Foo \
  bin/phpunit --coverage-php var/report/coverage_bar.cov tests/Bar \
  bin/phpunit --coverage-php var/report/coverage_baz.cov tests/Baz
bin/phpcov merge --clover var/report/coverage.xml var/report/
```

READMEには、`--halt-on-error=now,fail=1`のオプションは記述ありませんが、エラーが起きたときは即時にfailしてほしかったので、追加しました。
詳しくは公式の[マニュアル](http://www.gnu.org/software/parallel/man.html)を参照ください。

## ローカルの仮想環境で確認するときの注意事項
あとは、準備したスクリプトを実行するだけなのですが、ローカルの仮想環境で実行するときには、CPU数をデフォルトの1から2以上に変更しないと、並列実行の効果がほとんど得られません。

### Docker for Macの場合（2018-09-20更新）
Dockerの場合は、`Preferences`からCPU数を変更できます。

#### 参考URL
- [Get started with Docker for Mac | Docker Documentation](https://docs.docker.com/docker-for-mac/#advanced)

### Vagrantの場合
Vagrantの場合は、Vagrantfileに下記の記述を追加します。

```
# Vagrantfile
  config.vm.provider :virtualbox do |vb|
    vb.customize ['modifyvm', :id, '--cpus', '2', '--ioapic', 'on']
  end
```

#### 参考URL
- [Configuration - VirtualBox Provider - Vagrant by HashiCorp](https://www.vagrantup.com/docs/virtualbox/configuration.html#vboxmanage-customizations)

## 結果
どのくらい早くなるかは、一概には言えませんが、例えば下記のように実行時間がかかっているとすれば、7分かかっていた処理が、`tests/Baz => 3分` + `phpcov merge => 1分` で4分にはなるイメージです。

- tests/Foo => 1分
- tests/Bar => 2分
- tests/Baz => 3分
- phpcov merge => 1分

## 上記以外のソリューション
いずれも今回は検討のみで試したわけではないのですが、上記以外にもいくつかソリューションがあります。

### 特定のブランチのみカバレッジを測る
[polidog](http://qiita.com/polidog)さんのブログ記事『[CircleCIでmasterブランチのみカバレッジを測るようにphpunitを実行する](http://polidog.jp/2015/06/28/circleci/)』に詳しい説明があります。

### CIの並列実行機能を使う
CIによっては、CI側で並列実行の機能を提供しています。

- [circleci](https://circleci.com/docs/parallel-manual-setup/)
- [travis-ci](https://docs.travis-ci.com/user/speeding-up-the-build/)
- [scrutinizer-ci](https://scrutinizer-ci.com/docs/build/running_tests_in_parallel)

## おわりに
テストの実行時間は開発効率に直接影響します。
今回紹介した方法は、特定のCIに依存したものではないので、参考にしてみてください。