Laravel5.1（以降）でのテスト


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

##5.1で何が変わったか？

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

```php
public function testJson()
{
	//リクエスト＋レスポンス
    $response = $this->call('GET', '/users/json');
    //オブジェクト取得
    $obj = $response->getData();
    //アサーション
    $this->assertEquals("OK",$obj->status);
}
```
という感じで、PHPUnitのassertを使って、少々冗長でした。
これが、新しく、

```php
public function testJson()
{
	//チェック
    $this->get('/users/json')
         ->seeJson([
            'status'=>'OK',
        ]);
}
```
こんな感じでチェーンメソッドを使いより直感的に、完結に書けるように（も）なりました。

詳しくは、[Laravelの本家サイト](http://laravel.com/docs/5.1/testing)か、[こちら](http://laravelcoding.com/blog/laravel-5-beauty-testing)のサイトが参考になります。

##注意点

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

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

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

逆に向いていないのは、

* JSを使ったページ

JSを多く使ったページは[Selenumを利用する方がいい](http://qiita.com/zaburo/private/f11357170953a3c34b8f)でしょう。

##テストへの最適化

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

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

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

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

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

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

###とりあえず実行

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

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

```bash
cd LARAVEL_HOME（お好みで）
./vendor/bin/phpunit
```
とすると、tests以下の対象ファイルが実行されます。
標準では、ExampleTest.phpの中で、1つのテストと2つのアサート（評価）が行われているので、

```bash
OK (1 test, 2 assertions)
```
とグリーンで表示されるはずです。

###テストの対象

Laravel（というかPHPUnit）では、

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

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

###記述方法

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

```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という文字列が存在するかを確認しています。

簡単ですね。

##テスト用のサイト（画面）を用意する

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

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

* 簡単なForm（バリデーション付き）と簡単な表示
* Databaseのテスト
* 簡単なJSON


###準備

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

```bash
php artisan migrate
```

###Form

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

####Route
ひとまずRouteは

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

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

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

```bash
php artisan make:controller UsersController
```

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

```php
<?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

```html
<!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

```html
@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
```

####一覧表示

```php
@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
<?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で挿入された値もワンラインで確認できます。便利！。

###テストを実行してみる

テストは、

```bash
./vendor/bin/phpunit 
```

で、OKです。

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

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

```php
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

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

```php
public function indexJson()
{

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

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

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

####テストコード

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

###おまけ（調査中）

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

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

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

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

とりあえず素のlaracast/integratedなら動くので[こちら](http://qiita.com/zaburo/private/45cc4089cbbd9d5f52df)も参考にしてみてください。