【初心者向け】Laravelテストチュートリアル

Laravel6

はじめに

「ようこそ・・・『テストの世界』へ・・・」

本記事では、テストコードをまだ書いたことのないプログラミング初学者向けに

「今日からテストを書いてみようかな」

と思ってもらえるよう、チュートリアル形式で簡単なテストの流れを説明します。

題材はLaravelですが、他のフレームワークでも同じようなことはできると思います。

注:本記事は、以前に私がQiitaに投稿したLaravel5.8のテストチュートリアルをLaravel6で動作するよう改訂したものです。

目次

前提

本記事の対象者

  • Laravel初心者で、
    • テストをまだ書いたことの無い人
    • テストで何ができるのか知らない人
    • テストに興味はあるが忙しくてどう書けば良いのかまだ調べられていない人

本記事で取り扱うこと

  • ごく簡単なHTTPテストの書き方

本記事で取り扱わないこと

  • テストの全般的な話(利点、注意点など)
  • テストケースの作り方
  • CIツールによるテスト自動化

環境

  • Laravel 6.4.x

簡単な画面(HTTPレスポンス)のテスト

まずは、Laravelを触ったことのある人なら一度は見たことのある?Welcome画面をテストしてみます。

これが正常に表示されることを、目視ではなくテストコードで確認してみましょう。

Welcome画面

Laravelをインストールすると、既にtests/Feature/ExampleTest.phpという、テストコードのサンプルが存在します。

tests/Feature/ExampleTest.php

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testBasicTest()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

このExampleTestは、Tests\TestCaseを継承しており、テストに関する様々なメソッドが使えます。

$response = $this->get('/')で、'/'にアクセスした(GETリクエストした)結果のレスポンス(注1)が$responseに代入されます。

注1: 正確には\Illuminate\Foundation\Testing\TestResponseが代入されます。

そして、$response->assertStatus(200)で、そのレスポンスが正常であること(ステータスコードが200 OKであること)をチェックしています。

ステータスコードについては以下を参考にしてください。

このExampleTestを実行するには、Laravelのルートフォルダで以下コマンドを実行してください。

$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php

すると、以下の結果が表示されました。

PHPUnit 8.4.3 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 405 ms, Memory: 16.00 MB

OK (1 test, 1 assertion)

OK、と表示されています。

つまり、'/'へアクセスした(GETリクエストした)結果、正常にレスポンスが返ってきた(HTTPレスポンスステータスコードが200 OKだった)ということです。

これをテストコードで確認することができました。

https://shonansurvivors.booth.pm/items/3096761

ビューのテスト

ただ、このテストでわかったのは、正常にレスポンスが返ってきた、ということまでです。

Welcome画面が表示されたのかどうかまでテストできていません。

現状のルーティングを見ると、'/'へアクセスすると、'welcome'というビューが表示されることがわかります。

routes/web.php

<?php

// 略

Route::get('/', function () {
    return view('welcome');
});

welcomeというビューは、具体的にはresources/views/welcome.blade.phpというビューのテンプレートです。

このresources/views/welcome.blade.phpこそがあのWelcome画面なので、このテンプレートが使われているかどうかをテストで確認してみます。

tests/Feature/ExampleTest.phpに以下のコードを追加します。

tests/Feature/ExampleTest.php

<?php

// 略

public function testBasicTest()
{
    $response = $this->get('/');

    $response->assertStatus(200)
        ->assertViewIs('welcome'); // 追加
}

このようにassertViewIsメソッドで、どんなビューが使われたのかをテストすることができます。

修正後のExampleTestを実行してみます。

$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php
// 略
OK (1 test, 2 assertions)

こちらも結果はOKとなりました。

あのWelcome画面が表示されていることを、テストコードで確認することができました。

さらにassertSeeメソッドを使うと、表示(注2)されている文字列もテストできます。

注2: 正確にはhtml内にその文字列が含まれているかをテストします。htmlのタグなども確認の対象になります。

Welcome画面にLaravelの文字が表示されていることを、テストコードで確認してみましょう。

