7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Laravel5.1でユニットテストを行う

Last updated at Posted at 2017-05-19

#はじめに
Laravelの開発が徐々に軌道に乗ってきたので、いよいよ本格的なテストに力を入れていこうと思います。
Laravelには様々なテスト機能がありますが、その中でも比較的理解できる部分に絞って書いていこうかと。
モックやらミドルウェアやら使えたら便利なんだろうけどなぁ...。

#初期設定
##データベース
テストを行うときに、データベースと連携してゴニョゴニョするような処理があると思います。
Laravelさんはかしこいので実環境のテストを行ったあとにロールバックしたり、テスト用のDBを用意したり、そもそもDBを用意する必要がなかったりといろいろ方法はあるみたいですが...。
今回はテスト用のDBを用意することにしました。
ただし、テストの性質からインメモリ機能を利用します。
###設定手順
設定を変更する必要があるファイルは以下の通りです。

  • config/database.php
  • phpunit.xml
  • tests/TestCase.php
config/database.php
    'connections' => [
// 2017-10-05追記:不要
//        database_path('database.sqlite'),     // 変更
        'testing' => [
            'driver'   => 'sqlite',
            'database' => ':memory:',         // 追記
            'prefix'   => '',
            'options'  => [
                PDO::ATTR_PERSISTENT => true, // 追記
            ]
        ],
phpunit.xml
<php>
    <env name="APP_ENV" value="testing"/>
    <env name="CACHE_DRIVER" value="array"/>
    <env name="SESSION_DRIVER" value="array"/>
    <env name="QUEUE_DRIVER" value="sync"/>
    <env name="DB_DATABASE" value="testing"/> <!-- 追記 2017-10-05 DB_CONNECTION => DB_DATABASE へ -->
</php>

これでDBを実装することなくテストができるようになりました。

##View/Model
実際にテストを行う画面を以下に示しておきます。
###ログイン画面

auth/login.blade.php
<div class="content panel panel-primary">
    <div class="panel-heading"><h5>Login</h5></div>
    <form class="form-horizontal panel-body" role="form" method="post" action="/auth/login">
        {!! csrf_field() !!}

        <div class="form-group">
            <label class="control-label col-md-3 col-md-offset-1" for="email">メールアドレス</label>
            <div class="col-md-7">
                <input type="text" class="form-control" name="email" value="{{ old('email') }}">
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-3 col-md-offset-1" for="password">パスワード</label>
            <div class="col-md-7">
                <input type="password" class="form-control" name="password" value="{{ old('password') }}">
            </div>
        </div>

        <div class="col-md-offset-4 col-md-7">
            <button type="submit" class="btn btn-success btn-block btn-lg">ログイン</button>
        </div>
    </form>
</div>

###ユーザー登録画面

auth/register.blade.php
<div class="content panel panel-primary">
    <div class="panel-heading"><h5>Register</h5></div>
    <form class="form-horizontal panel-body" role="form" method="post" action="/auth/register">
        {!! csrf_field() !!}
        <div class="form-group">
            <label class="control-label col-md-3 col-md-offset-1" for="name">ユーザー名</label>
            <div class="col-md-7">
                <input type="text" class="form-control" name="name" value="{{ old('name') }}">
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-3 col-md-offset-1" for="email">メールアドレス</label>
            <div class="col-md-7">
                <input type="email" class="form-control" name="email" value="{{ old('email') }}">
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-3 col-md-offset-1" for="password">パスワード</label>
            <div class="col-md-7">
                <input type="password" class="form-control" name="password" value="{{ old('password') }}">
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-3 col-md-offset-1" for="password_confirmation">パスワード確認</label>
            <div class="col-md-7">
                <input type="password" class="form-control" name="password_confirmation" value="{{ old('password_confirmation') }}">
            </div>
        </div>

        <div class="col-md-offset-4 col-md-7">
            <button type="submit" class="btn btn-warning btn-block btn-lg">登録</button>
        </div>
    </form>
</div>

###ルート

app/Html/Routes.php
Route::get('auth/login',     'Auth\AuthController@getLogin');
Route::post('auth/login',    'Auth\AuthController@postLogin');
Route::get('auth/logout',    'Auth\AuthController@getLogout');
Route::get('auth/register',  'Auth\AuthController@getRegister');
Route::post('auth/register', 'Auth\AuthController@postRegister');
Route::get('home', function(){ return view('home'); });

この辺はマニュアルまんまですね。

#テスト
###テストコード生成
php artisan make:test LoginTestと打ち込んでテストコードを生成します。

###とりあえずテスト実行
./vendor/bin/phpunitコマンドを叩いて正常にテストができることを確認します。
導入に関しての詳しいお話はグーグル先生に丸投げしときます。
もし仮に、

1) Warning
No tests found in class "TestCase".

的なエラーコードが出たとしたら次のように対応してあげてください。

tests/TesCase.php
// class TestCase extends Illuminate\Foundation\Testing\TestCase
abstract class TestCase extends Illuminate\Foundation\Testing\TestCase
{...

###テストコード生成
先ほど生成したtests/LoginTest.phpをいじっていきます。

tests/LoginTest.php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

use App\User;  // 状況に応じて使うモデルを定義
class sample extends TestCase
{

    use DatabaseMigrations; // これでテスト時にマイグレーションしてくれる

    /**
     * @before
     * テスト実行前に行う処理を記述できる。
     * 今回はデータベースのマイグレーションを実行した。
     */
    public function setUpDatabase()
    {
        $this->artisan('migrate');
    }
    public function testユーザー登録_成功(){
        $this->visit('/auth/register')
            ->type('test user','name')
            ->type('test@test.com','email')
            ->type('test123','password')
            ->type('test123','password_confirmation')
            ->press('登録')
            ->seePageIs('home')
        ;
        $this->seeInDatabase('users', [
            'name'=> 'test user',
            'email'=>'test@test.com',
        ]);
    }
    /**
     * @tests
     * testsアノテーションを付けることでテストとしてみなされる
     */
    public function ログイン成功()
    {
        $this->visit('/')
            ->see('ログイン')
            ->type('test@test.com','email')
            ->type('test123','password')
            ->press('ログイン')
            ->seePageIs('home')
        ;
    }
    public function testログイン後() {
        $user = User::first();
        $this->actingAs($user) // 注4
            ->visit('/chat')
            ->see("ようこそ、{$user->name}さん!")
        ;
    }
    ...
}

#所感
...という風に、アレをしてその後コレをして...って感じでテストを記述できるのですごくわかりやすいです。
ただこれ、あくまでもUI側のテスト的な側面が大きいんですよね。
実際はUI変更ありまくりだからController単体でテストしたかったり、もっと変更に耐えるテストコードにしたかったりとやりたいことは多いですが、頭が追いつかない。
おいおい勉強していくことにします。

ただひとつ言えることは、「画面・機能に合わせてテストコードを書く」のではなく、「テストしやすいように画面・機能を作っていく」ことが大事なのだということ。

あと個人的に疑問に思ってたこと「クラスの中のprivate関数に対してどうテストするのか?本来privateで記述する必要のあるものもpublicで書かなきゃいけないの?」ってのがあったんですが、「phpのリフレクションとかClosure::bindを利用すればいいんじゃないかな」って言われて目からうろこでした。

や、普通に考えればそんなアホみたいな話はないんですけどねー。

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?