読者です 読者をやめる 読者になる 読者になる

tanaka's Programming Memo

プログラミングについてのメモ。

Laravel5.2でのテストのメモ

PHP Laravel

Laravel5.3のものをこちらに書きました → Laravel5.3でのテストのメモ - tanaka's Programming Memo

    • -


自分向けのメモです。

LaravelにはPHPUnitによるテストの設定が組み込まれています。公式マニュアルで概要を確認して、データベースのチェックや、URLごとのテストについて調べました。

現時点で、Laravelは5.3がリリースされていますが、Sentinelがまだ対応していないので、5.2の方を調べました。

実行時の注意点

以下のようなエラーが表示された場合の対処方法です。

PHP Fatal error:  Call to undefined method PHPUnit_Framework_TestResult::warnings() in /Users/yutanaka/.composer/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php on line 297

Laravelに組み込まれているphpunitはやや古い可能性があります。グローバルのphpunitとバージョンが異なる場合に、上記のようなエラーが表示される場合があります。その際は、Laravelに組み込まれているphpunitを呼び出すように package.json に以下のようなスクリプト呼び出しを追加して、 npm test でテストを開始すると良いでしょう。

  "scripts": {
    // :
    "test": "vendor/phpunit/phpunit/phpunit"
  },

公式マニュアル概要

Testing - Laravel - The PHP Framework For Web Artisans を参照して、概要を確認します。

Introduction

  • Laravelアプリフォルダー直下のphpunit.xmlと、testsフォルダー内にサンプルが設定されている
  • アプリフォルダー内で phpunit で実行できる
テスト環境
  • テストを開始すると、Laravelが自動的に環境設定をtestingに設定する
  • Laravelは、テスト中はarrayドライバーでセッションやキャッシュを設定するので、テスト中のセッションやキャッシュは後に残ることがない
  • テスト環境は、phpunit.xmlファイルのtesting環境変数で変更して構わない。変更した後は、artisanコマンドのconfig:clearを実行して、設定のキャッシュをクリアすること
新しいテストの定義と実行
  • php artisan make:test UserTest などで、新しいテストを定義できる
  • testsフォルダー内に、新しいUserTestファイルが作成されるので、phpunitのコマンドを書いてテスト内容を記述する
  • 実行は、phpunitを呼び出す
  • setUpメソッドをオーバーライドする時は、親のsetUpを呼び出すこと

アプリケーションのテスト

  • Laravelは、HTTPリクエストを生成したり、出力をテストしたり、フォームを埋めたりするのが楽になるAPIを提供する
  • visit()メソッドは、GETリクエストをアプリケーションに発行する
  • see()メソッドは、アプリケーションの戻り値から指定した文字列が含まれるかをアサートする
  • dontSee()メソッドは、指定の文字列が戻り値に含まれないことをアサートする
アプリケーションとの連携
  • リンクをクリックする
    • visit()で画面を表示
    • click('<クリックしたい文字列>')
    • seePageIs('遷移先のURL')
  • フォームの操作
    • type(<入力内容>, <フォームのname>)で、指定のnameの入力フォームに指定のテキストを入力する
    • select(<選択内容>, <フォームのname>)で、ラジオボタンやドロップダウンの選択
    • check(<フォームのname>)で、指定のチェックボックスにチェック
    • uncheck(<フォームのname>)で、指定のチェックボックスのチェックを外す
    • attach(<ファイルのパス>, <フォームのname>)で、指定のファイルをアップロード対象にする
    • press(<ボタンやテキストや要素のname>)で、該当するものを押す
JSONテスト
  • get, post, put, patch, deleteメソッドで、指定のURLに、指定のパラメータを渡した呼び出しができる
  • seeJson()で、指定のデータが戻り値に含まれるかをチェックする。「含むか」なので、完全一致じゃなくてもテストは成功する
  • JSONの完全な一致をチェックしたい場合は、seeJsonEquals()メソッドを利用する
  • 戻り値の内容ではなく、構造をチェックしたい場合は、seeJsonStructure()メソッドを使う。指定していないキーがあっても、指定した構造が含まれていればテストは成功する
  • 何らかのキーに、指定の構造が含まれるかをチェックする場合は、 * を使う。ネストも可能
SessionとAuthentication
  • withSession()メソッドで、セッションを設定できる。ページを訪れる前に、テストしたい値をセッションに設定しておくことができる
  • actingAs()で、カレントユーザーを設定できる。事前に、ModelFactoryで、新規のユーザーモデルを作成して、それを引数に渡してユーザーを作成できる
  • actingAs()の第2引数を指定すると、毎回認証が必要な保護認証に対して、保護名を設定できる
ミドルウェアの無効化
  • WithoutMiddlewareトレイトを使うと、ミドルウェアを無効化して、テストを簡易にできる
    • クラス内に、use WithoutMiddleware;を宣言すると、そのテスト全てでミドルウェアが無効になる
    • 特定のテストのみで無効にしたい場合は、テストメソッドの中で、$this-.withoutMiddleware();を呼び出す
カスタムHTTPリクエスト
  • カスタムリクエストを作成して、戻り値のIlluminate\Http\Responseオブジェクトを取得したい場合は、call()メソッドを使う
  • POST, PUT, PATCHリクエストに必要な入力データは、配列で渡す
PHPUnitアサーション