tests/Feature/ExampleTest.phpに以下のコードを追加します。

tests/Feature/ExampleTest.php

<?php

// 略

public function testBasicTest()
{
    $response = $this->get('/');

    $response->assertStatus(200)
        ->assertViewIs('welcome')
        ->assertSee('Laravel'); // 追加
}

修正後のExampleTestを実行してみます。

$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php
// 略
OK (1 test, 3 assertions)

こちらも結果はOKとなりました!

ログイン中かどうかを絡めたテスト

ここから先は、ユーザーがログイン中かそうでないかによって結果が異なる画面をテストしていきます。

laravel/uiライブラリからユーザー登録画面やログイン画面等を作成できるので、これを実行します。

$ php artisan migrate
$ composer require laravel/ui
$ php artisan ui vue --auth
$ npm install
$ npm run dev

上記コマンドの詳細については、以下の記事を参考にしてください。

Welcome画面にユーザー登録画面とログイン画面へのリンクが追加されました。

LoginとRegisterが追加されたWelcome画面

以下、テストではなく人力でユーザー登録画面にアクセスし、ユーザー登録を行なっています。

Register画面

ユーザー登録が完了すると、以下のホーム画面へ遷移します。なお、ログイン画面からログインした場合も、このホーム画面へ遷移します。

ホーム画面

ホーム画面は、ログイン中の時のみ表示されます。

ログインしていない状態で直接/homeへアクセスすると、ホーム画面は表示されず、ログイン画面へリダイレクトされます。

この「ホーム画面は、ログイン中の時のみ表示される」ということをテストコードで確認してみましょう。

ホーム画面の表示は、php artisan make:authコマンドによって作成された、app\Http\Controlles\HomeControllerで行われています。

このHomeControllerをテストするためのHomeControllerTestを作成します。

以下コマンドを実行すると、テストの雛形を作成できます。

$ php artisan make:test HomeControllerTest

作成されたテストの雛形の内容は、以下になります。

tests/Feature/HomeControllerTest.php

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class HomeControllerTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testExample()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

このtests/Feature/HomeControllerTest.phpを以下の通り編集します。

tests/Feature/HomeControllerTest.php

<?php

// 略

public function testExample()
{
    $response = $this->get('/home'); // 変更(ホーム画面のパスに変更)

    $response->assertStatus(200)
        ->assertViewIs('home') // 追加(ここでの'home'は、ホーム画面で使われているビュー名)
        ->assertSee('You are logged in!'); // 追加(ホーム画面で表示されているメッセージ)
}

このHomeControllerTestを、以下コマンドで実行します。

$ ./vendor/bin/phpunit ./tests/Feature/HomeControllerTest.php

すると、以下の通り、OKではなくFAILURES!となりました。

PHPUnit 8.4.3 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 1.15 seconds, Memory: 16.00 MB

There was 1 failure:

1) Tests\Feature\HomeControllerTest::testExample
Expected status code 200 but received 302.
Failed asserting that false is true.

/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:183
/var/www/tests/Feature/HomeControllerTest.php:20

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Expected status code 200 but received 302.と出力されています。

ログイン中でない状態でアクセスしたので、リダイレクトを示す302のステータスコードが返ってきた、ということです。

そこで、ログインした状態を作り出してテストしてみます。

DBには、先ほど人力でユーザー登録画面にアクセスして登録したユーザーデータが存在するので、テストではこのユーザーを使うことにします。

# select id, name from users;
 id |        name        
----+--------------------
  1 | shonansurvivors
(1 rows)

tests/Feature/HomeControllerTest.phpを以下の通り編集します。

tests/Feature/HomeControllerTest.php

<?php

namespace Tests\Feature;

use App\User; // 追加
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;


class HomeControllerTest extends TestCase
{
    public function testExample() 
    {
        $response = $this
            ->actingAs(User::find(1)) // 追加
            ->get(route('home'));

        $response->assertStatus(200)
            ->assertViewIs('home')
            ->assertSee('You are logged in!');
    }
}

actingAsメソッドで、そのユーザーとしてログイン済の状態になります。

