実務1年目駆け出しエンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(その43)
0. 初めに
このシリーズでは、Webアプリケーション開発のやり方をゼロから解説しています!
前回から、テストを開始しました!
今日から本格的にUnitテストを実施していきたいと思います!!
1. ブランチ運用
developブランチを最新にして、新規ブランチを切って作業をしましょう。
ブランチ名は、test/unit/policiesとかにしましょう。
2. レビューポリシー
Unitテストでは主にポリシーとモデルについてテストしたいと思います。
今回は、ポリシーについてのテストを実施します。
まずは、レビューポリシーについてのテストケースを作成します。
2.1 ファイル作成
\project-root\src\tests\Unitというフォルダがあるかなと思います。
これは、Laravelをインストールした際に自動で生成されたもので、Unitテストのテストケースを格納するためのフォルダとなります。
前回修正した、phpunit.xmlで設定されているため、テスト実行時に参照されます。
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
この下にさらにPoliciesというフォルダを作成して、その中にReviewPolicyTest.phpというファイルを作成してください。
<?php
namespace Tests\Unit\Policies;
use App\Models\User;
use App\Models\Review;
use App\Policies\ReviewPolicy;
use PHPUnit\Framework\TestCase; // ← DB不要のときはこちら
class ReviewPolicyTest extends TestCase
{
// テスト名は日本語OK。「何が・どうなるべきか」を書く
public function test_自分のレビューは編集できる(): void
{
// 1. 準備(Arrange)
$user = new User();
$user->id = 1;
$review = new Review();
$review->user_id = 1;
$policy = new ReviewPolicy();
// 2. 実行(Act)
$result = $policy->update($user, $review);
// 3. 検証(Assert)
$this->assertTrue($result);
}
}
2.2 テストケース作成(AAAパターン)
作成したファイルの中身について解説します。
これは、AAAパターンと呼ばれる世界的も普及している一般的になテストケースの書き方です。
以下の記事が分かりやすいと思います。
https://tech.anycloud.co.jp/articles/test-aaa/
これによると、
- Arrange:テストに必要なオブジェクトの生成、データの準備
- Act:テスト対象の機能を1回だけ実行
- Assert:期待する結果との比較を行う
という三つの役割を意識して書くことで、読みやすいテストコードになりますということですね。
これを意識して見てみましょう。
まず、以下の部分を見てみましょう。
class ReviewPolicyTest extends TestCase
TestCaseはLaravel内部であらかじめ作られているクラスで、これを継承することでテストで使用されるメソッドなどを呼び出すことができるようになります。
これは、DBを使わないUnitテストを使うときに使用します。
use PHPUnit\Framework\TestCase;
次に以下の部分。
public function test_自分のレビューは編集できる(): void
クラスの中にメソッドを定義しています。
PHPUnitでは、クラス名に日本語を使用することができ、読みやすさの観点から日本ではこのように書くのが流行っているみたいです。
最後に、メソッドの中身を見ていきましょう。
まずは、Arange(準備)の部分です。
$user = new User();
$user->id = 1;
$review = new Review();
$review->user_id = 1;
$policy = new ReviewPolicy();
このブロックでは、データの準備をします。
newでインスタンス化して、DBを使わずに直接idを代入しています。
次に、Act(実行)ブロックを見てみましょう。
$result = $policy->update($user, $review);
Arrangeブロックでインスタンス化した$policyのupdateメソッドを実行します。
これは、バックエンド実装編で作成したものです。
最後に、Assert(確認)の部分です。
$this->assertTrue($result);
assertTrueはPHPUnitが用意しているメソッドで、引数がtrueならテスト成功を意味します。
※アサーションメソッドについては、以下のドキュメントにすべて書かれているので気になる場合は参照してください。
https://net-newbie.com/phpunit/assertions.html
2.3 テスト実行
以下のコマンドをPHPのコンテナの中で実行することでテストを実行できます。
実行コマンド
$ php artisan test --filter=ReviewPolicityTest
まだ、テストケースを一つしか作成していないので、「1 passed」と表示されています。
成功していることが分かります!
試しに、Assertの部分を以下のように反転させてあえて失敗させてみると...??
$this->assertTrue(!$result);
今回は挙動を見るためにあえて失敗させてみたので、テストケースのAssertの部分は確認が終わったら元に戻しておきましょう。
また、これはDBに登録や削除はしないので何度実行しても問題ありません。
2.4 振り返り(結局何をしているのか)
これは、過去に作成したポリシー(どのユーザーがどの操作をすることができるのか)をテストしているものです。
// ユーザーがレビューを編集できるかどうかを判定
public function update(User $user, Review $review)
{
// 自分が投稿したレビューのみ編集可能
return $user->id === $review->user_id;
}
ユーザーのIDとレビュー投稿者のIDが一致しているかどうかを見ています。
これにより、「自分が投稿したレビューが誰かに編集される」こともないし、「自分が他の人のレビューを編集したりすることができないこと」を保証しています!
2.5 テストケース追加
同様に、falseが返るケースとdeleteについてのテストケース用のメソッドも追加しましょう。
assertFalseもPHPUnitが用意しているメソッドで、引数が逆にfalseなら成功とするものです。
<?php
namespace Tests\Unit\Policies;
use App\Models\User;
use App\Models\Review;
use App\Policies\ReviewPolicy;
use PHPUnit\Framework\TestCase;
class ReviewPolicyTest extends TestCase
{
public function test_自分のレビューは編集できる(): void
{
// Arrange
$user = new User();
$user->id = 1;
$review = new Review();
$review->user_id = 1;
$policy = new ReviewPolicy();
// Act
$result = $policy->update($user, $review);
// Assert
$this->assertTrue($result);
}
public function test_他人のレビューは編集できない(): void
{
// Arrange
$user = new User();
$user->id = 1;
$review = new Review();
$review->user_id = 2;
$policy = new ReviewPolicy();
// Act
$result = $policy->update($user, $review);
// Assert
$this->assertFalse($result);
}
public function test_自分のレビューは削除できる(): void
{
// Arrange
$user = new User();
$user->id = 1;
$review = new Review();
$review->user_id = 1;
$policy = new ReviewPolicy();
// Act
$result = $policy->delete($user, $review);
// Assert
$this->assertTrue($result);
}
public function test_他人のレビューは削除できない(): void
{
// Arrange
$user = new User();
$user->id = 1;
$review = new Review();
$review->user_id = 2;
$policy = new ReviewPolicy();
// Act
$result = $policy->delete($user, $review);
// Assert
$this->assertFalse($result);
}
}
最終的なコードは上記のようになります。
ファイルを保存出来たら、再びテストを実行しましょう!
実行コマンド
$ php artisan test --filter=ReviewPolicyTest
createはDB登録もしたいので、Featureテストの方に回そうかなと思います。
3. コメントポリシー
同じ要領で、コメントポリシーのテストも作成しましょう。
先ほどと同様の場所に新規のファイルを作成してください。
<?php
namespace Tests\Unit\Policies;
use App\Models\Comment;
use App\Models\User;
use App\Policies\CommentPolicy;
use PHPUnit\Framework\TestCase;
class CommentPolicyTest extends TestCase
{
public function test_自分のコメントは編集できる(): void
{
// Arrange
$user = new User();
$user->id = 1;
$comment = new Comment();
$comment->user_id = 1;
$policy = new CommentPolicy();
// Act
$result = $policy->update($user, $comment);
// Assert
$this->assertTrue($result);
}
public function test_他人のコメントは編集できない(): void
{
// Arrange
$user = new User();
$user->id = 1;
$comment = new Comment();
$comment->user_id = 2;
$policy = new CommentPolicy();
// Act
$result = $policy->update($user, $comment);
// Assert
$this->assertFalse($result);
}
public function test_自分のコメントは削除できる(): void
{
// Arrange
$user = new User();
$user->id = 1;
$user->is_admin = false;
$comment = new Comment();
$comment->user_id = 1;
$policy = new CommentPolicy();
// Act
$result = $policy->delete($user, $comment);
// Assert
$this->assertTrue($result);
}
public function test_管理者は他人のコメントを削除できる(): void
{
// Arrange
$user = new User();
$user->id = 1;
$user->is_admin = true;
$comment = new Comment();
$comment->user_id = 2;
$policy = new CommentPolicy();
// Act
$result = $policy->delete($user, $comment);
// Assert
$this->assertTrue($result);
}
public function test_一般ユーザーは他人のコメントを削除できない(): void
{
// Arrange
$user = new User();
$user->id = 1;
$user->is_admin = false;
$comment = new Comment();
$comment->user_id = 2;
$policy = new CommentPolicy();
// Act
$result = $policy->delete($user, $comment);
// Assert
$this->assertFalse($result);
}
}
レビューにはない「管理者かどうか」という観点が増えますが、なんてことはないでしょう!
実行してみます。
実行コマンド
$ php artisan test --filter=CommentPolicyTest
4. ブックマークポリシー
同じノリでブックマークポリシーに関しても作成してみましょう。
<?php
namespace Tests\Unit\Policies;
use App\Models\Bookmark;
use App\Models\User;
use App\Policies\BookmarkPolicy;
use PHPUnit\Framework\TestCase;
class BookmarkPolicyTest extends TestCase
{
public function test_自分のブックマークは削除できる(): void
{
// Arrange
$user = new User();
$user->id = 1;
$bookmark = new Bookmark();
$bookmark->user_id = 1;
$policy = new BookmarkPolicy();
// Act
$result = $policy->delete($user, $bookmark);
// Assert
$this->assertTrue($result);
}
public function test_他人のブックマークは削除できない(): void
{
// Arrange
$user = new User();
$user->id = 1;
$bookmark = new Bookmark();
$bookmark->user_id = 2;
$policy = new BookmarkPolicy();
// Act
$result = $policy->delete($user, $bookmark);
// Assert
$this->assertFalse($result);
}
}
更新メソッドがないので楽ですね。
実行コマンド
$ php artisan test --filter=BookmarkPolicyTest
5. 研究室ポリシー
こちらは、削除のみです。
<?php
namespace Tests\Unit\Policies;
use App\Models\User;
use App\Policies\LabPolicy;
use PHPUnit\Framework\TestCase;
class LabPolicyTest extends TestCase
{
public function test_管理者は研究室を削除できる(): void
{
// Arrange
$user = new User();
$user->is_admin = true;
$policy = new LabPolicy();
// Act
$result = $policy->delete($user);
// Assert
$this->assertTrue($result);
}
public function test_一般ユーザーは研究室を削除できない(): void
{
// Arrange
$user = new User();
$user->is_admin = false;
$policy = new LabPolicy();
// Act
$result = $policy->delete($user);
// Assert
$this->assertFalse($result);
}
}
「ログインしているかどうか」に関しては、ミドルウェアを使って実装してきたのでFeatureテストに回したいと思います。
実行コマンド
$ php artisan test --filter=LabPolicyTest
6. 学部ポリシー
研究室ポリシーと全く同じです。
<?php
namespace Tests\Unit\Policies;
use App\Models\User;
use App\Policies\FacultyPolicy;
use PHPUnit\Framework\TestCase;
class FacultyPolicyTest extends TestCase
{
public function test_管理者は学部を削除できる(): void
{
// Arrange
$user = new User();
$user->is_admin = true;
$policy = new FacultyPolicy();
// Act
$result = $policy->delete($user);
// Assert
$this->assertTrue($result);
}
public function test_一般ユーザーは学部を削除できない(): void
{
// Arrange
$user = new User();
$user->is_admin = false;
$policy = new FacultyPolicy();
// Act
$result = $policy->delete($user);
// Assert
$this->assertFalse($result);
}
}
実行コマンド
$ php artisan test --filter=FacultyPolicyTest
7. 大学ポリシー
こちらも同じです。
<?php
namespace Tests\Unit\Policies;
use App\Models\User;
use App\Policies\UniversityPolicy;
use PHPUnit\Framework\TestCase;
class UniversityPolicyTest extends TestCase
{
public function test_管理者は大学を削除できる(): void
{
// Arrange
$user = new User();
$user->is_admin = true;
$policy = new UniversityPolicy();
// Act
$result = $policy->delete($user);
// Assert
$this->assertTrue($result);
}
public function test_一般ユーザーは大学を削除できない(): void
{
// Arrange
$user = new User();
$user->is_admin = false;
$policy = new UniversityPolicy();
// Act
$result = $policy->delete($user);
// Assert
$this->assertFalse($result);
}
}
実行コマンド
$ php artisan test --filter=UniversityPolicyTest
また、これまでに作成したテストケースをまとめて実行するなら以下です。
実行コマンド
$ php artisan test --testsuite=Unit
全部で18個通っていればOK!!
このようにまとめて実行することで、(今回はなかったですが)バグを見つけて修正したとこで別のところでバグがまた発生しないか(いわゆるデグレがおきていないか)を確認することができます!
ここまでできていたら、今日はおしまいです!
コミット・プッシュ、PR作成・マージ、ブランチ削除を忘れずにしておきましょう。
8. まとめ・次回予告
お疲れ様でした。
今回は、Unitテストの2回目ということで、ポリシーについてのテストを作成・実行しました。
テストの書き方では、「AAAパターン」という書き方を採用したことで読みやすいテストコードを作ることができましたね!
次回は、引き続きUnitテストのモデルについて作成・実行していこうと思います!
これまでの記事一覧
☆要件定義・設計編
☆環境構築編
☆バックエンド実装編
- その7: バックエンド実装編① ~認証機能作成~
- その8: バックエンド実装編②前編 ~レビュー投稿機能作成~
- その8.5: バックエンド実装編②後編 ~レビュー投稿機能作成~
- その9: バックエンド実装編③ ~レビューCRUD機能作成~
- その10: バックエンド実装編④ ~レビューCRUD機能作成その2~
- その11: バックエンド実装編⑤ ~新規大学・学部・研究室作成機能作成~
- その12: バックエンド実装編⑥ ~大学検索機能作成~
- その13: バックエンド実装編⑦ ~大学・学部・研究室編集機能作成~
- その14: バックエンド実装編⑧ ~コメント投稿機能~
- その15: バックエンド実装編⑨ ~コメント編集・削除機能~
- その16: バックエンド実装編⑩ ~ブックマーク機能~
- その17: バックエンド実装編⑪ ~排他制御・トランザクション処理~
- その18: バックエンド実装編⑫ ~マイページ機能作成~
- その19: バックエンド実装編⑬ ~管理者アカウント機能作成~
- その20: バックエンド実装編⑭ ~通知機能作成~
- その21: バックエンド実装編⑮ ~ソーシャルログイン機能作成~
☆フロントエンド実装編
- その22: フロントエンド実装編① ~メインコンテンツ領域作成~
- その23: フロントエンド実装編② ~ヘッダー作成~
- その24: フロントエンド実装編③ ~サイドバー作成~
- その25: フロントエンド実装編④ ~ホームページ作成~
- その26: フロントエンド実装編⑤ ~大学検索結果画面作成~
- その27: フロントエンド実装編⑥ ~大学詳細・学部一覧画面作成~
- その28: フロントエンド実装編⑦ ~学部詳細・研究室一覧画面作成~
- その29: フロントエンド実装編⑧前編 ~研究室詳細・レビュー画面作成前編~
- その29.5: フロントエンド実装編⑧後編 ~研究室詳細・レビュー画面作成後編~
- その30: フロントエンド実装編⑨ ~マイページ作成~
- その31: フロントエンド実装編⑩ ~パンくずリスト作成~
- その32: フロントエンド実装編⑪ ~ログイン・新規登録モーダル作成~
- その33: フロントエンド実装編⑫ ~大学作成・編集モーダル作成~
- その34: フロントエンド実装編⑬ ~学部作成・編集モーダル作成~
- その35: フロントエンド実装編⑭ ~研究室作成・編集モーダル作成~
- その36: フロントエンド実装編⑮ ~レビュー作成・編集モーダル作成~
- その37: フロントエンド実装編⑯ ~コメント一覧表示モーダル作成~
- その38: フロントエンド実装編⑰ ~編集履歴ページ・削除依頼ページ・通知ドロップダウン作成~
- その39: フロントエンド実装編⑱ ~トースト作成~
- その40: フロントエンド実装編⑲ ~退会ページ作成~
- その41: フロントエンド実装編⑳ ~ファビコン作成~
- その42: フロントエンド実装編㉑ ~レスポンシブデザイン対応~
☆テスト・デバッグ編
参考
軽く宣伝
YouTubeを始めました(というか始めてました)。
内容としては、Webエンジニアの生活や稼げるようになるまでの成長記録などを発信していく予定です。
現在、まったく再生されておらず、落ち込みそうなので、見てくださる方はぜひ高評価を教えもらえると励みになると思います。"(-""-)"








