前のパートに戻る 完了して次のパートへ  

  1-4 テスト駆動でフィーチャーテストを書いてみる

テスト駆動でフィーチャーテストを書いてみる

フィーチャーテストでは、複数のクラスが連動するような処理の実行後の状態を確認します。ウェブサービスでは、一般的に、Controller を対象とし、HTTP リクエストを入力値、レスポンスを出力値として検査します。


要件の確認とテストケースの洗い出し

  • レッスンの詳細ページを開くと、レッスン名と空き状況マークが表示されていること


レッドフェーズ

ユニットテストのときと同様、クラス名やインターフェイスを決めます。

クラス名は LessonController、URLは、

GET /lessons/{id}

としておきましょう。

決まったらテストクラスを作ります。

# php artisan make:test Http/Controllers/LessonControllerTest

tests/Feature/Http/Controllers/LessonControllerTest.php を開き、最小限のテストを書きます。

1行ずつ解説します。

まず最初に、テストしたい URL へアクセスするために TestCase::get() を呼びます。POST リクエストの場合は TestCase::post() を使用してください。

実際に HTTP 通信をするわけではなく、内部で Controller を生成し、プロダクションなら Illuminate\Http\Response オブジェクトを返す代わりにそのラッパークラスである \Illuminate\Testing\TestResponse クラスのインスタンスを返します。

次に、レスポンスコードをチェックするために、 TestResponse::assertStatus() を使います。

定数を使っていますが、 200 などの数値を指定してもいいでしょう(これらのコードはよほどのことがない限り変更されることはないので)。

続いて、返却されたレスポンス(HTMLドキュメントデータ)に含まれていなければならないデータが含まれているかどうかをチェックするために TestResponse::assertSee() を使います。

assertSee() は HTML タグも含めることができ、純粋にテキストだけをチェックするのであれば assertSeeText() を使うこともできます。

テストを実行します。

# php artisan test tests/Feature/Http/Controllers/LessonControllerTest.php 

   FAIL  Feature\Http\Controllers\LessonControllerTest
  ✕ show

  Tests:  1 failed

  Expected status code 200 but received 404. Failed asserting that false is true.

  at tests/Feature/Http/Controllers/LessonControllerTest.php:21
    17|     public function testShow()
    18|     {
    19|         $response = $this->get('/lessons/1');
    20| 
  > 21|         $response->assertStatus(Response::HTTP_OK);
    22|         $response->assertSee('楽しいヨガレッスン');
    23|         $response->assertSee('×');
    24|     }
    25| }

正しく失敗しました。

グリーンフェーズ

レッスン情報はデータベースにある想定なので、まずはモデルとテーブル、それとモデルファクトリを作ります。

モデル生成時に -a オプションを渡すと、マイグレーション、モデルファクトリ、シーダ、およびコントローラーを一緒に生成します(それぞれ -m-f-s-c を個別に指定することもできます)。

# php artisan make:model Models/Lesson -a

テーブル構造は 0-6 設計にあるテーブル定義を見ながら up メソッドを完成させます。

app/database/migrations/xxxxxx_create_lessons_table.php のupメソッドを以下のように編集してください(xxxxxxにはマイグレーションファイルを作成した日時が入ります)。

モデルファクトリを定義する際、適当な値を適当にセットしてよければ Faker を使います。

Faker は Laravel の機能ではなく、独立したパッケージです。

https://github.com/fzaninotto/Faker

文字列、数値、日付、時刻、などのプリミティブ型データをランダムに生成したり、配列からランダムに要素を取得したり、テストデータのようにデータの中身は特になんでも構わない、というようなシーンで役立ちます。

どんなプロバイダ(データの種類に応じてランダムデータを提供する仕組み)があるか興味ある方は、上記の GitHub リポジトリのドキュメントやソースコードを読んでみてください。

日本語の文字列がほしければ config/app.php の faker_locale を ja_JP に変更すると(すべてではないですが)日本語のランダム文字列が生成されるようになります。

