laravel ユニットテストあれこれ

# 概要

- laravelのユニットテストについて諸々をまとめる

# laravelのテストの種類

- laravelは2種類のテストをデフォルトでサポートしている。
  - フィーチャーテスト
    - Viewやエンドポイントごとのテスト
  - ユニットテスト
    - 関数ごとのテスト
- 今回はユニットテストについてまとめてみる。

# テストの実施方法

- テストは下記の順に実施して実行する。
    1. コマンドを用いたテストクラスの作成
    1. テスト用関数の記載
    1. コマンドを用いたテストの実行

# コマンドを用いたテストクラスの作成

1. 下記コマンドを実行してテストクラスを作成する。（`アプリ名ディレクトリ/tests/Unit`直下にFooTest.phpが作成される）

    ```terminal
    $ php artisan make:test FooTest --unit
    ```

# テスト用関数の記載

1. 先に作成したFooTest.phpを開きテスト用の関数を定義する。
1. テスト用関数は関数名の最初に`test`と記載するか当該関数にアノテーションで`@test`と記載する。両方の定義方法で下記にテスト用関数を定義してみる。
    - 関数名にtestとつける

        ```php
        public funtion test_example() {
            //テストの処理を記載
        }
        ```

    - アノテーションで@testとつける

        ```php
        /**
         * @test
         */
        public funtion example() {
            //テストの処理を記載
        }
        ```

1. 上記の2種類のテスト用関数の命名方法はどちらが正解ということはない。参画しているプロジェクト内で決まっていればそれに従おう。（テスト用関数の命名にあたっては日本語を用いることが割とある。 → テスト失敗時にテスト用関数名が出力されるので度のチェックが失敗したかわかりやすいように。）
1. 今回は関数名にtestとつける方法で一つテストを書いてみよう。FooTest.phpに下記の関数を定義する。

    ```FooTest.php
    public function test_example() {
        $this->assertTrue(true);
    }
    ```

1. このテストはassertTrue()の引数にtrueが来ればテストOK、falseが来ればテストNGとなるテストである。（これだけだとコードを一切チェックしていない無意味なテスト）

# コマンドを用いたテストの実行

1. 下記コマンドを実行してテストを実行する。

    ```terminal
    $ ./vendor/bin/phpunit tests/Unit/FooTest.php
    ```

1. 下記のように出力される。

    ```terminal
    PHPUnit 9.5.20 #StandWithUkraine
    
    .                                                                   1 / 1 (100%)
    
    Time: 00:00.044, Memory: 8.00 MB
    
    OK (1 test, 1 assertion)
    ```

1. FooTest.phpの内容を下記のように修正してテストをエラーにしてみる。

    ```FooTest.php
    public function test_example() {
        $this->assertTrue(false);
    }
    ```

1. テストを実行すると下記のようにテストがエラーになる。

    ```terminal
    PHPUnit 9.5.20 #StandWithUkraine
    
    F                                                                   1 / 1 (100%)
    
    Time: 00:00.046, Memory: 8.00 MB
    
    There was 1 failure:
    
    1) Tests\Unit\FooTest::test_example
    Failed asserting that false is true.
    
    /var/www/html/tests/Unit/FooTest.php:16
    
    FAILURES!
    Tests: 1, Assertions: 1, Failures: 1.
    ```

# テストを詳しく書いてみる

1. 流石に`$this->assertTrue(true);`だと何もコードをチェックしていないのでもう少し突っ込んで記載してみる。
1. 何かしらのクラス内の関数の実行結果が意図したものになっているかをチェックするようなテストを記載してみる。
1. 下記のように`app/Services`にBarServiceClassを定義する。定義したcalculation()関数は引数の値に応じて戻り値が変化する。（引数が10未満のとき→0を返す。引数が10より大きく20
1. 未満のとき→引数をそのまま帰す。引数が20より大きいとき→引数を2倍して返す。）

    ```app/Services/BarService.php
    <?php
    
    namespace App\Services;
    
    class BarService
    {
        public function calculation(int $value)
        {
            if ($value < 10) {
                return 0;
            }
        
            if (($value > 10) && ($value < 20)) {
                return $value;
            }
        
            if ($value > 20) {
                return $value * 2;
            }
        }
    }
    ```

1. 下記のようにFooTest.phpを記載する。各条件に一致するように関数を定義

    ```tests/Unit/FooTest.php
    <?php
    
    namespace Tests\Unit;
    
    use PHPUnit\Framework\TestCase;
    use App\Services\BarService;
    
    class FooTest extends TestCase
    {
        /**
         * @return void
         */
        public function test_入力値が10より小さい(): void
        {
            $barService = new BarService();
            $result = $barService->calculation(9);
        
            $this->assertSame(0, $result);
        }
    
        /**
         * @return void
         */
        public function test_入力値が10より大きく20未満(): void
        {
            $value = 15;
            $barService = new BarService();
            $result = $barService->calculation($value);
        
            $this->assertSame($value, $result);
        }
        /**
         * @return void
         */
    
        public function test_入力値が20以上(): void
        {
            $value = 25;
            $barService = new BarService();
            $result = $barService->calculation($value);
        
            $this->assertSame($value * 2, $result);
        }
    }
    ```

1. テストを実行するとエラー無く完了した。
1. 今度はテストをあえて失敗させてみる。BarService.phpを下記のように修正する。（引数が10未満の場合100が変えるように修正）

    ```app/Services/BarService.php
    <?php
    
    namespace App\Services;
    
    class BarService
    {
        public function calculation(int $value)
        {
            if ($value < 10) {
                return 100;
            }
        
            if (($value > 10) && ($value < 20)) {
                return $value;
            }
        
            if ($value > 20) {
                return $value * 2;
            }
        }
    }
    ```

1. テストを実行すると下記のようにエラーが変える。

    ```
    PHPUnit 9.5.20 #StandWithUkraine
    
    F..                                                                 3 / 3 (100%)
    
    Time: 00:00.041, Memory: 8.00 MB
    
    There was 1 failure:
    
    1) Tests\Unit\FooTest::test_入力値が10より小さい
    Failed asserting that 100 is identical to 0.
    
    /var/www/html/tests/Unit/FooTest.php:18
    
    FAILURES!
    Tests: 3, Assertions: 3, Failures: 1.
    ```