0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

実務1年目駆け出しエンジニアがLaravel&ReactでWebアプリケーション開発に挑戦してみた!(テスト・デバッグ編②)~Unitテスト2[ポリシー]~

0
Posted at

実務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で設定されているため、テスト実行時に参照されます。

\project-root\src\phpunit.xml
    <testsuites>
        <testsuite name="Unit">
            <directory>tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory>tests/Feature</directory>
        </testsuite>
    </testsuites>

この下にさらにPoliciesというフォルダを作成して、その中にReviewPolicyTest.phpというファイルを作成してください。

\project-root\src\tests\Unit\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ブロックでインスタンス化した$policyupdateメソッドを実行します。

これは、バックエンド実装編で作成したものです。

最後に、Assert(確認)の部分です。

        $this->assertTrue($result);

assertTrueはPHPUnitが用意しているメソッドで、引数がtrueならテスト成功を意味します。

※アサーションメソッドについては、以下のドキュメントにすべて書かれているので気になる場合は参照してください。
https://net-newbie.com/phpunit/assertions.html

2.3 テスト実行

以下のコマンドをPHPのコンテナの中で実行することでテストを実行できます。

実行コマンド

/var/www
$ php artisan test --filter=ReviewPolicityTest

結果は以下の通りです。
image.png

まだ、テストケースを一つしか作成していないので、「1 passed」と表示されています。
成功していることが分かります!

試しに、Assertの部分を以下のように反転させてあえて失敗させてみると...??

        $this->assertTrue(!$result);

失敗した件数と、どこで失敗したのかが表示されます。
image.png

今回は挙動を見るためにあえて失敗させてみたので、テストケースのAssertの部分は確認が終わったら元に戻しておきましょう。

また、これはDBに登録や削除はしないので何度実行しても問題ありません。

2.4 振り返り(結局何をしているのか)

これは、過去に作成したポリシー(どのユーザーがどの操作をすることができるのか)をテストしているものです。

\project-root\src\app\Policies\ReviewPolicy.php
    // ユーザーがレビューを編集できるかどうかを判定
    public function update(User $user, Review $review)
    {
        // 自分が投稿したレビューのみ編集可能
        return $user->id === $review->user_id;
    }

ユーザーのIDとレビュー投稿者のIDが一致しているかどうかを見ています。
これにより、「自分が投稿したレビューが誰かに編集される」こともないし、「自分が他の人のレビューを編集したりすることができないこと」を保証しています!

2.5 テストケース追加

同様に、falseが返るケースとdeleteについてのテストケース用のメソッドも追加しましょう。

assertFalseもPHPUnitが用意しているメソッドで、引数が逆にfalseなら成功とするものです。

\project-root\src\tests\Unit\Policies\ReviewPolicyTest.php
<?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);
    }
}

最終的なコードは上記のようになります。

ファイルを保存出来たら、再びテストを実行しましょう!

実行コマンド

/var/www
$ php artisan test --filter=ReviewPolicyTest

感じに「4 passed」と出ていれば成功です!
image.png

createはDB登録もしたいので、Featureテストの方に回そうかなと思います。

3. コメントポリシー

同じ要領で、コメントポリシーのテストも作成しましょう。
先ほどと同様の場所に新規のファイルを作成してください。

\project-root\src\tests\Unit\Policies\CommentPolicyTest.php
<?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);
    }
}

レビューにはない「管理者かどうか」という観点が増えますが、なんてことはないでしょう!

実行してみます。

実行コマンド

/var/www
$ php artisan test --filter=CommentPolicyTest

以下のように「5 passed」と出ていれば成功です!
image.png

4. ブックマークポリシー

同じノリでブックマークポリシーに関しても作成してみましょう。

\project-root\src\tests\Unit\Policies\BookmarkPolicyTest.php
<?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);
    }
}

更新メソッドがないので楽ですね。

実行コマンド

/var/www
$ php artisan test --filter=BookmarkPolicyTest

以下のような結果が得られればOK
image.png

5. 研究室ポリシー

こちらは、削除のみです。

\project-root\src\tests\Unit\Policies\LabPolicyTest.php
<?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テストに回したいと思います。

実行コマンド

/var/www
$ php artisan test --filter=LabPolicyTest

実行結果
image.png

6. 学部ポリシー

研究室ポリシーと全く同じです。

\project-root\src\tests\Unit\Policies\FacultyPolicyTest.php
<?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);
    }
}

実行コマンド

/var/www
$ php artisan test --filter=FacultyPolicyTest

実行結果
image.png

7. 大学ポリシー

こちらも同じです。

\project-root\src\tests\Unit\Policies\UniversityPolicyTest.php
<?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);
    }
}

実行コマンド

/var/www
$ php artisan test --filter=UniversityPolicyTest

実行結果
image.png

また、これまでに作成したテストケースをまとめて実行するなら以下です。
実行コマンド

/var/www
$ php artisan test --testsuite=Unit

実行結果
image.png

全部で18個通っていればOK!!

このようにまとめて実行することで、(今回はなかったですが)バグを見つけて修正したとこで別のところでバグがまた発生しないか(いわゆるデグレがおきていないか)を確認することができます!

ここまでできていたら、今日はおしまいです!
コミット・プッシュ、PR作成・マージ、ブランチ削除を忘れずにしておきましょう。

8. まとめ・次回予告

お疲れ様でした。
今回は、Unitテストの2回目ということで、ポリシーについてのテストを作成・実行しました。

テストの書き方では、「AAAパターン」という書き方を採用したことで読みやすいテストコードを作ることができましたね!

次回は、引き続きUnitテストのモデルについて作成・実行していこうと思います!

これまでの記事一覧

☆要件定義・設計編

☆環境構築編

☆バックエンド実装編

☆フロントエンド実装編

☆テスト・デバッグ編

参考

軽く宣伝

YouTubeを始めました(というか始めてました)。
内容としては、Webエンジニアの生活や稼げるようになるまでの成長記録などを発信していく予定です。

現在、まったく再生されておらず、落ち込みそうなので、見てくださる方はぜひ高評価を教えもらえると励みになると思います。"(-""-)"

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?