また、User::find(1)で、DBのusersテーブルからid1であるユーザーを取ってきています。

改めてHomeControllerTestを、以下コマンドで実行します。

$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php

すると、OKとなりました。

OK (1 test, 3 assertions)

ログイン中の状態で/homeへアクセスすると、

  • ステータスコードが200となること
  • homeというビューが使われていること
  • You are logged in!が、html中に存在すること

が確認できました。

このようにログイン中かどうかが絡む機能も、テストコードで確認できます。

テストに必要なDBのデータを自動で準備する

今回はたまたま開発環境のDBにユーザーのデータが存在し、テストではこれを利用してログインを行いましたが、 テストに必要なDBのデータ準備もコードで自動化してみます。

Laravelではテスト用のDBのデータを作るのに、ファクトリというものを使う方法があります。

database/factories/UserFactory.phpが、ユーザーデータを作るためのファクトリです。

database/factories/UserFactory.php

<?php

use App\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;

$factory->define(User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // password

        'remember_token' => Str::random(10),
    ];
});

このファクトリの詳細については本記事では触れませんが、これを使うといい感じにダミーのデータを作ってくれます。

ファクトリについてもっと知りたい方は、以下記事を参考にしてください。

このファクトリを使うよう、tests/Feature/HomeControllerTest.phpを以下の通り変更します。

tests/Feature/HomeControllerTest.php

<?php

namespace Tests\Feature;

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


class HomeControllerTest extends TestCase
{
    public function testExample()
    {
        $user = factory(User::class)->create(); // 変更(ファクトリでユーザーデータを作成)

        $response = $this
            ->actingAs($user) // 変更(ファクトリで作ったユーザーデータでログイン中状態を作る)
            ->get(route('home'));

        $response->assertStatus(200)
            ->assertViewIs('home')
            ->assertSee('You are logged in!');
    }
}

ファクトリで作成されたユーザーにて、ログイン中の状態を作っています。

以下コマンドでHomeControllerTestを実行すると、結果はOKとなりました。

$ ./vendor/bin/phpunit ./tests/Feature/HomeControllerTest.php
// 略
OK (1 test, 3 assertions)

ただ、これだとHomeControllerTest実行のたびにDBにダミーのユーザーデータが1件また1件...と作られてしまいます。

sample=# select id, name from users;
 id |        name        
----+--------------------
  1 | shonansurvivors
  2 | Dr. Jayce Wiegand
(2 rows)

2件目がファクトリで作られたダミーのユーザーデータです。

これに対しては、ひとつのやり方として、テスト用のDatabaseTransactionsを使うという方法があります。

これを使うと、テストの実行後にDBをロールバックし、テスト実行中にDBに作ったデータが残らなくなります。

tests/Feature/HomeControllerTest.phpで、DatabaseTransactionsを使うようにします。

tests/Feature/HomeControllerTest.php

<?php

namespace Tests\Feature;

use App\User;
use Illuminate\Foundation\Testing\DatabaseTransactions; // 追加
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;


class HomeControllerTest extends TestCase
{
    use DatabaseTransactions; // 追加

    public function testExample()
    {
        // 以下変更点なしにつき省略

以下コマンドでHomeControllerTestを実行すると、結果は引き続きOKとなりますが・・・

$ ./vendor/bin/phpunit ./tests/Feature/HomeControllerTest.php
// 略
OK (1 test, 3 assertions)

テスト終了後のDBを見ると、ユーザーデータはテスト開始前時点から増えてはいません(2件のまま)でした。

sample=# select id, name from users;
 id |        name        
----+--------------------
  1 | shonansurvivors
  2 | Dr. Jayce Wiegand
(2 rows)

これでDBにダミーデータが残ることなく、何度でもテストを実行できます。

最後に

以上で、本記事のテスト体験ツアーは終了です。

テストでどのようなことができるか、イメージはつかめたでしょうか。

本記事をきっかけに、テストを書き始めていただければ幸いです。

「納期は守る」「品質も確保する」 「両方」やらなくちゃあならないってのが「エンジニア」のつらいところだな