[Laravel6.x]テスト ~備忘録~

##はじめに
この記事はプログラミング初学者による備忘録用の記事であり、また、少しでも他の初学者のお役に立てればと思い書いています。

今回は、テストを実装する際、再度公式ドキュメント等を見返すと手間がかかるので、Laravelのテストについて調べたことを備忘録としてまとめておきたいと思います。(新しい情報を見つけ次第、随時更新します)

間違いなどがございましたら、ご指摘のほどよろしくお願い致します。

##テストとは
入力に対して、意図した処理が実行できているかを確認するものだと思っています。

テストパターンが複雑で、スマートに書けないと感じたら、対象となるメソッドが責任を持ちすぎている可能性を疑い、設計の見直しを行うよう心がけています。

##テスト対象になるコード
・コードの一部を変更したときに、不具合が発生しそうなコード
・セキュリティ上、重要なコード
・メール送信など不具合があると致命的なコード
・テストを自動化した方が効率的になるコード
・複雑なロジック等を含むコード
・例外処理
など

##テストを実行する上で意識すべきこと
・誰が見ても分かりやすいテストコードを意識する

・全てのコードに対してテストを書くのではなく、必要最低限のテスト数に抑える

・AAA（Arrange、Act、Assert）を意識したテストコードを書く

・データの変更を伴うテストを書く場合は、before/afterの両方を検証する

・複雑なテストコードは可能な限り避ける

・実際のユースケースが想像できるテストデータを用意する

・テストが全部パスしていても、画面の表示が崩れていたりすることがあるので、画面上の見た目も意識したテストコードを書く(assertSee等を使う)

・テストが書きにくい場合、アプリケーション側のコードを変更することも検討する

・外部リソース（Amazon S3や外部APIなど）と連携するコードは、自動テストにおいてモック等でしかテストが実行できない場合が多いので、手動テストする



##テストの種類
``Unitテスト``と``Featureテスト``が存在します。(Laravel Duskを利用したブラウザテストも存在しますが、まだ実際に触れた機会がないので今回は記事の対象外としています。いつか試してみたい...。)

``Unitテストとは``
Unitテストは、コードの独立した一部をテストします。実際には、テスト対象となるクラスやメソッドなど、モジュール単位の動作を検証することに焦点を当ててテストを実行します。

