35
31

More than 5 years have passed since last update.

Laravel5.1(以降)でのテスト

Last updated at Posted at 2015-11-23

以前にもLaravelでのテストについては書いたのですが、5.1以降でテストがより強化(Integrated Testと呼ぶことにする)、簡素化されたので試してみたいと思います。

5.1で何が変わったか?

例えば、以前、JsonAPIのテストを書きたい時は、

public function testJson()
{
    //リクエスト+レスポンス
    $response = $this->call('GET', '/users/json');
    //オブジェクト取得
    $obj = $response->getData();
    //アサーション
    $this->assertEquals("OK",$obj->status);
}

という感じで、PHPUnitのassertを使って、少々冗長でした。
これが、新しく、

public function testJson()
{
    //チェック
    $this->get('/users/json')
         ->seeJson([
            'status'=>'OK',
        ]);
}

こんな感じでチェーンメソッドを使いより直感的に、完結に書けるように(も)なりました。

詳しくは、Laravelの本家サイトか、こちらのサイトが参考になります。

注意点

Integrated Testは、非常にお手軽ですが、いくつかの制限があります。
特にクライアント側で実行されるJavaScriptを利用したサイトは基本的にテストできません。Seleniumと連携させる方法もありますが、少し完成度が低いようです。

今のところ向いているのは

  • Laravelのお作法に沿った画面のテスト
  • WebAPIのテスト

逆に向いていないのは、

  • JSを使ったページ

JSを多く使ったページはSelenumを利用する方がいいでしょう。

テストへの最適化

本末転倒感もありますが、Integrated Testを有効に活用するためには、テストしやすいようにコーディングする必要があります。主な留意点としては下記のようなものがあります。

  • 入力はFormとSubmitで行う。
  • その他のボタンはで実現する(bootstrap使っていれば問題ないかと)。
  • javascriptは極力利用しない。
  • Form-SubmitやにJSを仕込んだ場合、JSは無視され、action先、href先には移動するので、正常系のテストはできます。

まあTFの考えからすれば、テストできる仕様で実装すべきということかもしれん。ただ、管理画面ならJS無しも不可能ではないですが、フロントは無理ですよね。

Laravelでのテスト実行と書き方

Laravelでは、testsディレクトリ以下にテストを書くルールになっています。

LARAVEL_HOME/phpinit.xmlで定義されているので変更することもできます。

とりあえず実行

既に、tests以下にはExampleTest.phpというサンプルがあるので、とりあえず、それを実行してみます。

前提でLaraelが正常にインストールされ、php artisan serve等で起動していることが前提です。

cd LARAVEL_HOME(お好みで)
./vendor/bin/phpunit

とすると、tests以下の対象ファイルが実行されます。
標準では、ExampleTest.phpの中で、1つのテストと2つのアサート(評価)が行われているので、

OK (1 test, 2 assertions)

とグリーンで表示されるはずです。

テストの対象

Laravel(というかPHPUnit)では、

  • xxxTest.phpというファイル(クラス)名
  • public function testXXXX()というファンクション名

のいずれかをテスト対象とします(他にも@testとコメントに入れるというものある)。

記述方法

では、ExampleTest.phpの中身を見てみます。

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->visit('/')
             ->see('Laravel 5');
    }
}
  • TestクラスはTestCaseクラスを継承します。
  • LaravelのHomeに訪問し、Laravel 5という文字列が存在するかを確認しています。

簡単ですね。

テスト用のサイト(画面)を用意する

長いので、必要ない人は下の「テストをする」を見て下さい。

テスト用に下記2つを用意してみます。

  • 簡単なForm(バリデーション付き)と簡単な表示
  • Databaseのテスト
  • 簡単なJSON

準備

DBも使いたいので、.envを設定して下さい。
で、migrateで生成できるusersテーブルをテスト用に利用してみたいと思います。

php artisan migrate

Form

usersのnameとemailにインサートする簡単なFormを作ってみます。
挿入後はリスト表示してみます。

Route

ひとまずRouteは

Route::get('users/create','UsersController@create'); //フォーム表示
Route::post('users/store','UsersController@store'); //Insert処理
Route::get('users','UsersController@index'); //登録情報リスト表示

の3つを用意しておきます。

Controller

処理を書くControllerを用意します。

php artisan make:controller UsersController

処理を書いて行きます。
保存の際は、nameにだけ、requiredバリデーションを適用しています。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;

use App\User;

class UsersController extends Controller
{
    //一覧表示
    public function index()
    {
        //create query
        $query = User::query();
        //get all data
        $users = $query->get();
        //return view
        return view('users.index')->with('users',$users);
    }
    //Form表示
    public function create()
    {
        //return view(form)
        return view('users.create');
    }
    //保存処理
    public function store(Request $request)
    {
        //validation
        $inputs = $request->all();
        //rules
        $rules = [
            'name'=>'required',
        ];
        //message
        $messages = [
            'name.required'=>'名前は必須です。',
        ];

        //バリデート
        $validation = \Validator::make($inputs,$rules,$messages);

        //バリデーションエラーだったら、元のページに戻る
        if($validation->fails())
        {
            return redirect()->back()->withErrors($validation->errors())->withInput();
        }

        //create User
        $user = User::create();

        $user->name = $request->name;
        $user->email = $request->email;

        //save
        $user->save();

        //redirect to list 
        return redirect()->to('/users');
    }
}

