Laravel でのテストの基本。Feature テストでリスト表示画面をテストします。
テスト
前回までに、ToDo を一覧で表示するリスト画面を作成して、テストデータの準備方法を学びました。
今回のテーマは、自動テストです。前回までは、動作が正しいことを目視で確認していましたが、テストコードを作成することで自動で行えるようにします。
自動化することで、修正や拡張した後に、前は正しく動いたものが修正後に動かなくなったという問題を防ぐことができます。
テストケース
仕様から考えられるテストケースは下記になります。
- 表示されたタスクの件数が正しいこと
- ステータスが未着手のタスクのみ表示されていること
- 未着手のデータがないときはリストが空であること
- タスクテーブルにレコードがない場合は空で表示されること
これらを自動でテストできるようにしていきます。
テストデータの修正
始めに、4 つのテストケースのためのデータを用意します。
ケース別にテストデータを準備しやすいように前回作成したモデルファクトリに修正を加えます。
database/factories/TaskFactory
に以下を追記してください。
$factory->state(Task::class, 'Ready', function (Faker $faker) {
return ['status' => 1,'title' => 'Ready'];
});
$factory->state(Task::class, 'Doing', function (Faker $faker) {
return ['status' => 2,'title' => 'Doing'];
});
$factory->state(Task::class, 'Done', function (Faker $faker) {
return ['status' => 3,'title' => 'Done'];
});
$factory->state(Task::class, 'notReady', function (Faker $faker) {
return ['status' => 4,'title' => 'notReady'];
});
上記はステータス毎のデータを定義したものです。 このようにすることで、ステータス毎にテストデータを簡単に用意できます。 例えば、未着手を 5 件、そのほかのスタータスを 1 件ずつ用意するコードは下記になります。
factory(Task::class,5)->state('Ready')->create();
factory(Task::class,1)->state('Doing')->create();
factory(Task::class,1)->state('Done')->create();
factory(Task::class,1)->state('notReady')->create();
具体的な使用方法はテスト実装とともに解説します。
テスト実装
テストクラスを作成していきます。
Laravel の公式ドキュメントの翻訳は下記です。
https://readouble.com/laravel/7.x/ja/testing.html
artisanコマンドで、テストコントローラーの雛形を作成します。
php artisan make:test TaskControllerTest
上記コマンドの実行で、tests/Feature/
以下に、TaskControllerTest.php
が生成されます。
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class TaskControllerTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
public function testExample()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
Laravel で標準で作られるテストケースはフューチャーテストになります。 PHP は、基本的にリクエストを受け取りレスポンスを返すための言語です。 そして、この一連の流れを正しく動作するかを確認するのは、フューチャーテストになります。 Laravel では、フューチャーテストを簡単に行うための機能がいくつも用意されています。 もちろんクラス単位のテストとしてユニットテストも行うことができます。
TaskControllerTest.php
にテストケースを実装していきます。
始めに、「表示されたタスクの件数が正しいこと」というケースを実装します。
testReadyListOnly
というメソッドを追加します。
public function testReadyListOnly()
{
factory(Task::class,5)->state('Ready')->create();
factory(Task::class,1)->state('Doing')->create();
factory(Task::class,1)->state('Done')->create();
factory(Task::class,1)->state('notReady')->create();
$response = $this->get("/");
$response->assertStatus(200);
$response->assertViewHas('tasks');
$tasks = $response->original['tasks'];
$this->assertEquals(5, count($tasks));
}
また、use App\Task;
をuse Tests\TestCase;
の下に追加してください。
メソッド名がtest
から始まるものがテストケースとして実行されます。
factory
メソッドでテストデータをdatabase
に登録します。
ここでは、未着手 5 件、着手 1 件、完了 1 件、中止 1 件となります。
$this->get("/")
で、home にアクセスし、レスポンス(サーバからの応答)を得ます。
その後、以下 3 つを確認しています。
assertStatus(200)
で、レスポンスのステータスコードが 200 であることassertViewHas('tasks')
で、レスポンスの VIEW に配列のtasks
が含まれること$response->original['tasks']
で、レスポンスからtasks
の配列を取得して、assertEquals(5, count($tasks))
にて、5 件であること
すべてTRUE
のときにテストが OK となります。
テスト環境の設定
テスト環境を設定します。
phpunit.xml
の設定
todo
直下の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>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
</whitelist>
</filter>
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<!-- <server name="DB_CONNECTION" value="pgsql"/> -->
<!-- <server name="DB_DATABASE" value="todo"/> -->
<server name="MAIL_DRIVER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
</php>
</phpunit>
DB_CONNECTION
と DB_DATABASE
をコメントアウトします。
.env.testing
の作成
.env
をコピーして、.env.testing
を作成します。
$ cp .env .env.testing
APP_KEY
を設定します。
$ php artisan key:generate --env=testing
変更後に、Laravel のキャッシュをクリアします。
php artisan config:clear
テスト実行
テストを実行します。
$ ./vendor/bin/phpunit
Laravel7の場合は、下記でも実行できます。
$ php artisan test
フューチャーテストのみ実行するなら下記です。 上記のコマンドに、オプションを付与します。
$ ./vendor/bin/phpunit tests/Feature/
Laravel7の場合は、下記でも実行できます。
$ php artisan test tests/Feature/
特定のテストのみ実行するなら下記となります。
$ ./vendor/bin/phpunit tests/Feature/TaskControllerTest.php
Laravel7の場合は、下記でも実行できます。
$ php artisan test tests/Feature/TaskControllerTest.php
特定のテストのみ実行した結果は、下記のようにTests: 2, Assertions: 4, Failures: 1.
になり、testReadyListOnly
がエラーとなっています。
.F 2 / 2 (100%)
Time: 00:00.115, Memory: 32.01 MB
There was 1 failure:
1) Tests\Feature\TaskControllerTest::testReadyListOnly
Failed asserting that 80 matches expected 5.
/var/www/html/todo/tests/Feature/TaskControllerTest.php:39
FAILURES!
Tests: 2, Assertions: 4, Failures: 1.
これは、tasks
テーブルに 5 件を超えた数のタスクがあったためです。
今回、テストデータとして5件追加しましたが、そもそも既にタスクテーブルにデータが存在していたためです。
事前にtasks
テーブルを空にしておく必要がありました。
都度テストを行うたびに、手動でtasks
テーブルをtruncate
するのは面倒かつ忘れがちになります。
こういう場合、Laravel のテストでは、RefreshDatabase
を使用することで、テストが終わるとともに、データベースを初期化できます。
テストのための初期化
RefreshDatabase
を使用すると各テストメソッド終了毎に/database/migrations
以下のMigration
が実行されます。
結果、次回のテストが実行されるときは、すべてのテーブルが空の状態となります。
TaskControllerTest.php
を修正します。
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class TaskControllerTest extends TestCase
{
use RefreshDatabase;
注意点としては、すべてのテーブルが空になるということは、マスタデータも消えてしまうことになります。
マスタデータが必要な場合は、テストクラスにsetup
メソッドを追加し、そのメソッド内にてマスタデータのシーダーを呼ぶます。
そうすることで、テスト実行前にマスタデータがテーブルに登録されます。
<?php
protected function setUp(): void
{
parent::setUp();
$this->artisan('db:seed');
}
今回、作成するものには、マスタデータを使用していないためsetup
メソッドは不要です。
テスト用のデータベース作成
自動テスト終了後はデータベースが初期化状態となると、コーディング中の動作確認をするためのデータがなくなってしまいます。 そこで、今までのデータベースに加えて、自動テスト用のデータベースを作ります。
$ sudo -i -u postgres
$ createdb -O developer todo_test
$ exit
.env.testing
のDB_DATABASE
を修正します。
DB_DATABASE=todo_test
テスト用のデータベース内にテーブルを作成します。
$ php artisan migrate --env=testing
マスタデータなど、テスト実施に必要な共通のデータをがある場合は、
setUp()
メソッド内の$this->artisan('db:seed');
を修正する必要があります。
テスト用のデータベースに対してデータを登録するために下記のように修正します。
\Artisan::call('db:seed', ['--env' => 'testing',]);
今回、作成するものには、マスタデータを使用していないため上記は不要です。
テストの再実行
$ ./vendor/bin/phpunit tests/Feature/TaskControllerTest.php
developer@Ryzen:/var/www/html/todo$ ./vendor/bin/phpunit tests/Feature/TaskControllerTest.php
PHPUnit 9.5.1 by Sebastian Bergmann and contributors.
Warning: Your XML configuration validates against a deprecated schema.
Suggestion: Migrate your XML configuration using "--migrate-configuration"!
.F 2 / 2 (100%)
Time: 00:00.236, Memory: 36.01 MB
There was 1 failure:
1) Tests\Feature\TaskControllerTest::testReadyListOnly
Failed asserting that 8 matches expected 5.
/var/www/html/todo/tests/Feature/TaskControllerTest.php:44
FAILURES!
Tests: 2, Assertions: 4, Failures: 1.
テストが失敗します。 5件表示されるはずが8件表示されています。 未着手だけを表示させるはずが全件表示されてしまっています。
エラーの修正
TaskController.php
の index()
メソッドを修正します。
public function index()
{
$tasks = Task::Where('status',1)->get();
return view('index', compact('tasks'));
}
テストの再実行
developer@Ryzen:/var/www/html/todo$ ./vendor/bin/phpunit tests/Feature/TaskControllerTest.php
PHPUnit 9.5.1 by Sebastian Bergmann and contributors.
Warning: Your XML configuration validates against a deprecated schema.
Suggestion: Migrate your XML configuration using "--migrate-configuration"!
.. 2 / 2 (100%)
Time: 00:00.223, Memory: 36.01 MB
OK (2 tests, 4 assertions)
手間をかける必要がありますが、この一手間で楽になります。
ところで、テスト実行時にWarningが出ていました。設定ファイルに廃止された項目があるということです。これを修正します。
メッセージに従い、./vendor/bin/phpunit --migrate-configuration
を実行します。
developer@Ryzen:/var/www/html/todo$ ./vendor/bin/phpunit --migrate-configuration
PHPUnit 9.5.1 by Sebastian Bergmann and contributors.
Created backup: /var/www/html/todo/phpunit.xml.bak
Migrated configuration: /var/www/html/todo/phpunit.xml
再実行して、Warningが表示されないことを確認します。
developer@Ryzen:/var/www/html/todo$ ./vendor/bin/phpunit tests/Feature/TaskControllerTest.php
PHPUnit 9.5.1 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)
Time: 00:00.224, Memory: 36.01 MB
OK (2 tests, 4 assertions)
Warningも消えてスッキリしました。
残りのテストケースの実装
- (実装済み)表示されたタスクの件数が正しいこと
- ステータスが未着手のタスクのみ表示されていること
- 未着手のデータがないとき
- タスクテーブルにレコードがない場合は空で表示されること
ステータスが未着手のタスクのみ表示されていること
public function testReadyListAll()
{
factory(Task::class,5)->state('Ready')->create();
factory(Task::class,1)->state('Doing')->create();
factory(Task::class,1)->state('Done')->create();
factory(Task::class,1)->state('notReady')->create();
$response = $this->get("/");
$response->assertStatus(200);
$response->assertViewHas('tasks', function ($tasks) {
foreach ($tasks as $task) {
if ($task->status !== 1) {
return false;
}
}
return true;
});
}
未着手のデータがないとき
public function testReadyListNone()
{
factory(Task::class,1)->state('Doing')->create();
factory(Task::class,1)->state('Done')->create();
factory(Task::class,1)->state('notReady')->create();
$response = $this->get("/");
$response->assertStatus(200);
$response->assertViewHas('tasks', null);
}
タスクテーブルにレコードがない場合は空で表示されること
public function testListNone()
{
$response = $this->get("/");
$response->assertStatus(200);
$response->assertViewHas('tasks', null);
}
テストの再実行
developer@Ryzen:/var/www/html/todo$ ./vendor/bin/phpunit tests/Feature/TaskControllerTest.php
PHPUnit 9.5.1 by Sebastian Bergmann and contributors.
..... 5 / 5 (100%)
Time: 00:00.279, Memory: 36.01 MB
OK (5 tests, 10 assertions)
developer@Ryzen:/var/www/html/todo$
すべてのテストが OK なら、リスト表示は完成です。 テスト実行時に、–testdox のオプションを付与すると、メソッド名が表示されます。
developer@Ryzen:/var/www/html/todo$ ./vendor/bin/phpunit tests/Feature/TaskControllerTest.php --testdox
PHPUnit 9.5.1 by Sebastian Bergmann and contributors.
Task Controller (Tests\Feature\TaskController)
✔ Example
✔ Ready list only
✔ Ready list all
✔ Ready list none
✔ List none
Time: 00:00.275, Memory: 36.01 MB
OK (5 tests, 10 assertions)
こっちの方が、達成感が出て気持ちいいですね。
次回への申し送り事項
再度、今回の新規登録画面の仕様を見返して抜け漏れがないかを確認します。
確認の結果、以下が漏れているもしくは先送りされています。
- 新規登録ボタンの設置
- 新規登録ボタンの自動テスト
新規登録画面の実装はこれからですが、ボタンは仮の飛び先を作って実装します。 テストは、フューチャーテストではなくブラウザテストを行う必要があります。
次回にて取り組みます。