#はじめに
Laravelの開発が徐々に軌道に乗ってきたので、いよいよ本格的なテストに力を入れていこうと思います。
Laravelには様々なテスト機能がありますが、その中でも比較的理解できる部分に絞って書いていこうかと。
モックやらミドルウェアやら使えたら便利なんだろうけどなぁ...。
#初期設定
##データベース
テストを行うときに、データベースと連携してゴニョゴニョするような処理があると思います。
Laravelさんはかしこいので実環境のテストを行ったあとにロールバックしたり、テスト用のDBを用意したり、そもそもDBを用意する必要がなかったりといろいろ方法はあるみたいですが...。
今回はテスト用のDBを用意することにしました。
ただし、テストの性質からインメモリ機能を利用します。
###設定手順
設定を変更する必要があるファイルは以下の通りです。
- config/database.php
- phpunit.xml
- tests/TestCase.php
'connections' => [
// 2017-10-05追記:不要
// database_path('database.sqlite'), // 変更
'testing' => [
'driver' => 'sqlite',
'database' => ':memory:', // 追記
'prefix' => '',
'options' => [
PDO::ATTR_PERSISTENT => true, // 追記
]
],
<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
実際にテストを行う画面を以下に示しておきます。
###ログイン画面
<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>
###ユーザー登録画面
<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>
###ルート
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".
的なエラーコードが出たとしたら次のように対応してあげてください。
// class TestCase extends Illuminate\Foundation\Testing\TestCase
abstract class TestCase extends Illuminate\Foundation\Testing\TestCase
{...
###テストコード生成
先ほど生成した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を利用すればいいんじゃないかな」って言われて目からうろこでした。
や、普通に考えればそんなアホみたいな話はないんですけどねー。