View

では、表示用のViewを用意します。resources/view以下にusersフォルダを作成し、以下のファイルを置いていきます。

共通View

テストなので各ファイルに書いてもいいですが、いちおう用意します。
layout.blade.php

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
</head>
<body>

    <div class="container">
        @yield('content')
    </div>

<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

</body>
</html>

入力フォーム

次に入力フォーム。create.blade.php

@extends('layout')

@section('content')
<h1>新規登録</h1>

<form method="post" action="/users/store">
    <div class="form-group">
        <label>名前</label>
        <input type="text" name="name" value="" class="form-control">
        <span class="help-block">{{$errors->first('name')}}</span>
    </div>
    <div class="form-group">
        <label>E-Mail</label>
        <input type="text" name="email" value="" class="form-control">
    </div>
    <input type="hidden" name="_token" value="{{csrf_token()}}">
    <input type="submit" value="登録" class="btn btn-primary">
</form>
@stop

一覧表示

@extends('layout')

@section('content')
    <h1>一覧表示</h1>

    <div class="row">
        <div class="col-sm-12">
            <a href="/users/create" class="btn btn-primary" style="margin:20px">新規登録</a>
        </div>
    </div>

    <table class="table table-striped">
        @foreach($users as $user)
            <tr>
                <td>{{$user->id}}</td>
                <td>{{$user->name}}</td>
                <td>{{$user->email}}</td>
            </tr>
        @endforeach
    </table>
@stop

以上です。

テストをする

新しいクラスを用意してもいいですが、ここでは、ExampleTest.phpを編集することにします。

Formをテストする

どうやって、入力してSubmitボタンを押すのか私も気になりましたが、非常に簡単です。
なお、Userのemailには標準でunique属性が付いているので、テストの毎に違うemailが入るようにFakerを利用してemailを自動生成しています。

やっていることは、

  • user/createにアクセス(できるか?)
  • 新規登録とう文字列が含まれているか?
  • nameにHogeと入力
  • emailにFakerがユニークに生成したemailを挿入
  • Submitボタンを押す(valueで指定)
  • 移動したページに一覧表示が含まれるか?
  • データベースに挿入したemailが存在するか?
<?php

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

class ExampleTest extends TestCase
{
    public function testCreateOK()
    {
        //emailを生成
        $faker = Faker\Factory::create('ja_JP');
        $email = $faker->unique()->email;

        $this->visit('users/create')
             ->see('新規登録')
             ->type('Hoge','name')
             ->type($email,'email')
             ->press('登録')
             ->see('一覧表示');

        //check db
        $this->seeInDatabase('users',['email'=>$email]);
    }

}

typeだけでなく、radioボタン、selectはselectを。checkboxはcheckを利用するようです。

また、seeInDatabaseで挿入された値もワンラインで確認できます。便利!。

テストを実行してみる

テストは、

./vendor/bin/phpunit 

で、OKです。

バリデーションエラーの場合

バリデーションエラーの場合もチェックしてみたいと思います(functionのみ書きます)。

public function testCreateNG()
{
    $this->visit('users/create')
         ->see('新規登録')
         ->type('','name')
         ->type('foo@hoge.com','email')
         ->press('登録')
         ->see('名前は必須です。');
}

nameを空で送信してみます。
メッセージで「名前は必須です。」が出ているはずなので、それが出ているかチェックしています。

Formはあとはいろいろと工夫すれば済むようです。

JSON APIをテストする

次にJSON APIをテストしてみたいと思います。
DBに溜まった情報を、単純にJSONで出力してみます。いちおう、ステータスも変えまします。

Controller

コントローラーにメソッドを追加します。

public function indexJson()
{

    $query = User::query();
    $users = $query->get();

    $response['status'] = "OK";
    $response['data'] = $users;

    return \Response::json($response);
}

テストコード

public function testJson()
{
    $this->get('/users/json?key=kagi')
         ->seeJson([
            'status'=>'OK',
        ]);
}

便利。

おまけ(調査中)

あまりに便利なので、Laravel以外のテストにも使えればと思い少し調べましたが、結論NGでした。
元クラスで$baseUrlが定義されているので、変更してみたましたが、どうしても、ローカルを見てるみたいです。

$this->baseUrl = "http://www.yahoo.co.jp";
$this->visit("/")->see("Yahoo");

としてみましたが、アクセスできませんでした。
Laravelではない、普通の環境でlaracast/integratedを利用した場合はOKだったので、Laravelへの統合クラスのバグかもしれません。

LaravelTestCase.phpの中で、baseUrlが"http://localhost/"でスタティックにreturnされているからみたいです。

とりあえず素のlaracast/integratedなら動くのでこちらも参考にしてみてください。

35
31
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
35
31