app/database/factories/LessonFactory.php を以下のように編集してください。

これで準備が整ったので、テストコードを整備しつつ、プロダクションコードを書いていきます。

まずはテストコードを、データベースを使うように変更していきます。

app/tests/Feature/Http/Controllers/LessonControllerTest.php を以下のように編集してください。

RefreshDatabase トレイトは、「Laravel アプリケーションにおけるテストの基本的な考え方」でも紹介しましたが、テスト実行時にデータベースを再構築してくれます。

以下の行は、同じく「Laravel アプリケーションにおけるテストの基本的な考え方」で紹介した、モデルファクトリを使ってデータベースにテストで使用するための「適当な」データを投入する処理です。以下の処理が実行されると lessons テーブルにレコードが追加されます。

モデルファクトリについては、「3章:フィーチャーテストの作り方を学ぶ」でもう少し詳しく解説します。

続いてプロダクションコードを書きます。

まずはルーティングを定義します。

app/routes/web.php に以下のコードを追加してください。

次に Controller。空き状況はとりあえず残り枠数 0 で初期化し、そのまま View に渡すようにしておきます(最初のグリーンフェーズは、いちばん楽な方法で実装しましょう。テンポを大切に)。

app/Http/Controllers/LessonCotroller.php を以下のように編集してください。

続いて View。resources/views/lesson/show.blade.php を作ります。ひとまずマークアップは適当でいいです。

テストを実行します。

# php artisan test tests/Feature/Http/Controllers/LessonControllerTest.php 

   PASS  Feature\Http\Controllers\LessonControllerTest
  ✓ show

  Tests:  1 passed
  Time:   4.01s

リファクタリングフェーズ

空き状況レベルを、レッスンオブジェクトから取れるようにリファクタリングしていきます。将来的な完成形のイメージとしては、レッスンが予約可能最大数と現在の予約数を保持しており、その差が空き状況レベルの初期値となる感じです。

resources/views/lesson/show.blade.php にある $vacancyLevel を $lesson->vacancyLevel に変更します。

app/Http/Controllers/LessonCotroller.php から VacancyLevel を取り除きます。

app/Models/Lesson.php にゲッターを定義します。 remainingCount() はひとまず 0 を返す状態で定義しておきます。

View で <span>空き状況: {{ $lesson->vacancyLevel->mark() }}</span> としていたところを <span>空き状況: {{ $lesson->vacancyLevel }}</span> と書けるように、 app/Models/VacancyLevel.php に __toString() メソッドを追加します。

明示的に mark() を呼ぶか、暗黙的に文字列変換させるかは好みの問題だと思うので、お好きなほうを選んでいただければと思います。

では最後にテストが通るか確かめましょう。

# php artisan test tests/Feature/Http/Controllers/LessonControllerTest.php 

   PASS  Tests\Feature\Http\Controllers\LessonControllerTest
  ✓ show

  Tests:  1 passed
  Time:   3.68s


課題

remainingCount() メソッドの中身を実装してください。

それに伴い、

  1. App\Models\Reservation モデルを追加
  2. Reservation の HasMany を返す reservations() メソッドを追加
  3. モデルファクトリを使って reservations を追加するように testShow() を変更(テストデータはデータプロバイダで渡すようにする)

してください。

これもテスト駆動で、3, 1, 2 の順番でやってみてもいいでしょう。

3 に関してはヒントを書きます。完成形は下記のような感じになります。$capacity$reservationCount, および $user の3つの変数をテストが通るように定義してください。 $capacity >= $reservationCount とする必要がありますので、そこだけご注意を。

同じユーザーが同一のレッスンに予約を入れることができないので、ループ内で毎回 User を生成します。

$capacity と $reservationCount をデータプロバイダから提供すると、 $expectedVacancyLevelMark も、以下のようにこれらの値に応じて変化するようにしなければなりません。

次節にこの課題の解答例を載せます。

議論

0 質問

このコースの評価は?