第2章 タスク詳細表示

04.エラー処理がちょっと気になる

賢明なみなさんであれば、そろそろ「エラー処理」が気になってくるのではないだろうか。 この点についても、先に進む前に確かめておきたい。

今回は、app/Http/Controllers/TaskController.phpで修正したdetail()の中に、 問題が起きそうな箇所がある。



 




    public function detail(int $id)
    {
        $task = Task::find($id);

        return view('tasks.detail', ['task' => $task]);
    }

この3行目、$idを使ってタスクの検索しているが、「指定したIDが存在しない」ということもありえる。 (URLの一部を$idとして使うので、存在しないIDを手動で指定可能だ。)

存在しないIDが指定された場合のテスト

指定したIDが存在しない場合には、リソースが存在していないので404のHTTPステータスコードを返却すべきだ。 まずは、それを確認するテストコードを追加しよう。

tests/Feature/TaskControllerTest.phpを下記のようにする。



































 
 
 
 
 
 
 
 
 
 
 


<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class TaskControllerTest extends TestCase
{
    /**
     * Get All Tasks Path Test
     *
     * @return void
     */
    public function testGetAllTasksPath()
    {
        $response = $this->get('/tasks');

        $response->assertStatus(200);
    }

    /**
     * Get Task Detail Path Test
     *
     * @return void
     */
    public function testGetTaskPath()
    {
        $response = $this->get('/tasks/1');

        $response->assertStatus(200);
    }

    /**
     * Get Task Detail Path Not Exists Test
     *
     * @return void
     */
    public function testGetTaskPathNotExists()
    {
        $response = $this->get('/tasks/0');

        $response->assertStatus(404);
    }
}

testGetTaskPathNotExists()で、存在しないIDを指定すると404のステータスコードが返ってくる、 ということを確認している。

一度、テストを実行してみよう。

$ vendor/bin/phpunit 
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

..F..                                                               5 / 5 (100%)

Time: 996 ms, Memory: 20.00MB

There was 1 failure:

1) Tests\Feature\TaskControllerTest::testGetTaskPathNotExists
Expected status code 404 but received 500.
Failed asserting that false is true.

/Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:78
/Users/[ユーザ名]/task-manager/task-manager/tests/Feature/TaskControllerTest.php:44

FAILURES!
Tests: 5, Assertions: 9, Failures: 1.

追加したテストが失敗した。 エラーメッセージを読むと、500のステータスコードが返ってきたことがわかる。 このステータスコードは、サーバの内部エラーなので、つまりプロダクトコードでエラーが発生している、というわけだ。

ログを使って調査

発生箇所はこのメッセージだけだとわかりづらい。 念のためにLaravelの出力するログファイルにも目を通しておこう。

storage/logs/laravel.logというファイルに、プロダクトコードでエラーが発生した場合のログが保存されている。 分量は各自の状況で変わるが、ファイル内に下記で始まるメッセージがあるはずだ。

