TestFinishedSubscriberクラスの使われ方
関連処理は大きく分けて以下の2つになります。
-
TestFinishedSubscriberクラスをイベントとして登録する -
イベントの発火で
TestFinishedSubscriberクラスが実行される
また、この後のクラス間の呼び出し処理は一部省略しているところがあることをご了承ください。
TestFinishedSubscriberクラスをイベントとして登録する
まずは「TestFinishedSubscriberクラスをイベントとして登録する」部分から見ていきます。
// 2-1
// PHPUnit\TextUI\Application
public function run(array $argv): int
{
// ...略...
$printer = OutputFacade::init(
$configuration,
$extensionReplacesProgressOutput,
$extensionReplacesResultOutput,
);
// ...略...
}
// ...クラス間の呼び出し省略...
// 2-2
// PHPUnit\TextUI\Output\Default\ProgressPrinter\ProgressPrinter
private function registerSubscribers(Facade $facade): void
{
$facade->registerSubscribers(
new BeforeTestClassMethodErroredSubscriber($this),
new TestConsideredRiskySubscriber($this),
new TestErroredSubscriber($this),
new TestFailedSubscriber($this),
new TestFinishedSubscriber($this),
new TestMarkedIncompleteSubscriber($this),
new TestPreparedSubscriber($this),
new TestRunnerExecutionStartedSubscriber($this),
new TestSkippedSubscriber($this),
new TestTriggeredDeprecationSubscriber($this),
new TestTriggeredNoticeSubscriber($this),
new TestTriggeredPhpDeprecationSubscriber($this),
new TestTriggeredPhpNoticeSubscriber($this),
new TestTriggeredPhpunitDeprecationSubscriber($this),
new TestTriggeredPhpunitWarningSubscriber($this),
new TestTriggeredPhpWarningSubscriber($this),
new TestTriggeredWarningSubscriber($this),
);
}
// ...クラス間の呼び出し省略...
// 2-3
// PHPUnit\Event\DirectDispatcher
public function registerSubscriber(Subscriber $subscriber): void
{
// ...略...
$this->subscribers[$eventClassName][] = $subscriber;
}
エントリポイントであるApplicationクラス(Application.php)のrunメソッドをみていきます(2-1)。
OutputFacadeクラスのinitメソッドを呼び出してる箇所があり、その処理の中でProgressPrinterクラスのregisterSubscribersメソッドを呼び出しています。
さらに中を辿っていくとFacadeクラスのregisterSubscribersメソッドで複数のSubscriberを引数として渡している処理があります(2-2)。
この中にTestFinishedSubscriberクラスがあり、また他のSubscriberたちも渡されているようです。
さらに辿ると、最終的にDirectDispatcherのregisterSubscriberメソッドに行き着きます(2-3)。
DirectDispatcherクラスの$subscribersプロパティにEvent名をキー、Subscriberをバリュー(配列)として格納しています。具体的には、キーはPHPUnit\Event\Test\Finishedという文字列、バリューはTestFinishedSubscriberオブジェクトが配列として入っています。
大事なのは、この時点ではまだプロパティにデータを追加しただけで、実際の進捗結果の出力処理は動かないということです。
イベントの発火でTestFinishedSubscriberクラスが実行される
次は「イベントの発火でTestFinishedSubscriberクラスが実行される」部分を見ていきましょう。
// 3-1
// PHPUnit\TextUI\Application
public function run(array $argv): int
{
// ...略...
$runner = new TestRunner;
$runner->run(
$configuration,
$resultCache,
$testSuite,
);
// ...略...
}
// 3-2
// PHPUnit\Framework\TestRunner
public function run(TestCase $test): void
{
// ...略...
$test->runBare();
// ...略...
if ($test->wasPrepared()) {
Facade::emitter()->testFinished(
$test->valueObjectForEvents(),
$test->numberOfAssertionsPerformed(),
);
}
}
// 3-3
// PHPUnit\Event\DispatchingEmitter
public function testFinished(Code\Test $test, int $numberOfAssertionsPerformed): void
{
$this->dispatcher->dispatch(
new Test\Finished(
$this->telemetryInfo(),
$test,
$numberOfAssertionsPerformed,
),
);
}
// ...クラス間の呼び出し省略...
// 3-4
// PHPUnit\Event\DirectDispatcher
public function dispatch(Event $event): void
{
// ...略...
foreach ($this->subscribers[$eventClassName] as $subscriber) {
try {
/** @phpstan-ignore method.notFound */
$subscriber->notify($event);
} catch (Throwable $t) {
$this->handleThrowable($t);
}
}
}
再度、エントリポイントであるApplicationクラスのrunメソッドに戻りましょう(3-1)。TestRunnerクラスのrunメソッドを呼び出しています。
次にTestRunnerのrunメソッドでの処理を見ていきます(3-2)。このメソッド全体の詳細は省略しますが、テスト実行の核心部分は$test->runBare();にあり、ここでテストケースを実際に実行します。
しかし今回注目するのは、メソッドの最後の方にあるFacade::emitter()->testFinishedです(省略している部分にもFacade::emitter()によるその他のメソッド呼び出しが散見されます)。
生成箇所を辿るとFacade::emitter()はDispatchingEmitterクラスということがわかります(3-3)。DispatchingEmitterクラスのtestFinishedメソッドではDispatchingEmitterクラスがPHPUnit\Event\Test\Finishedクラスのインスタンスを渡しています。
最終的に呼ばれるのはDirectDispatcherクラスのdispatchメソッドです(3-4)。3-3で渡されたPHPUnit\Event\Test\Finishedクラスのインスタンスに基づき、$subscribersプロパティのPHPUnit\Event\Test\Finished(文字列)キーに登録されている配列がforeachで回ります。
ここで、以前登録したTestFinishedSubscriberクラスが対象となりnotifyメソッドが呼び出されるのです。
ここまでの流れを1つの図にしてみると、下記のようになります。
Observerパターン
ここまで固有名詞による説明をしていませんでしたが、実はこの一連の処理は「Observerパターン」というデザインパターンです。初見だとどのような流れかを理解するのは難しいですが、覚えてしまうとさまざまな場面で利用されていることに気づく有用なデザインパターンです。
Observerパターンとは「あるオブジェクトの状態が変化した際、それに依存するすべてのオブジェクトに対して自動的に通知、更新を行う」ものです(書籍『オブジェクト指向のこころ』より引用)。
まず、オブジェクトの状態が変化した際に実行したい処理を事前に登録します。その後、実際にオブジェクトの状態が変化すると、登録された処理が自動的に実行されます。
今回の例で表現してみましょう。
PHPUnit\Event\Test\Finishedというイベントに対してTestFinishedSubscriberを事前に登録しました。その後、DispatchingEmitterがPHPUnit\Event\Test\Finishedのイベントを発火させ、それによりTestFinishedSubscriberが実際に動作し、進捗結果が表示されたのでした。
構成要素が多いと感じるかもしれませんが、Observerパターンにより「大筋の目的」と「大筋の目的に紐づいて行いたい処理」の責務をきっちり分けることができます。
「テストの実行」という大筋の目的と分けて、「テストの実行結果を出力」という処理が別のクラスで管理されていることで、コードが理解しやすくなります。大筋の目的である「テストの実行」に「進捗結果を出力」する処理を自然に組み込むために、Observerパターンを利用して実装されているのです。
「Observerパターン」については、自身がPHPerKaigi 2022で発表した資料がありますので、ぜひご参考にしていただければと思います。
