Laravel / PHPUnit のFeature Testを書いていると、テストメソッド名だけでも「何を確認しているか」はある程度分かります。
しかし、なぜそのテストが必要なのか、なぜその検証方法を選んだのかまでは、メソッド名だけでは伝わらないことがあります。この記事では、実際のLaravel Feature Testを題材に、PHPDocで「Why」を残す考え方を整理します。
目次
- 環境
- PHPDocとは
- テストメソッド名だけでは足りないことがある
- WhatコメントとWhyコメントの違い
- 実例1:会員登録画面の初期状態
- 実例2:バリデーションエラーとARIA属性
- 実例3:正常な会員登録
- 実例4:メール認証通知
- private helperにもPHPDocを書く
getSingleElementById()にもWhyを残す- PHPDocを付けるときに意識したいこと
- PHPDocは全部のテストに必要なのか
- 今回の整理
- 関連記事
環境
- PHP 8.4
- Laravel 13
- Laravel Breeze
- PHPUnit
- Laravel Sail
PHPDocとは
PHPでは複数行コメントとして、
/*
* コメント
*/
を書くことができます。
その中でも、次のように/**から始まる形式はDoc Comment / DocBlockとして使われます。
/**
* コメント
*/
PHP公式マニュアルでは、PHPが複数行コメントをサポートしていることが説明されています。
また、PHPUnitではDocBlockが歴史的にテストのメタデータ用途にも使われてきました。PHPUnit 12では、その用途でのAnnotationサポートは削除され、メタデータにはAttributesを使用します。一方、Doc Comment自体は、コードの意図や設計上の理由を説明するコメントとして利用できます。
参考:
テストメソッド名だけでは足りないことがある
例えば次のテスト名を見ます。
public function test_new_users_receive_email_verification_notification(): void
この名前だけでも、
新規ユーザーがメール認証通知を受け取ることを確認する
という「What」は分かります。
しかし、このテストが存在する理由までは完全には分かりません。
今回の実装では、
会員登録
↓
Registeredイベント
↓
Laravel標準のVerifyEmail通知
↓
メール認証
というメール認証フローを保証したいという背景があります。
そこでPHPDocで、
/**
* 会員登録時にLaravel標準のメール認証通知が送信されることを保証する。
*/
public function test_new_users_receive_email_verification_notification(): void
と残します。
テスト名が「何を確認するか」、PHPDocが「なぜ保証する必要があるか」を補う形です。
WhatコメントとWhyコメントの違い
Whatだけのコメント
/**
* メール認証通知をテストする。
*/
これはコードを読めば分かる内容です。
Notification::assertSentTo(
$user,
VerifyEmail::class,
);
を見れば、メール認証通知を確認していることは分かります。
Whyまで説明するコメント
/**
* 会員登録時にLaravel標準のメール認証通知が送信されることを保証する。
*/
こちらは、
会員登録フローの一部として、この通知が欠けるとメール認証自体が成立しない
というテストの存在理由が読み取りやすくなります。
コメントを書くなら、コードを日本語に翻訳するだけではなく、そのテストが守っている仕様や意図を書く方が価値があります。
実例1:会員登録画面の初期状態
次のテストでは、会員登録画面の入力欄、ラベル、ARIA属性、エラー要素を確認しています。
public function test_registration_screen_can_be_rendered(): void
{
// ...
}
PHPDocを付けるなら、
/**
* 会員登録画面の初期表示で入力欄とラベルが正しく関連付けられ、
* エラーがない状態では不要なARIA属性やエラー要素が表示されないことを保証する。
*/
とできます。
ポイントは単に、
会員登録画面が表示される
だけではなく、
正常時に誤ったエラー状態を支援技術へ伝えない
という意図まで残していることです。
実例2:バリデーションエラーとARIA属性
対象テスト:
public function test_registration_validation_errors_have_accessible_aria_attributes(): void
{
// ...
}
PHPDoc例:
/**
* 会員登録のバリデーションエラーを支援技術へ正しく伝えるため、
* 対象入力欄に必要なARIA属性が付与され、他の入力欄へ誤って影響しないことを保証する。
*/
このテストでは単にエラーメッセージが表示されるだけでなく、
aria-invalidaria-describedby- エラー要素
- 他フィールドへの誤ったARIA属性付与
などを確認しています。
そのためPHPDocにも、
支援技術へ正しく伝える
他の入力欄へ影響しない
という「Why」を含めると、テストの役割が明確になります。
実例3:正常な会員登録
対象テスト:
public function test_new_users_can_register(): void
{
// ...
}
PHPDoc例:
/**
* 正常な入力で会員登録でき、登録後にログイン状態となって
* アプリケーションのホームへ遷移できることを保証する。
*/
ここで「認証済み」と書かないのが重要です。
Laravel Breezeのメール認証を有効にした場合でも、
登録直後
↓
ログイン済み
↓
メールアドレスはまだ未認証
という状態があり得ます。
そのため、
認証済みセッション
ではなく、
ログイン状態
と書く方が正確です。
コメントも実装の現在状態に合わせて書く必要があります。
実例4:メール認証通知
実際に追加したテストです。
/**
* 会員登録時にLaravel標準のメール認証通知が送信されることを保証する。
*/
public function test_new_users_receive_email_verification_notification(): void
{
Notification::fake();
$this->post('/register', [
'name' => 'Test User',
'email' => 'test@example.com',
'password' => 'password',
'password_confirmation' => 'password',
]);
$user = User::where('email', 'test@example.com')->firstOrFail();
$this->assertNull($user->email_verified_at);
Notification::assertSentTo(
$user,
VerifyEmail::class,
);
}
このテストでは、
会員登録
↓
email_verified_at は null
↓
VerifyEmail通知は送信される
という状態を保証しています。
PHPDocを読むだけでも、
Laravel標準のメール認証フローを守るためのテスト
だと理解できます。
private helperにもPHPDocを書く
テストメソッドだけでなく、テスト用helperにも「Why」が有効です。
例えば、
private function createXPath(string $html): DOMXPath
には、
/**
* 文字列一致へ依存せず、対象DOM要素自身の属性を検証するためXPathを生成する。
*/
というPHPDocを付けています。
これは良いWhyコメントです。
単に、
XPathを作る
ではなく、
なぜXPathを使うのか
→ HTML文字列の単純一致ではなく、対象DOM要素自身を確認したいから
という設計理由を説明しています。
getSingleElementById()にもWhyを残す
例えば次のhelperがあります。
private function getSingleElementById(DOMXPath $xpath, string $id): DOMElement
{
$elements = $xpath->query(sprintf('//*[@id="%s"]', $id));
$this->assertNotFalse($elements);
$this->assertCount(1, $elements);
$element = $elements->item(0);
$this->assertInstanceOf(DOMElement::class, $element);
return $element;
}
この処理を日本語に翻訳するだけなら、
/**
* 指定IDの要素を取得する。
*/
でも間違いではありません。
ただし、これではassertCount(1, ...)を行う理由が伝わりません。
Whyまで含めるなら、
/**
* 重複IDや対象要素の欠落を見逃さず、ARIA属性などを対象要素自身で検証するため、
* 指定IDの要素が1件だけ存在することを確認してDOMElementとして返す。
*/
と書けます。
これなら、
なぜ1件だけと確認するのか
↓
HTMLのID重複・欠落を見逃さないため
なぜDOMElementを返すのか
↓
ARIA属性などを要素単位で検証するため
まで分かります。
PHPDocを付けるときに意識したいこと
コードをそのまま日本語にしない
避けたい例:
/**
* ユーザーを作成してPOSTしてリダイレクトを確認する。
*/
コードを読めば分かります。
守っている仕様を書く
良い例:
/**
* メール未認証ユーザーからの公開レビュー投稿を防ぐため、
* 認証案内画面へリダイレクトされ、レビューが保存されないことを保証する。
*/
このコメントなら、
なぜこのテストが存在するのか
が分かります。
実装詳細を書きすぎない
例えば、
EnsureEmailIsVerifiedのhandle()がredirectToRoute()を呼ぶことを保証する
のようにLaravel内部実装へ寄りすぎると、フレームワーク内部変更に弱いテスト説明になります。
アプリケーション側の仕様として、
未認証ユーザーはレビュー投稿できない
と書く方が読みやすくなります。
PHPDocは全部のテストに必要なのか
必ずしもPHPの言語仕様やPHPUnitが、すべてのテストメソッドへPHPDocを書くことを要求しているわけではありません。
例えば、
public function test_login_screen_can_be_rendered(): void
のようにテスト名だけで十分に意図が伝わるケースもあります。
ただし、プロジェクトで、
- セキュリティ上の理由
- アクセシビリティ上の理由
- 回帰防止の理由
- 一見すると不要に見えるAssertion
- 独自helperを使う理由
などを重視している場合、Whyコメントを残す価値があります。
特に複雑なFeature Testでは、
何をしているか
より、
なぜそれを保証する必要があるか
の方が、数か月後に読み返したときに役立ちます。
今回の整理
テストコードでは、
テストメソッド名
↓
何を確認するか
PHPDoc
↓
なぜそれを保証する必要があるか
テストコード
↓
どうやって確認するか
と役割を分けると読みやすくなります。
例えば、
/**
* メール未認証ユーザーからの公開レビュー返信を防ぐため、
* 認証案内画面へリダイレクトされ、返信が保存されないことを保証する。
*/
public function test_unverified_user_cannot_store_review_comment(): void
{
// Arrange / Act / Assert
}
を見ると、
What
未認証ユーザーはレビュー返信できない
Why
未認証アカウントから公開返信を作成させないため
How
リダイレクトとDB未保存をFeature Testで確認
が分かります。
PHPDocを単なる説明文ではなく、テストの設計意図を残す場所として使うと、Feature Test全体が読みやすくなりました。
関連記事
この内容は、次の記事と分けて相互リンクすると整理しやすくなります。
- Laravel Feature Testでメール認証を検証する:署名付きURL・期限切れ・別ユーザー・認証済み分岐まで
- Laravel Feature Testを1行ずつ理解する:会員登録時のメール認証通知をテストする
- Laravel Feature Testを1行ずつ理解する:メール未認証ユーザーのレビュー投稿・返信をテストする
- PHPUnit・Laravel Featureテストでよく使う英単語集
この記事は「PHPDoc / Whyコメント」、Feature Test記事は「テストコードの書き方」、英単語集は「テスト名の読み方」と役割を分けます。