``Featureテストとは``
>
Featureテストは、多くのオブジェクトがそれぞれどのように関しているかとか、JSONエンドポイントへ完全なHTTPリクエストを送ることさえ含む、コードの幅広い範囲をテストします。
``引用``[Laravel6.x テスト:テストの準備](https://readouble.com/laravel/6.x/ja/testing.html)

つまり、複数のクラスが連動して行う一連の動作をテストします。コントローラをテストする際によく使います。

##テストの準備
**・テスト用のDBを確認する**
テスト実行時に使用するデータベースは、Laravelインストール後のデフォルトの設定でSQLiteが使用されます。
**phpunit.xml**を確認してみると、``<server name="DB_CONNECTION" value="sqlite"/>``という箇所で、環境変数DB_CONNECTIONに**sqlite**が設定されています。
また、``<server name="DB_DATABASE" value=":memory:"/>``という箇所で、環境変数DB_DATABASEに**:memory:**が設定されています。
これらの設定により、テストでは軽量なSQLiteが使用され、データベースの保存先として[インメモリ](https://e-words.jp/w/%E3%82%A4%E3%83%B3%E3%83%A1%E3%83%A2%E3%83%AA.html)が使用されます。

``phpunit.xml``

```
<?xml version="1.0" encoding="UTF-8"?>
<!--略-->
<php>
   <server name="APP_ENV" value="testing"/>
   <server name="BCRYPT_ROUNDS" value="4"/>
   <server name="CACHE_DRIVER" value="array"/>
   <server name="DB_CONNECTION" value="sqlite"/>
   <server name="DB_DATABASE" value=":memory:"/>
   <server name="MAIL_DRIVER" value="array"/>
   <server name="QUEUE_CONNECTION" value="sync"/>
   <server name="SESSION_DRIVER" value="array"/>
</php>
<!--略-->
```
**・テストする箇所の優先順位を決める**
全てのコードに対してテストを実行することは現実的ではないので、自分の中で優先順位を決め、重要なところからテストを書いていく。

機能が複雑になると、思うようにテストを実行することが困難になるため、アプリケーション上で重要度が高いところを優先してテストを書いていくようにするのがベストではないかと思っています。

##テストを書く流れ
テストの書き方のパターンとして、**AAA(Arrange-Act-Assert)**というものがあります。

流れとしては、``準備・実行・検証``という順でテストを書いていきます。

各段階の役割
・準備 - ファクトリーなどを利用して、テストに必要となるデータを用意する
・実行 - テスト対象となるメソッドを実行する
・検証 - 実行結果のレスポンスを検証する

``参考文献:``[Arrange-Act-Assert](http://wiki.c2.com/?ArrangeActAssert)

後は下記にて``Unitテスト``と``Featureテスト``について少し深掘りしたことをまとめておきます。

##Unitテスト
``使い方``
テスト対象: 1クラスの1メソッド単位

ModelやMiddleware、FormRequestなど自分で作成した1クラスの1メソッド単位に対応するようにテストファイルを作成します。そのファイルの中に、対象クラスのメソッドの動作をテストするコードを記述していきます。

※コントローラクラスのメソッドはUnitテストではなく、Featureテストに書く。

``作成コマンド``
下記のコマンドでテストファイルを作成します。
命名規則は**アッパーキャメルケース**となっており、テストファイルは**testsディレクトリ**に自動生成されます。

```
php artisan make:test SampleTest --unit
```

``基本的なテストメソッドの書き方``

```tests/Feature/SampleTest.php
<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class SampleTest extends TestCase
{
    /**
     * 基本的なテスト例
     *
     * @return void
     */
    public function testBasicTest()
    {
　　　　　　　　　　　　$response = $this->get('/');
      $response->assertStatus(200);        
    }
}
```
``テストコードの解説``
上記の例のテストコードで**get()**と**assertStatus()**というメソッドが使用されています。

**getメソッド**は、アプリケーションに対して、GETリクエストを作成します。getメソッドを利用することで、web.phpで作成したルーティングを指定してGETリクエストを作成できます。
**assertStatusメソッド**は、PHPUnitで使われる``Assertメソッド``の一つであり、**クライアントのレスポンスが指定した(getメソッドで指定)HTTPステータスコードである**ことを宣言しています。

このように、PHPUnit機能テスト用に、数多くの追加アサートメソッドをLaravelは提供しています。それぞれのアサートで、json、get、post、put、deleteテストメソッドから返されたレスポンスへアクセスしてください。
``利用可能なアサート一覧:``[Laravel6.x HTTPテスト](https://readouble.com/laravel/6.x/ja/http-tests.html#assert-cookie)

``テストの実行``
下記コマンドでテストが実行されます。

```
//全てのテストを実行する
./vendor/bin/phpunit

//実行結果
PHPUnit 9.5.2 by Sebastian Bergmann and contributors.
.
1 / 1 (100%)

Time: 00:00.513, Memory: 22.00 MB

OK (1 tests, 1 assertions)
```
テストを指定して実行する場合、下記のようにテストメソッド名を指定して実行します。

```
//指定したテストメソッドのみ実行する
./vendor/bin/phpunit tests/Feature/ArticleControllerTest
```

``注意点``
・入出力のパターンを取りこぼさないよう、必要なテストをしっかりと作成する
・データベースや外部APIに依存しないテスト設計をする
・依存する場合は、データベース処理をモックする

##Featureテスト
``使い方``
テスト対象: HTTPリクエスト→HTTPレスポンスを通した一連の処理

１つのHTTPリクエスト単位の動き(1つのコントローラアクションに対応したテスト)をテストするために使用します。
Featureテストファイルの中身としては、コントローラのアクションメソッド単位でのテストメソッドを複数作成します。つまりは**ルーティング1つごとにテストファイルを作成する**ということです。

テストの形としては、リクエストを受けレスポンスを返すまでの過程をテストします。

``作成コマンド``

```
php artisan make:test SampleTest
```
``基本的なテストメソッドの書き方``

```php
<?php

namespace Tests\Feature;

use App\Models\User;
use App\Models\Article;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ArticleControllerTest extends TestCase
{
     use RefreshDatabase;
     
    /**
     * ログイン後、トップページの表示内容のテスト
     *
     * @return void
     */
    public function testAuthIndex()
    {
        $user = factory(User::class)->create();

        $response = $this->actingAs($user)
            ->get(route('articles.index'));

        $response->assertStatus(200);
    }
}
```
``テストコードの解説``
入力でDBレコードを作成するメソッドをテストする場合、``ファクトリー``を定義して利用しています。
ファクトリーとは、事前にファクトリーファイルでModelの情報を作成しておくことで、テスト時に自動でModelをnew/insert出来る仕組みとなっています。

ファクトリーでユーザデータを作成し、``actingAs()``を利用することでそのユーザデータで認証済みの状態(ログイン状態)にし、ログイン状態でないと遷移できないページのテストを実行可能にします。

また、actingAs()の第2引数に認証用でどのguardを使うかを指定することができます。

ちなみに、ファクトリーを定義する際、``use Faker\Generator as Faker;``が自動生成され、Fakerを利用したテストで頻繁に使われる文字列や数値を自動で生成してくれます。

##補足
###Factoryの作成
テストデータが必要な際は、ファクトリークラスを作成してファクトリークラス内でテストデータを作成します。

###外部APIを利用する際はServiceクラスを作成後、モックでテスト実行
テストが外部APIに依存している状態を避けるために、外部APIを扱うクラスの振る舞いを、別途定義したものにすり替える為にモックを利用する。

※補足内容はそれぞれ1つの記事としてまとめたいと思います。

##参考文献
[Laravel6.x テスト](https://readouble.com/laravel/6.x/ja/testing.html)
[Laravel6.x HTTPテスト](https://readouble.com/laravel/6.x/ja/http-tests.html)