以下、Laravelが提供するPHPUnitテスト用のメソッド。

  • assertResponseOk();
    • クライアントのレスポンスコードがOkかを判定
  • assertResponseStatus($code);
  • assertViewHas($key, $value = null);
    • 戻り値のビューに対して、$keyに$valueが設定されているかを判定
  • assertViewHasAll(array);
    • 戻り値が、指定の配列を持つかを判定
  • assertViewMissing($key);
    • 戻り値のビューに指定のkeyが含まれないことを判定
  • assertRedirectedTo($uri, $with = []);
    • 指定のURIにリダイレクトしたかを判定
  • assertRedirectedToRoute($name, $parameters = [], $with = []);
    • 指定のルートにリダイレクトされたかを判定
  • assertRedirectedToAction($name, $parameters = [], $with = []);
    • 指定のアクションにリダイレクトされたかを判定
  • assertSessionHas($key, $value = null);
    • セッションが指定のkeyとvalueを持つかを判定
  • assertSessionHasAll(array $bindings);
    • セッションが指定の配列の値を持つかを判定
  • assertSessionHasErrors($bindings = [], $format = null);
    • セッションが指定のエラーになっていないかを判定
  • assertHasOldInput();
    • セッションがold inputを持っていないかを判定
  • ssertSessionMissing($key);
    • セッションが指定のkeyを持っていないことを判定

Databaseのテスト

Laravelは、データベース駆動のアプリケーション向けのテスト環境も提供しています。

  • seeInDatabase()メソッドで、指定のデータが、データベースの指定のテーブルに含まれるかを判定
テスト後のデータベースのリセット

各テスト後に、テスト時のデータベースへの変更をもとに戻す方法です。

  • Migrationを使う
    • 次のテストの前に、DatabaseMigrationsトレイトを使って、Migrationする
    • テストクラス内で、 use DatabaseMigrations; を定義すれば、テストごとに自動的にマイグレーションを実施する
  • トランザクションを使う
    • 全てのテストを、データベースのトランザクションで囲む方法もある
    • DatabaseTransactionsトレイトを使えば、Migrationsと同様にテストごとに自動的にこれを行う
    • このトレイトは、デフォルトのデータベース接続にのみ対応
モデルファクトリー

複数のテストにまたがって、共通の幾つかのレコードをデータベースに登録したい場合、手動で特定の値を列ごとに設定するのではなく、Eloquentのモデルファクトリーを利用することができます。

  • database/factories/ModelFactory.php ファイルに、データの定義例がある
  • $factory->define()メソッドに、デフォルトデータを戻すクロージャーを渡す
  • クロージャーは、Faker PHP ライブラリのインスタンスを返し、テストのための乱数を設定できる
  • ModelFactory.phpファイルには、自由にファクトリーを追加できる
  • UserFactory.phpやCommentFactory.phpなど、database/factoriesディレクトリーに加えることができる

同じEloquentモデルクラスに対して、複数のファクトリーを利用するには、ベースとなるユーザーファクトリーを複製して、raw()メソッドでベースファクトリーを変更する方法があります。$factory->defineAs()メソッドでファクトリーを定義して、クロージャー内で$factoryを受け取って、$factory->row()でモデルクラスを受け取って、array_merge()で配列を結合します。

  • ファクトリーのテストでの使い方
    • ファクトリーを定義したら、テスト関数でfactory(モデルクラス)->make();とすれば、ファクトリーから生成したモデルのインスタンスを得られる
    • ファクトリーで作成したデフォルト値を書き換えたい場合は、make()メソッドに連想配列で変更要素を渡す
    • factory()の第2引数に数を渡すと、指定の数のインスタンスを生成する
    • 同じく、ファクトリー名を渡すと、該当するファクトリーを生成する
  • ファクトリーモデルを持続させる
    • create()メソッドは、モデルのインスタンスを作成した上で、データベースに値を保存する
    • create()メソッドに連想配列を渡すと、属性を上書きできる
  • リレーションの追加
    • create()してモデルを作成後、each()メソッドを呼び出して、posts()->save(factory(モデル暮らす)->make()); で設定できる
  • ファクトリーを定義するファクトリーに渡すクロージャーでモデルのリレーションを設定することもできる
    • Postを作成する時に、新しいユーザーを作成して、idをリレーションさせることができる
    • 生成したユーザーのIDを、その後の属性で利用することも可能

モック

イベントのモック
  • Laravelで大量にイベントシステムを構築していた場合、テスト中にイベントをモック化できる
  • ユーザー登録をした時に、登録が完了したイベント(例えばUserRegistered)が発行すると、登録完了のメールが送信されてりする。それをキャンセルするとテストが楽である
  • $this->expectsEvents(App\Events\UserRegistered::class); とすると、UserRegisteredイベントをキャンセルできる
  • doesntExpectEvents()メソッドを使うと、指定のイベントが発動しなかったことを確認できる
  • withoutEvents()メソッドを呼び出すと、すべてのイベントハンドラを抑制できる
ジョブをモック

アプリケーションがリクエストを作成した時に、作成したコントローラーが特定のジョブを呼び出すかを試す簡単なテストをしたい場合があるでしょう。そのような時の方法です。これにより、ルートとコントローラーのテストを分離することができます。

  • $this->expectsJobs(App\Jobs\PurchasePodcast::class);などとすることで、指定のジョブが呼び出されたことを確認する。行うのは確認のみで、ジョブ自体を実行することはしない
  • このメソッドは、DispatchesJobsトレイトか、dispatch()メソッドによって発行されたジョブにのみ反応する。Queue::push()で直に送信されたジョブは対象外
Facadeのモック

Facade(ファサード)は、Laravelのサービスを静的に呼び出せるようにしたもの。これをモックにします。例えば、Cacheファサードをモックに入れ替えてテストができます。

  • shouldReceive()メソッドを呼び出すと、Mockeryクラスのモックのインスタンスが返される
  • Laravelのservice containerにより管理されるので、そのままクラスを利用するよりもテストしやすい
  • Requestファサードをモックにすると、テストを実行する時にcall()やpost()メソッドのようなHTTPヘルパーメソッドもキャンセルされてしまうので、モックにするべきではない