[2018-05-20 13:02:57] testing.ERROR: Trying to get property 'title' of non-object (View: /Users/[ユーザ名]/task-manager/task-manager/resources/views/tasks/detail.blade.php) {"exception":"[object] (ErrorException(code: 0): Trying to get property 'title' of non-object (View: /Users/[ユーザ名]/task-manager/task-manager/resources/views/tasks/detail.blade.php) at /Users/[ユーザ名]/task-manager/task-manager/storage/framework/views/37e261dc716e7ccccdd8961010ed27a327614092.php:31, ErrorException(code: 0): Trying to get property 'title' of non-object at /Users/[ユーザ名]/task-manager/task-manager/storage/framework/views/37e261dc716e7ccccdd8961010ed27a327614092.php:31)
[stacktrace]
#0 /Users/[ユーザ名]/task-manager/task-manager/vendor/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php(45): Illuminate\\View\\Engines\\CompilerEngine->handleViewException(Object(ErrorException), 1)
(略)

これを見ると、ビューのdetail.blade.phptitleを使えなかったよ、というエラーメッセージであることが確認できる。

つまり、「指定したIDが存在しない場合」でもビューの表示に処理が遷移しているが、 しかし実際にはデータが無いので処理できずにエラーが発生した、、、ということがわかる。

ということで、当初考えていたとおり、 指定したIDが存在しない場合に404のHTTPステータスコードを返却するように実装すれば良さそうだ。

テストを使ってフレームワークの動作確認

では、「指定したIDが存在しない場合」はどうやってわかるだろうか。 今度は、モデルのテストを使って、動作確認コードを作ってみよう。

tests/Unit/TaskTest.phpに、下記のようにコードを追加してみよう。


















































 
 
 
 


<?php

namespace Tests\Unit;

use App\Task;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

class TaskTest extends TestCase
{
    /**
     * Get Seeder Tasks Test
     *
     * @return void
     */
    public function testGetSeederTasks()
    {
        // 全件取得
        $tasks = Task::all();
        // 3件取得できるはず
        $this->assertEquals(3, count($tasks));

        // 実行完了していないものを取得
        $taskNotFinished = Task::where('executed', false)->get();
        // 2件取得できるはず
        $this->assertEquals(2, count($taskNotFinished));

        // 実行完了しているものを取得
        $taskFinished = Task::where('executed', true)->get();
        // 1件取得できるはず
        $this->assertEquals(1, count($taskFinished));

        // 「テストタスク」というタイトルのレコードを取得
        $task1 = Task::where('title', "テストタスク")->first();
        // 実行完了していないはず
        $this->assertFalse(boolval($task1->executed));

        // 「終了タスク」というタイトルのレコードを取得
        $task1 = Task::where('title', "終了タスク")->first();
        // 実行完了しているはず
        $this->assertTrue(boolval($task1->executed));
    }

    public function testGetTaskDetail() {
        $tasks = Task::find(2);
        $this->assertEquals('テストタスク', $tasks->title);
    }

    public function testGetTaskDetailNotExists() {
        $tasks = Task::find(0);
        $this->assertNull($tasks);
    }
}

取得できなかったらnullになるのではないか?という発想で、このようなコードとした。 一般的にはnullとなりがちなので、確かめてみる価値はあるだろう。

今回は動作確認が目的なので、tests/Unit/TaskTest.phpだけテストを実行しよう。

$ vendor/bin/phpunit tests/Unit/TaskTest.php
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 450 ms, Memory: 12.00MB

OK (3 tests, 7 assertions)

お、成功している。つまり、find()で取得できなかったらnullになる、 という動作で良いようだ。

コントローラの修正

では、安心してコントローラの修正に取り掛かろう。 app/Http/Controllers/TaskController.phpを、下記のように修正してみる。




















 
 
 





<?php

namespace App\Http\Controllers;

use App\Task;
use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function index()
    {
        $tasks = Task::all();

        return view('tasks.index', ['tasks' => $tasks]);
    }

    public function detail(int $id)
    {
        $task = Task::find($id);
        if ($task === null) {
            abort(404);
        }

        return view('tasks.detail', ['task' => $task]);
    }
}

find()の後にチェック処理としてnullの判定を入れた。 nullだった場合には404のステータスコードをabort()で発生させて、 ここで処理が終わるようになっている。

では、テストを実行してみよう。

$ vendor/bin/phpunit
PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

......                                                              6 / 6 (100%)

Time: 643 ms, Memory: 14.00MB

OK (6 tests, 10 assertions)

問題なくパスするようになった。

エラー処理や例外処理といったものも、気づいた時にテストコードを書いて、 テストをパスするようにプロダクトコードを修正、というリズムで進めることは変わらない。

さらに、今回はフレームワークの仕様(find()で検索に失敗した場合の返り値)もテストで確認した。 このように「知識の面での動作確認」にもテストを使うことができるわけだ。

少し寄り道が長くなったように感じるかもしれないが、必要なことを確かめるためだ。 気にせず、次に進んでいこう。

次が本章の最後、ページ遷移の実装だ。

Last Updated (JST): 7/7/2019, 2:32:08 PM