
会議や記事で、数万のユニットテストを短時間で「実行」することを繰り返しました。 ご想像のとおり、マルチスレッドにより主な効果が得られます。 そして、すべては問題ありませんが、重要なテストメトリックの1つは、コードをテストでカバーすることです。
今日は、マルチスレッド環境でそれを数え、それを集約し、非常に迅速に行う方法を説明します。 最適化を行わないと、カバレッジの計算には単体テストのみで70時間以上かかりました。 最適化後、すべての単体テストと合計3万件を超える2セットの統合テストのカバレッジの計算に2.5時間しかかかりません。
PHPのBadooでテストを作成し、Sebastian Bergmann(Sebastian Bergmann、 phpunit.de )のPHPUnit Frameworkを使用します。
他の多くのフレームワークと同様に、このフレームワークのカバレッジは、 Xdebug拡張機能を単純な呼び出しとして使用することと見なされます。
xdebug_start_code_coverage(); //… … $codeCoverage = xdebug_get_code_coverage(); xdebug_stop_code_coverage();
出力は、カバレッジの収集中に実行されたファイルと、特別なフラグ(コードが呼び出された、呼び出されなかった、または呼び出されるべきではない)を持つファイルの行番号を含むネストされた配列です。 コーティングされたXdebugの操作の詳細は、プロジェクトのWebサイトで見つけることができます。
Sebastian BergmanにはPHP_CodeCoverageライブラリがあり、さまざまな形式でカバレッジを収集、処理、表示します。 このライブラリは便利で、拡張性があり、非常に満足しています。 彼女はコンソールフロントエンドphpcovを持っています。
しかし、便宜上、さまざまな形式のカバレッジと出力の計算は、PHPUnit呼び出し自体に既に統合されています。
--coverage-clover <file> Generate code coverage report in Clover XML format. --coverage-html <dir> Generate code coverage report in HTML format. --coverage-php <file> Serialize PHP_CodeCoverage object to file. --coverage-text=<file> Generate code coverage report in text format.
--coverage-phpオプションは、マルチスレッド起動に必要なものです。各スレッドはカバレッジをカウントし、個別の* .covファイルにエクスポートします。 --mergeフラグを付けてphpcovを呼び出すことで、美しいHTMLレポートへの集約と出力を行うことができます。
--merge Merges PHP_CodeCoverage objects stored in .cov files.
すべてが折りたたまれており、美しく、箱から出して動作するはずです。 しかし、どうやら、ライブラリの作成者を含め、誰もがこのメカニズムを使用しているわけではないようです。 順番に見てみましょう、問題は何ですか。
特別なレポータークラスPHP_CodeCoverage_Report_PHPは、* .cov形式へのエクスポートを担当します。このインターフェイスは非常にシンプルです。 これは、PHP_CodeCoverageクラスの入力オブジェクトを取得し、serialize()関数でシリアル化するprocess()メソッドです。
結果はファイルに書き込まれる(ファイルへのパスが渡される場合)か、メソッドの結果として返されます。
class PHP_CodeCoverage_Report_PHP { /** * @param PHP_CodeCoverage $coverage * @param string $target * @return string */ public function process(PHP_CodeCoverage $coverage, $target = NULL) { $coverage = serialize($coverage); if ($target !== NULL) { return file_put_contents($target, $coverage); } else { return $coverage; } } }
反対に、phpcovユーティリティを使用してインポートすると、拡張子が* .covのディレクトリ内のすべてのファイルが取得され、それぞれがオブジェクト内でunserialize()されます 。 その後、オブジェクトは、カバレッジが集約されるPHP_CodeCoverageオブジェクトのmerge()メソッドに渡されます。
protected function execute(InputInterface $input, OutputInterface $output) { $coverage = new PHP_CodeCoverage; $finder = new FinderFacade( array($input->getArgument('directory')), array(), array('*.cov') ); foreach ($finder->findFiles() as $file) { $coverage->merge(unserialize(file_get_contents($file))); } $this->handleReports($coverage, $input, $output); }
マージプロセス自体は非常に簡単です。 これは、すでにインポートされたものを無視したり、phpcovにフィルターパラメーターとして渡されたり(--blacklistおよび--whitelist)などの小さなニュアンスを持つarray_merge()配列のマージです。
/** * Merges the data from another instance of PHP_CodeCoverage. * * @param PHP_CodeCoverage $that */ public function merge(PHP_CodeCoverage $that) { foreach ($that->data as $file => $lines) { if (!isset($this->data[$file])) { if (!$this->filter->isFiltered($file)) { $this->data[$file] = $lines; } continue; } foreach ($lines as $line => $data) { if ($data !== NULL) { if (!isset($this->data[$file][$line])) { $this->data[$file][$line] = $data; } else { $this->data[$file][$line] = array_unique( array_merge($this->data[$file][$line], $data) ); } } } } $this->tests = array_merge($this->tests, $that->getTests()); }
シリアル化とデシリアル化のアプローチを使用することが問題の原因となり、カバレッジを迅速に生成できませんでした。 コミュニティは、PHPでの関数のシリアル化と非シリアル化のパフォーマンスについて何度も議論しています。
http://stackoverflow.com/questions/1256949/serialize-a-large-array-in-php ;
http://habrahabr.ru/post/104069など
私たちの小さなプロジェクトの場合、PHPリポジトリには35,000以上のファイルが含まれており、カバレッジのあるファイルの重量はそれぞれ数百メガバイトです。 異なるストリームからの「連続した」一般的なファイルの重量は、ほぼ2ギガバイトです。 このようなデータボリュームでは、unserializeが栄光を見せました。数日間、カバレッジが生成されるのを待ちました。
そのため、最も明白な最適化方法であるvar_exportと後続のインクルードファイルを試すことにしました。
これを行うために、 新しいレポータークラスが php-code-coverageリポジトリに追加され、var_exportを介して新しい形式でエクスポートされます。
class PHP_CodeCoverage_Report_PHPSmart { /** * @param PHP_CodeCoverage $coverage * @param string $target * @return string */ public function process(PHP_CodeCoverage $coverage, $target = NULL) { $output = '<?php $filter = new PHP_CodeCoverage_Filter();' . '$filter->setBlacklistedFiles(' . var_export($coverage->filter()->getBlacklistedFiles(), 1) . ');' . '$filter->setWhitelistedFiles(' . var_export($coverage->filter()->getWhitelistedFiles(), 1) . ');' . '$object = new PHP_CodeCoverage(new PHP_CodeCoverage_Driver_Xdebug(), $filter); $object->setData(' . var_export($coverage->getData(), 1) . '); $object->setTests(' . var_export($coverage->getTests(), 1) . '); return $object;'; if ($target !== NULL) { return file_put_contents($target, $output); } else { return $output; } } }
ファイル形式をPHPSmartと控えめに呼んでいます。 この形式のファイルの拡張子は* .smartです。
PHP_CodeCoverageクラスのオブジェクトが自身をエクスポートして新しいフォーマットにインポートできるようにするために、そのプロパティのセッターとゲッターが追加されました。
phpunitおよびphpcovリポジトリのいくつかの修正により、このようなオブジェクトの操作方法を学習し、わずか2時間半でカバレッジが収集されるようになりました。
インポートは次のとおりです。
foreach ($finder->findFiles() as $file) { $extension = pathinfo($file, PATHINFO_EXTENSION); switch ($extension) { case 'smart': $object = include($file); $coverage->merge($object); unset($object); break; default: $coverage->merge(unserialize(file_get_contents($file))); } }
GitHubで編集を見つけ、プロジェクトでこのアプローチを試してください。
github.com/uyga/php-code-coverage
github.com/uyga/phpcov
github.com/uyga/phpunit
Sebastian Bergmanに編集のプルリクエストを送信し、作成者の公式リポジトリですぐに表示されることを期待しています。
github.com/sebastianbergmann/phpunit/pull/988
github.com/sebastianbergmann/phpcov/pull/7
github.com/sebastianbergmann/php-code-coverage/pull/185
しかし、彼はそれらを閉じて、追加のフォーマットではなく、自分のものではなく私たちのものを望んでいると言った。

私たちは喜んでやった。 そして今、私たちの変更は作成者の公式リポジトリに入り、* .covファイルで以前に使用されていた形式を置き換えました。
github.com/sebastianbergmann/php-code-coverage/pull/186
github.com/sebastianbergmann/phpcov/pull/8
github.com/sebastianbergmann/phpunit/pull/989
このような小さな最適化により、カバレッジの収集が約30(!)回短縮されました。 カバレッジを計算するために単体テストだけでなく、2セットの統合テストを追加することもできました。 これは、インポート/エクスポートおよび結果のマージの時間に大きな影響を与えませんでした。
PS:

イリヤ・アジェエフ、
QAリード