Help us understand the problem. What is going on with this article?

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

はじめに

本記事では、テストをまだ書いたことのない人向けに

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

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

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

前提

本記事の対象者

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

本記事で取り扱うこと

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

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

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

環境

  • Laravel 5.8.x

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

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

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

localhost_8080_.png

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

tests/Feature/ExampleTest.php
<?php

namespace Tests\Feature;

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

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に代入されます。
そして、$response->assertStatus(200)で、そのレスポンスが正常であること(ステータスコードが200 OKであること)をチェックしています。

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

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

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

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

PHPUnit 7.5.12 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 448 ms, Memory: 14.00 MB

OK (1 test, 1 assertion)

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

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

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

ビューのテスト

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

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

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

routes/web.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
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文字列もテストできます。
Welcome画面にLaravelの文字が表示されていることを、テストコードで確認してみましょう。

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

tests/Feature/ExampleTest.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では以下コマンドでユーザー登録画面やログイン画面等を作成できるので、これを実行します。

$ php artisan make:auth

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

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

localhost_8080_2.png

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

localhost_8080_register.png

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

localhost_8080_home.png

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

ログインしていない状態で直接/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 Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

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
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 7.5.12 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 442 ms, Memory: 14.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:133
/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 Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

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 Illuminate\Support\Str;
use Faker\Generator as Faker;

$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 Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

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
  3 | Miss Shayna Deckow
(3 rows)

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

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

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

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

tests/Feature/HomeControllerTest.php
<?php

namespace Tests\Feature;

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

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

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

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

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

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

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

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

最後に

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

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

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

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

参考


  1. 正確には\Illuminate\Foundation\Testing\TestResponse 

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away