この前Laravelのテストのやり方を書いたときは、API用のだった。
普通のWeb画面で動くシステムをテストするのって、どうすんの?
てことで、またぞんざいな感じで書いてみる。
1.どんなテストをするか考える。
この前勢いに任せてLaravel8で画面だけ作ったけれど、まだCRUDまで作ってないんだよな。なので今回は認証して画面遷移するところだけテスト対象とするよ。
・Laravel標準ログイン画面でログイン
・メニュー表示
・照会画面を表示
・更新画面を表示
・更新できないユーザーの時、更新できないメッセージが表示されることを確認
画面のテストはchromeを自動で動かしてやる方法もあるらしいけれど、今回は普通にテストの機能を使ってテストするよ。
ちなみにCRUDは今後テスト駆動であーしてこーしたことを記事にして、laravelerのハートをわしづかみにしてやるぜ!乞うご期待!(うそ)
2.テスト用の環境を作る
前回は先走ってtestクラスを先に作っちゃったから、順番がよくわからない記事になっちゃったよ。その反省を踏まえ、今回は焦らず環境作りから書いていくよ。やる内容はこんな感じだ。
・テスト用DBの定義を.env.testingに書く
・phpunit.xmlにテスト環境の設定を書く
・テスト用DBの作成
なんか、ついつい設定よりDB作るとか、結果がわかる作業を先にしたくなるよね。
でも、落ち着けよブラザー。説明の都合上、定義を書くところから書いていきます。
2.1. テスト用DBの定義を.env.testingに書く
俺はテスト用のDBなんていらねーぜ!いつだって一発勝負さ!って人は、ここから先読み飛ばしてもらっていいです。てか、そういう人はテストしない気がする。そもそもこんな記事読まないか。
臆病者の私は、失敗しないようにおどおどしながらテスト用のDBを作ります(外国語直訳風)。
そういったテスト用の設定は、通常の.envとは別に、.env.testingというファイルに設定を書いておけます。これならおどおどしなくても平気だね。
laravelプロジェクトフォルダーの中にある、.envファイルをコピーして、「.env.testing」にリネームします。ドットふたつついているけど、これで正解です。
で、以下の部分を書き換えます。
APP_ENV → 「tesitng」に変更する
APP_KEY → 今書いてあるキーを消して、空白にする。後でコマンド入れて設定します。
DB_DATABASE → テスト用のDB名に変更します。今回はlara8test_testにします。
なんかプロジェクト名にtestって入れちゃったから、テスト作るときわかりづらくなった。よい子はマネしないでね。
2.2. phpunit.xmlにテスト環境の設定を書く。
laravelプロジェクトを作成すると、自動的にphpunitがついてくる。phpunitを使わなければphpunit.xmlは放っておいても平気だけど、phpunitを使うときはここにテストの設定を入れるよ。
phpunit.xmlの初期設定はこんな感じ。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<!-- <server name="DB_CONNECTION" value="sqlite"/> -->
<!-- <server name="DB_DATABASE" value=":memory:"/> -->
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>
で、この中の以下の部分を変更する。
・<server ~/>のserverを「env」に変更する。
・コメントになっているDB_CONNECTIONをちゃんと書く。.env.testingのDB_CONNECTIONに書いた値と一緒にしてね。
・コメントになっているDB_DATABASEをちゃんと書く。これも.env.testingのDB_CONNECTIONに書いた値と一緒だ。
ここまで変更したら、プロジェクトのフォルダーのところでphp artisanを使ってAPP_KEYを設定しよう。
> php artisan key:generate --env=testing
実行すると、.env.testingのAPP_KEYに値が設定されているよ。
2.3.テスト用DBの作成
ようやくDBを作ります。
> mysqladmin create lara8test_test
ちなみにこのコマンドは、mysqlとかmariaDB用だからね。普通にDB作ればいいだけの話です。
作ったDBの中にテーブルを作成したいわけだけど、これはmigrateを使ってやります。migrateする先のDBを、テスト用のDBにしてやるわけです。
php artisan migrate --env=testing
これでテスト環境が整いました。
3.testクラスを作る
もういい加減覚えたろう。laravelでなんか作るときは、php artisan makeだよね。
そこから先は、いつも忘れるんだけど。
> php artisan make:test TestinputControllerTest
作ったコントローラの名前の後ろに「Test」をつけたよ。
作られたソースはlaravelプロジェクトのtestsフォルダーの下のfeatuerフォルダーに作られる。featureは機能テスト用だそうな。ユニットテスト用にしたいときは、「--unit」を後ろにつけるとunitフォルダーに作るらしい。
さて、今回実際テストしたいロジックは、下記の通り。ちょびっとしかないね。
public function show($id)
{
//ぞんざいな照会画面を表示する
return view('testinput.show');
}
public function edit($id)
{
if(Gate::denies('user')) {
//ぞんざいな更新画面を表示する
return view('testinput.edit');
} else {
session()->flash('editmsg', 'あんた更新できないよ!!');
return view('menu/menu');
}
}
まずはぞんざいな照会画面のテストだ。
showは本来$idを渡しているから、その値でいろんなテストができそうだけど、ぞんざいに作ったから$idなんて使ってない。適当な値を渡しているだけだ。
なので、テストも雑に作ってみる。
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class TestinputControllerTest extends TestCase
{
/**
* ぞんざいな照会 正常ケース
*
* @test
* @return void
*/
public function show_ぞんざいな照会正常ケース()
{
$response = $this->get(route('testinput.show',' '));
$response->assertStatus(200);
}
}
@testアノテーションをつけるかfunctionの名前をtest~とつければ、テストメソッドとして実行される。今回は@testアノテーションをつけてみたよ。
routeで行き先を指定して、そのHTTPレスポンスのステータスが200ならOK,というテストだ。
せっかちなので、一回これで動かしてみる。
動かす時は、サーバーが動いているところのプロジェクトフォルダで「./vendor/bin/phpunit」を実行すればいい。
何言っているかわかんないって?端的に言えば、php artisan serverで動かしたときはローカルPCのコマンドプロンプトで実行、homesteadみたいな仮想サーバーで動かしているときは、仮想サーバーに接続して実行しろってことさ。
$ ./vendor/bin/phpunit
そのまま実行すると…あれ、OKだけど、「3 tests」って書いてある。
agrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 00:01.741, Memory: 22.00 MB
OK (3 tests, 3 assertions)
vagrant@homestead:~/code/lara8test$
それはね。プロジェクト作ったままだと「ExampleTest.php」がfeatureとunitに入っていて、その中にひとつずつテストが設定されているから、それも動いちゃったんだね。
ちぇ。いらないExampleTest.phpは速攻消してしまおう。
それで改めて実行すると。
vagrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 00:01.372, Memory: 20.00 MB
OK (1 test, 1 assertion)
1 testに変わったよ。一つのテストが成功したってことだね。
しかし、これでいいのか?チェックがなさ過ぎてつまんない。
なので、「ログインしていないとTestinputにつながらない」ようにしよう。
web.phpのTestinputのrouteに、そんなような仕込みを入れる。
Route::resource('testinput', TestinputController::class)->middleware('auth');
それでもう一回テストを流してみると。
vagrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.
F 1 / 1 (100%)
Time: 00:01.636, Memory: 20.00 MB
There was 1 failure:
1) Tests\Feature\TestinputControllerTest::show_ぞんざいな照会正常ケース
Expected status code 200 but received 302.
Failed asserting that 200 is identical to 302.
/home/vagrant/code/lara8test/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:186
/home/vagrant/code/lara8test/tests/Feature/TestinputControllerTest.php:21
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
エラーになった!「ステータス200って言ってるけど、302が返ってきたよ」みたいなことを言っている。多分。
4.認証データを仕込む
では、ログインしている体でテストができるようにしよう。
予めテスト用のDBにユーザーを登録しておいてもいいんだけれど、そうするとテスト用のユーザーを設定しておかないとテストが上手く動かなくなってしまう。なるべくなら、テストを実行したらいつでも同じようにテストが流れてほしいよね。
なので、テストの中でデータを作成するようにします。
テストデータは、factoryで仕込むんだよ。factoryって大量データを仕込める奴、って認識だったけど、どちらかというとテストを動かす時にデータを仕込める奴、だったんだね。だから「データをDBに保存しないモード」が存在するのか。
余談はさておき、ユーザーデータを仕込んでみよう。
/**
* ぞんざいな照会 正常ケース
*
* @test
* @return void
*/
public function show_ぞんざいな照会正常ケース()
{
$user = User::factory()->create();
$response = $this->actingAs($user)->get(route('testinput.show',' '));
$response->assertStatus(200);
}
追加ポイントはふたつ。
・User::factory()->create();を追加⇒userにfactoryで指定したデータを1件作成する。
・->actingAs($user)を追加⇒作成したデータでログインした体にしてくれる。
これで流してみる。
vagrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 00:01.916, Memory: 24.00 MB
OK (1 test, 1 assertion)
上手くいった!
では、更新系のテストも仕込んでみよう。
/**
* ぞんざいな更新 責任者のケース
*
* @test
* @return void
*/
public function edit_ぞんざいな更新責任者のケース()
{
$user = User::factory()->create(['access_auth' => '1']);
$response = $this->actingAs($user)->get(route('testinput.edit',' '));
$response->assertStatus(200)
->assertSessionMissing('editmsg');
}
/**
* ぞんざいな更新 管理者のケース
*
* @test
* @return void
*/
public function edit_ぞんざいな更新管理者のケース()
{
$user = User::factory()->create(['access_auth' => '9']);
$response = $this->actingAs($user)->get(route('testinput.edit',' '));
$response->assertStatus(200)
->assertSessionMissing('editmsg');
}
/**
* ぞんざいな更新 担当者のケース
*
* @test
* @return void
*/
public function edit_ぞんざいな更新担当者のケース()
{
$user = User::factory()->create(['access_auth' => '0']);
$response = $this->actingAs($user)->get(route('testinput.edit',' '));
$response->assertStatus(200)
->assertSessionHas('editmsg');
}
更新処理は、勝手に追加した「access_auth」という項目が'1'(責任者)、'9'(管理者)のときはそのまま表示するけど、'0'(担当者)だったら「あんた更新できないよ!!」ってメッセージをフラッシュで表示している。
なので、アクセスしに行ったときに担当者だったらフラッシュメッセージを出していること、責任者と管理者だったら逆にメッセージを出していないことをテストしたい。
なので、ステータスのチェック+フラッシュメッセージのチェックを行うよう設定する。
assertSessionHas⇒セッションが指定したデータを持っていることを宣言
assertSession⇒セッションが指定したキーを持っていないことを宣言
これで実行してみると。
vagrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.
.... 4 / 4 (100%)
Time: 00:02.187, Memory: 26.00 MB
OK (4 tests, 7 assertions)
上手く出来たっぽい。
でも本当か?本当にやり切ったのか?大人なんて信用できない!
てなわけで、わざわざエラーになるようソース変えちゃう。よい子はマネしないでね。
public function edit($id)
{
if(Gate::denies('user')) {
//ぞんざいな更新画面を表示する
session()->flash('editmsg', 'あんた更新できないよ!!');
return view('testinput.edit');
} else {
return view('menu/menu');
}
}
メッセージを出す条件を逆にしてみました。
これでさっきのテストを流すと。
vagrant@homestead:~/code/lara8test$ ./vendor/bin/phpunit
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.
.FFF 4 / 4 (100%)
Time: 00:02.426, Memory: 26.00 MB
There were 3 failures:
1) Tests\Feature\TestinputControllerTest::edit_ぞんざいな更新責任者のケース
Session has unexpected key [editmsg].
Failed asserting that true is false.
/home/vagrant/code/lara8test/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:1040
/home/vagrant/code/lara8test/tests/Feature/TestinputControllerTest.php:38
2) Tests\Feature\TestinputControllerTest::edit_ぞんざいな更新管理者のケース
Session has unexpected key [editmsg].
Failed asserting that true is false.
/home/vagrant/code/lara8test/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:1040
/home/vagrant/code/lara8test/tests/Feature/TestinputControllerTest.php:53
3) Tests\Feature\TestinputControllerTest::edit_ぞんざいな更新担当者のケース
Session is missing expected key [editmsg].
Failed asserting that false is true.
/home/vagrant/code/lara8test/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:865
/home/vagrant/code/lara8test/tests/Feature/TestinputControllerTest.php:68
FAILURES!
Tests: 4, Assertions: 7, Failures: 3.
ちゃんとエラーになった!大人も信用していいんだ!
こんな風にアサートを増やしていけば、いろんなチェックを追加していけるよ。
テストを作っていくのは面倒だけど、システムに機能を追加していくとデグレードの確認に手間がかかるから、なるべく早い段階でテストを準備していくほうがいいね。
それじゃー今日はこんなところだ。また逢う日まで、再見!