どのようにテストが実行されているか
では、PHPUnitの内部ではどのようにテストが実行されているのでしょうか?
PHPUnitをインストールすると、プロジェクト内のvendor/binディレクトリにphpunitというスクリプトファイルが配置されます。このファイルを用いてテストを実行するプロジェクトも多いでしょう(実際に私もこのスクリプトファイルを利用しました)。
まずこのスクリプトファイルはどこに存在し、どのようなロジックが動いているのかを見ていきます。
ここからはローカルマシンにPHPUnitをGitHubからクローンしてコードを読みます。
"bin": [
"phpunit"
]
上記はPHPUnitのcomposer.jsonの一部です。binセクションの指定により、vendor/bin/phpunitを実行するとPHPUnitのプロジェクト内にあるphpunitスクリプトファイルが呼び出されることになります(参照:Composerのドキュメント)。
次はこのスクリプトファイルの中で、どのような処理が行われているのかを詳しく見ていきます。
PHPのバージョンや拡張機能の確認・Composerの依存関係のセットアップなど、さまざまなチェックや準備が行われた後に以下の記述があります。
exit((new PHPUnit\TextUI\Application)->run($_SERVER['argv']));
つまりvendor/bin/phpunitを実行すると、PHPUnit\TextUI\Applicationクラスのrunメソッドがエントリポイントとして呼び出されます(以後Applicationクラスと表現します)。これにより、runメソッドの中身を読めば実際にどのような処理が行われるのかを把握することができます。
本記事内でもApplicationクラスを基点にしてコードリーディングをしていきます。
勘所を掴む
とはいえ、読んでいくといっても全てを網羅して読んでいくのは少々骨が折れます。ある程度アタリをつけてみましょう。
今回、私は'.'という文字列で検索をかけることにしました。どこかに.やFが文字列として定義されているはずと思ったためです。勘所は当たり、ProgressPrinterクラスのprintProgressForSuccessメソッドを見つけました。
// 「'.'」で文字列検索して見つけたクラス
// PHPUnit\TextUI\Output\Default\ProgressPrinter\ProgressPrinter
private function printProgressForSuccess(): void
{
$this->printProgress('.');
}
// 1-1
// PHPUnit\TextUI\Output\Default\ProgressPrinter\ProgressPrinter
private function printProgress(string $progress): void
{
$this->printer->print($progress);
// ...略...
}
// 1-2
// PHPUnit\TextUI\Output\DefaultPrinter
public function print(string $buffer): void
{
assert($this->isOpen);
fwrite($this->stream, $buffer);
}
中身を辿っていくと、ProgressPrinterクラスのprintProgressメソッドの中で$this->printerのprintメソッドを呼び出しています(1-1)。
$this->printerを生成している箇所を辿ると$this->printerはDefaultPrinterクラスになっていそうです。
そしてDefaultPrinterクラスのprintメソッドの中を見てみると、fwrite関数を見つけました(1-2)。
これにより検索で見つけたProgressPrinterクラスのprintProgressForSuccessメソッドが、実際に書き込みをしている処理ということが確定しました。
またProgressPrinterクラスにある以下メソッドたちも、printProgressForSuccessメソッドと同様にDefaultPrinterのprintメソッドが呼ばれていました。
- printProgressForSkipped(「S」をprintする)
- printProgressForIncomplete(「I」をprintする)
- printProgressForNotice(「N」をprintする)
- printProgressForDeprecation(「D」をprintする)
- printProgressForRisky(「R」をprintする)
- printProgressForWarning(「W」をprintする)
- printProgressForFailure(「F」をprintする)
- printProgressForError(「E」をprintする)
ということで、実際に標準出力している部分のロジックは見つけることができました。
コードをさらに読むと、TestFinishedSubscriberクラスのnotifyメソッドが呼ばれることで進捗結果の出力処理が行われるようです。単にメソッドが呼び出されるだけではなく、巧妙に設計されていることが伺えます。
では、TestFinishedSubscriberクラスがどのように使われているのか、紐解いていきましょう。
