• トップ
  • ブログ一覧
  • 【初学者向け】Laravelでのテスト - 導入 -
  • 【初学者向け】Laravelでのテスト - 導入 -

    みや(エンジニア)みや(エンジニア)
    2023.07.31

    IT技術

    はじめに

    こんにちは、今年の4月に新卒入社したみやです。まずは自己紹介からです。

    学生時代はプログラミングを触る機会は多くなく、

    ライトコードでエンジニアとして1からスタートしました。

    4~6月の3ヶ月間はwebエンジニアとしての基礎知識(HTML/CSS/PHP/SQL)とLaravel(基本部分)を身につける研修を受けました。7月からは実務に携わっていますが、まだまだ勉強中です。

    今回は、最近学んだ「Laravelでのテストの作成」について、自身のアウトプットを兼ねて初学者目線で紹介したいと思います。

    目次

    1. 本記事の概要
    2. テストとは?
    3. Laravelでのテスト
    4. まとめ

    本記事の概要

    はじめにテストについて調査し、整理しています。

    次に、Laravelのテストを紹介し、Laravel Breeze のインストール時にデフォルトで作成されるテストを見ながら、Laravelでのテストを理解します。

    本記事を通してわかること

    ・テストについて理解できる。

    ・Laravelでのテストの作り方がなんとなくわかる。

    テストとは?

    「テスト」について調べると以下のようなことが出てきます。

    ソフトウェアテストとは、開発中のソフトウェア(コンピュータプログラム)が意図した通りに動作するかを検証すること。テストにより欠陥(バグ)が発見されると、原因箇所を探し出して修正するデバッグ(debug)作業が行われる。

    引用 : IT用語辞典 e-Words

    テストはプログラムが正常に動作するかを検証する方法の一つで、テスト対象が期待通りに動いているかバグがないかを確認できます。

    テストには自らプログラムを動かして動作確認する「手動テスト」と検証用のコードにより自動で動作確認する「自動テスト」があります。「手動テスト」でもテストはできますが、総合的な作業量や安全性などさまざまな観点から「自動テスト」の方が有用であり(一概には言えませんが)、本記事では「自動テスト」を題材とします(以降、テスト=自動テスト)。

    ただし、テストを書くとなるとシステムを動かすコード以外にテスト用のコードを書く必要になり、労力が増えてしまいます(テストを専門に担当するテスターというエンジニアも存在するそうです)。

    当時の感想は「え、面倒くさそう。二度手間みたいで、本当に必要あるの?」でしたが、テストは開発で大きな役割を担っているようです。

    テストの必要性

    開発において、例えばあるコードを変えたら違う部分の動作が変わってしまうことがあります。テストがないと、変化の原因やどの部分を直せば良いのかを見つけるのが難しくなってしまいます(複数人での大きな開発をイメージすると分かりやすい)。最悪の場合、意図せぬ変化に気づかないままリリースしてしまうことも考えられます。その時にテストがあれば、意図せぬ変化に対応しやすくなります。他にもテストによりバグが早期発見でき、テスト作成のコスト以上にコストを削減できると言ったことも考えられます。このようにバグを生まずにスムーズに開発を行うにはテストが必要になると考えています。

    Laravelでのテスト

    LaravelにはPHPテストのフレームワークであるPHPUnitが標準搭載されています。Laravelをインストールした段階でPHPUnitがcomposerでインストールされています。実際にvendorディレクトリ(composerによりインストールしたパッケージのソース本体が入っている)にphpunitディレクトリがあることが確認できます。また、LaravelではPHPUnitを使いやすいようにラッパーしたものが用意されています。こちらについては後ほど説明します。

    今回はPHPUnitを使ったLaravelのテストを紹介していきます。

    動作環境

    • M1 Mac (macOs Ventura 13.3.1)
    • Docker Desktop for Mac(4.19.0

    環境構築

    検証用なのでLaravelアプリケーションを簡単に構築できるLaravel Sailを使って環境構築します(Docker Desktopを事前にインストールしている必要があります)。アプリケーションを作成したいディレクトリに移動し、以下のコマンドでLaravel Sailの新しいアプリケーションを作成します(最後にパスワードを求められるのでPCのパスワードを入力)。

    1curl -s "https://laravel.build/project" | bash

    上記の通りに実行するとprojectフォルダが作成され、その中にアプリケーションが作成されます(フォルダ名は任意)。また、シェルエイリアスの設定も行っておきましょう(以下、シェルエイリアスの設定を前提)。

    次にprojectディレクトリに移動して、sail up -d コマンドを実行し、Dockerコンテナを立ち上げます。コンテナが立ち上がった後にhttp://localhost/にアクセスし、Laravelのトップページが表示されることを確認します。これでLaravel sailの立ち上げは完了です。

    Laravel Breeze

    Laravel BreezeとはLaravel公式でスターターキットとして紹介されていて、認証機能が簡単に実装できるパッケージです。本記事ではLaravel Breezeのインスール時にデフォルトで作成されるテストファイルを参照しながら、テストについて理解をしていきます。

    では、projectディレクトリにいることを確認し、以下のコマンドでLaravel Breezeをインストールします。

    1sail composer require laravel/breeze --dev
    2sail artisan breeze:install blade
    3sail artisan migrate
    4sail npm install
    5sail npm run dev

    インストールが終わり、再度http://localhost/にアクセスすると、画面右上に「Log in」「Register」が表示されていることを確認して、完了です。

    Laravelのテスト作成の流れ

    詳しい手順は公式サイトに記載されていますが、簡単に紹介します。

    アプリケーションのtestsディレクトリにテストファイルを格納します。

    testsディレクトリにはUnitディレクトリとFeatureディレクトリがあります。公式サイトによると、それぞれ以下の特性を持ちます。

    ディレクトリ格納するテストの種類特徴
    Unit単体テスト単一のメソッドをテストすることが多い。アプリケーションのDBやその他のフレームワークサービスにアクセスできない。素のPHPUnitを使う。
    Feature機能テスト複数のオブジェクトが相互作用する方法や、JSONエンドポイントへの完全なHTTPリクエストなど、コードの広い部分をテストする。Laravel用に拡張したPHPUnitを使う。

    テストファイルの作成コマンドは以下の通りです(テストファイル名UserTestは適宜変更する)。

    Feature Test を作成

    1sail artisan make:test UserTest

    Unit Testを作成

    1sail artisan make:test UserTest --unit

    作成されたテストファイルにはテストによく使うクラスのインポート処理、TestCaseの継承などが既に記述されている雛形が用意されています。このファイルにテストするためのコードを加えていく流れです。

    テストの実行

    試しにLaravel Breezeインストール時にデフォルトで作成されているテストファイル「tests/Feature/Auth/RegistrationTest.php」をテストしてみましょう。以下のコマンドで実行できます。

    1sail artisan test tests/Feature/Auth/RegistrationTest.php

    少し待つと以下の画面が表示されます。

    テスト成功

    デフォルトで作成されているテストなので何も変更していなければ、テストを無事通過します。

    画像のTests: 2 passed (4 assertions) が意味するのは、2つのテストメソッドを実行し、4つのアサーションを行ったという意味です。テストメソッド・アサーションについては次の節で紹介します。

    失敗した場合は以下のようにエラーの原因が表示されます。

    テストファイル構成

    先ほどテストを実行した「tests/Feature/Auth/RegistrationTest.php」を例にテストファイルの構成を紹介します。これは新規ユーザを登録する処理を検証するテストです。

    1<?php
    2
    3namespace Tests\Feature\Auth;
    4
    5use App\Providers\RouteServiceProvider;
    6use Illuminate\Foundation\Testing\RefreshDatabase;
    7use Tests\TestCase;
    8
    9class RegistrationTest extends TestCase
    10{
    11    use RefreshDatabase;
    12
    13    public function test_registration_screen_can_be_rendered(): void
    14    {
    15        $response = $this->get('/register');
    16
    17        $response->assertStatus(200);
    18    }
    19
    20    public function test_new_users_can_register(): void
    21    {
    22        $response = $this->post('/register', [
    23            'name' => 'Test User',
    24            'email' => 'test@example.com',
    25            'password' => 'password',
    26            'password_confirmation' => 'password',
    27        ]);
    28
    29        $this->assertAuthenticated();
    30        $response->assertRedirect(RouteServiceProvider::HOME);
    31    }
    32}

    継承しているクラス

    RegistrationTestクラスが今回作成したテストクラスです。このクラスはTests\TestCaseクラスを継承しています。Tests\TestCaseクラスはLaravel用に拡張されたPHPUnit、つまりPHPUnitのラッパーです。これによりLaravelでのテストの記述が容易になっています。一方、単体テストである「tests/Unit/ExampleTest.php」(下画像)を見てみると、ExampleTestクラスがPHPUnit\Framework\TestCaseクラスを継承しています。このクラスは「vendor/phpunit/phpunit/src/Framework/TestCase.php」に存在しており、素のPHPUnitということがわかります。

    1<?php
    2
    3namespace Tests\Unit;
    4
    5use PHPUnit\Framework\TestCase;
    6
    7class ExampleTest extends TestCase
    8{
    9    /*
    10     * A basic test example.
    11     */
    12    public function test_that_true_is_true(): void
    13    {
    14        $this->assertTrue(true);
    15    }
    16}

    テストメソッド

    test_registration_screen_can_be_rendered()test_new_users_can_register() がテストメソッドです。テストを実行した時にTests: 2 passedと表示されたのは、これら2つのテストメソッドが実行されたからです。メソッド名から分かるようにtest_registration_screen_can_be_rendered()は登録画面が表示されているかをテストするメソッド、test_new_users_can_register()は新しいユーザを登録できているかをテストするメソッドです。このように登録という一連の流れを複数のテストメソッドを用いて検証しています。

    アサートメソッド

    テストメソッドの中で登場するaseert〇〇メソッドはLaravelが提供しているアサートメソッドです。アサートメソッドはある条件を満たしているかどうかを検証するメソッドです。アサートメソッドは様々な種類が存在し、公式サイトで紹介されています。HTTPに関するアサートメソッドの場合、ここで紹介されています。種類が多いので、「tests/Feature/Auth/RegistrationTest.php」で登場するアサートメソッドだけを紹介します。

    ・assertStatus()

    レスポンスに指定HTTPステータスコードがあることをチェックするメソッドです。$response = $this->get('/register');/register にGETリクエストを送信し、返ってきたレスポンスが$request に格納されます。そして、$response->assertStatus(200); はレスポンスにステータスコード:200があること、つまりリクエストが正常に受け付けられたかどうかを検証しています。

    ・assertAuthenticated()

    ユーザが認証済みであるかを検証するメソッドです。少しユーザの登録処理を行っている「Http/Controllers/Auth/RegisteredUserController.php」のstoreアクションを見てみましょう。詳しくは省略しますが、DBへのユーザの登録処理を行った後にそのユーザでログイン処理をして、'/dashboard' にリダイレクトさせています。よって、ユーザがログイン状態(認証済み)であれば、正常に登録が完了したことがわかります。

    1<?php
    2
    3namespace App\Http\Controllers\Auth;
    4
    5use App\Http\Controllers\Controller;
    6use App\Models\User;
    7use App\Providers\RouteServiceProvider;
    8use Illuminate\Auth\Events\Registered;
    9use Illuminate\Http\RedirectResponse;
    10use Illuminate\Http\Request;
    11use Illuminate\Support\Facades\Auth;
    12use Illuminate\Support\Facades\Hash;
    13use Illuminate\Validation\Rules;
    14use Illuminate\View\View;
    15
    16class RegisteredUserController extends Controller
    17{
    18    /*
    19     * Display the registration view.
    20     */
    21    public function create(): View
    22    {
    23        return view('auth.register');
    24    }
    25
    26    /**
    27     * Handle an incoming registration request.
    28     *
    29     * @throws \Illuminate\Validation\ValidationException
    30     */
    31    public function store(Request $request): RedirectResponse
    32    {
    33        $request->validate([
    34            'name' => ['required', 'string', 'max:255'],
    35            'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class],
    36            'password' => ['required', 'confirmed', Rules\Password::defaults()],
    37        ]);
    38
    39        $user = User::create([
    40            'name' => $request->name,
    41            'email' => $request->email,
    42            'password' => Hash::make($request->password),
    43        ]);
    44
    45        event(new Registered($user));
    46
    47        Auth::login($user);
    48
    49        return redirect(RouteServiceProvider::HOME);
    50    }
    51}

     

    ・assertRedirect()

    レスポンスが指定するURIへのリダイレクトであることをチェックするメソッドです。「Providers/RouteServiceProvider.php」を見ると、定数HOME'/dashboard' なので、'/dashboard'にリダイレクトされているかを検証しています。今回の処理ではコントローラで登録処理後に'/dashboard'にリダイレクト処理するように実装しているので、$response->assertRedirect(RouteServiceProvider::HOME); が通れば正常に処理が行われていることを示せます。

    最初は、今回実行されたアサートメソッドは3つであるのにテスト実行後の画面で、Tests: 2 passed (4 assertions)と4つのアサーションを行ったことになっているのはなぜかと疑問に思いました。これはassertRedirect メソッドに理由があります。assertRedirect メソッドは「vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php」で定義されていて、内容を見るとassertTrueメソッドとassertLocationメソッドが内部で行われていることがわかります。これにより、4 assertionsという表示になったと解釈しています(assertRedirectメソッドをコメントアウトしてテストを実行すると、Tests: 2 passed (2 assertions)になる)。

    1public function assertRedirect($uri = null)
    2{
    3    PHPUnit::assertTrue(
    4        $this->isRedirect(),
    5        $this->statusMessageWithDetails('201, 301, 302, 303, 307, 308', $this->getStatusCode()),
    6    );
    7
    8    if (! is_null($uri)) {
    9        $this->assertLocation($uri);
    10    }
    11
    12    return $this;
    13}

    まとめ

    本記事はここまでです。少し中途半端に終わった気もしますが、本記事を見ればなんとなくLaravelのテストが理解できるところまで行けたと思います。今回紹介したテストであれば、手動テスト(登録画面から新規登録を行い、ダッシュボードにリダイレクトされるのを確認・DBに登録されていることの確認を手動でやる)でも問題ないかと思うかもしれませんが、ある機能に対してテストを一回しか行わないということはないでしょう。その時に自動テストがあれば、正確に手間がかからずにテストを行えるはずです。より複雑な処理になってくればなおさらです。

    また機会があれば、自分でテストを作る流れやテスト作成中に出現するクラス(RefreshDatabaseなど)やメソッド(setUp)の紹介などより詳しい部分に踏み込んだ記事を書いていきます。

    本記事を通して分かったこと

    ・テストとは?

    プログラムが期待通りに動いているか・バグがないかを確認すること。

    ・Laravelでのテストの作り方は?

    アサートメソッドを駆使して、期待する処理がされるか確認するコードを記述してファイルを作成する。

     

    最後までご覧いただきありがとうございました。

    みや(エンジニア)

    みや(エンジニア)

    おすすめ記事