どのようにテストが実行されているか
では、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
クラスがどのように使われているのか、紐解いていきましょう。