tanaka's Programming Memo

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

Laravel5.3でSentinelを利用する(1)LaravelとSentinel、Codeceptionのインストール

次へ

完成プロジェクト→ GitHub - am1tanaka/lara5.3-sentinel at ver1.1.1


はじめに

PHPフレームワークLaravelには簡単な認証や認可ができるauthパッケージが組み込まれていますが、ユーザー登録時にメールの確認などなしにいきなり登録できてしまいます。

Sentinelは上記のような機能を持つPHPフレームワークで、Laravel5に組み込むための機能を提供してくれています。Sentinelが提供するのはロジックだけで、ビューやユーザー確認用のメールなどは自前で実装する必要がありますが、それでも手間は相当減らせます。

上記を組み合わせた環境をmac上で構築する作業メモです。完成プロジェクトはこちら→GitHub - am1tanaka/lara5.3-sentinel: Laravel5.3+Sentinelのユーザー認証サンプル


前提環境

  • Laravel5.3
  • Sentinel2.0.13以降
  • macOS Sierra 10.12.1
  • PHP5.6.19
  • MySQL5.7.16
  • composer version 1.2.1
  • Postfixを設定して、macからgmailなどを通してメール送信できる環境が構築できている
  • 記事内には登場しないが、MySQLの操作を楽にする Sequel Pro のインストールをオススメ



実装の方針

  • テスト用のフレームワーク Codeception を利用する
  • Sentinelの first_name と last_name は、 name に統一する
  • メールでの通知を Notification で実装する
  • Notification はキューに積む
    • キュードライバーは sync を使用
    • 必要になったら他のドライバーの環境を構築する方針とする
  • メール送信は、5.3の新しいMail機能を使う
    • メールドライバーは smtp を利用
    • SMTPサーバーが利用できない場合は、 Mailgun や SparkPost といったメール送信サービス用のドライバーを利用すると良い



Laravelのバージョン確認とインストール

  • macでターミナルを起動
  • プロジェクトフォルダーを作るフォルダーに移動
  • laravel -v を実行して、コマンドが見つからなかったら以下でグローバル環境にLaravelをインストールする
composer global require "laravel/installer"
  • Laravelをインストール済みだった場合、以下でバージョンを最新版にしておく
composer global update
  • 以下で、Laravelのプロジェクトを作成して、中に入る
laravel new lara5.3-sentinel
cd lara5.3-sentinel



データベースを作成する

今回のサンプルで操作するためのデータベースを作成します。

  • MySQL にログインする
mysql -u ユーザー名 -p
  • 上記でMySQLにログインできない場合は、MySQLサーバーを起動
sudo mysqld_safe
  • データベースを作成する。以下は、lara_sentinelというデータベースと、テスト用のlara_sentinel_testを作成する例
create database lara_sentinel;
create database lara_sentinel_test;
  • 操作用のユーザーを作成して、権限を与えておく。以下は、user_lara_sentというローカルホスト上のユーザーを作成する例。パスワードは任意
CREATE USER 'user_lara_sent'@'localhost' IDENTIFIED BY 'YourPassword';
  • 作成したローカルホストのuser_lara_sentユーザーに、lara_sentinelデータベースへのアクセス権限を与える
GRANT ALL PRIVILEGES ON lara_sentinel.* TO 'user_lara_sent'@'localhost';
GRANT ALL PRIVILEGES ON lara_sentinel_test.* TO 'user_lara_sent'@'localhost';
exit



Laravelの環境設定

  • Atomなどのエディターで、上記で作成したlara5.3-sentinelのプロジェクトフォルダーを開く
  • .envを開いて、各種設定を行う
    • APP_URLを、 http://localhost:8000 に変更
    • DB_DATABASEを、作成したデータベース名に変更。ここでは lara_sentinel
    • DB_USERNAMEを、作成したユーザー名に変更。ここでは、 user_lara_sent
    • DB_PASSWORDを、設定したパスワードに変更。任意に設定したパスワードを書く
    • メールについて設定する。macPostfixで送信できる場合は、以下のように設定。MAIL_FROM_ADDRESSとMAIL_FROM_NAMEは適した内容に変更のこと
MAIL_DRIVER=smtp
MAIL_HOST=localhost
MAIL_PORT=25
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=hello@example.com
MAIL_FROM_NAME=ExampleFrom
  • config/mail.php を開く
    • 以下のように書き換え
    'from' => [
        'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
        'name' => env('MAIL_FROM_NAME', 'Example'),
    ],



起動テスト

Laravelの起動を試します。

  • ターミナルで、プロジェクトフォルダーから以下を実行
php artisan serve

Webブラウザーから http://localhost:8000 にアクセスして「Laravel」と表示されたら成功です。


Sentinelをインストール

Sentinel の公式ページのインストール手順に従います。

  • Laravelプロジェクトを起動し続ける場合、新しいターミナルを開いて、プロジェクトフォルダーを開く
  • プロジェクトフォルダー内から以下を実行してインストールする
composer require cartalyst/sentinel
  • インストールが完了したら、 config/app.php を開く
  • providers の配列を探して、その中に以下の行を加える
        /*
         * Sentinel Service Providers...
         */
        Cartalyst\Sentinel\Laravel\SentinelServiceProvider::class,
  • その下の aliases の配列に、以下を加える
        // Sentinel Aliases
        'Activation' => Cartalyst\Sentinel\Laravel\Facades\Activation::class,
        'Reminder'   => Cartalyst\Sentinel\Laravel\Facades\Reminder::class,
        'Sentinel'   => Cartalyst\Sentinel\Laravel\Facades\Sentinel::class,
  • 上書き保存する
  • ターミナルから以下を実行して、resourcesフォルダーにvendor関連のリソースを出力する
php artisan vendor:publish

テーブルを作成するための設定ファイルが出力されたので、 users テーブルの first_name と last_name を削除して、nameにするための変更を加えます。

  • database/migrations/2014_07_02_230147_migration_cartalyst_sentinel.php を開く
  • [Schema::create('users', ]から始まるコードを探して、以下のように修正する
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('email');
            $table->string('password');
            $table->text('permissions')->nullable();
            $table->timestamp('last_login')->nullable();
            $table->string('name')->nullable();  // この辺りを修正
            $table->timestamps();

            $table->engine = 'InnoDB';
            $table->unique('email');
        });

既存のユーザーテーブルは不要なので、マイグレーションで作成されないように以下のファイルを削除します。

  • database/migrations/2014_10_12_000000_create_users_table.php
  • database/migrations/2014_10_12_100000_create_password_resets_table.php

データベースを作成します。

  • 以下をターミナルで実行
php artisan migrate
  • ここでエラーが発生したら、.envを確認してデータベースの接続情報が合っているかや、MySQLサーバーが起動しているかを確認する
  • config/cartalyst.sentinel.php をエディターで開いて、 'users' の設定を以下のように修正して、ここで作成したユーザーモデルに接続する
    'users' => [

        'model' => 'App\User',

    ],

以上でSentinelの設置は完了です。 config/cartalyst.sentinel.php にSentinelの設定があるので、必要があれば変更します。

Webブラウザーhttp://localhost:8000 を再読み込みして、エラーが発生しなければここまで成功です。


認証用のビューを作る

手間を減らすために、認証関連のビューやコントローラーを既存のauth機能から取り込みます。すべて手製で作るのであればこの手順は不要です。

  • ターミナルからプロジェクトフォルダー内で以下を実行
php artisan make:auth

http://localhost:8000 を再読み込みすると、画面の右上に認証のための[LOGIN]と[REGISTER]のリンクが追加されます。現時点では Laravel の auth 機能が実装されているので、正しく動作しません。これをSentinelに差し替えていきます。


Codeception を組み込む

テスト用フレームワークのCodeceptionを組み込みます。公式サイトはこちら
Laravel5.3 テストでテスト専用のデータベースを使う - tanaka's Programming Memo に簡単な使い方をメモしています。

  • ターミナルで以下を実行
composer require codeception/codeception --dev
composer exec codecept bootstrap
composer exec codecept run

以上で、Codeceptionの組み込み、設定ファイルの展開、テスト実行が完了します。現時点ではテスト内容がないので、「No tests executed!」と表示されれば成功です。

Codeception用の設定

Codeceptionを実行するときには、テスト用のデータベースに接続したいので、以下の通りで設定を作成します。

  • ターミナルで以下を実行して、 .env.testing という設定ファイルを作成
cp .env .env.testing
  • エディターで .gitignore を開いて、以下を追加して、テスト設定をgitから外す
.env.testing
  • .env.testing をエディターで開いて、 DB_DATABASE の項目を以下のようにテスト用に変更
DB_DATABASE=lara_sentinel_test
  • ユーザーやパスワードを変更していたら、それらも修正する

次に、Laravel5でのテスト用の設定を行います。とりあえず、Functionalテストを実施する設定です。

  • tests/functional.suite.yml をエディターで開く
class_name: FunctionalTester
modules:
    enabled:
        - Laravel5:
            environment_file: .env.testing
            run_database_migrations: true
        - \Helper\Functional

以上で、 Codeception のテストが Laravel5 に対応して、テストごとにデータベースが作り直されます(migration)。

テストを準備

開発はテストコードを書きながら行います。まずはルートを呼び出すと[Laravel]と表示されるかのテストです。

  • ターミナルで以下を実行して、テスト用のCestファイルを作成
composer exec codecept g:cest functional Open
  • tests/functional/OpenCest.php ファイルが出来上がっているので、エディターで開いて、 tryToTest メソッドを以下のように書き加える
    public function tryToTest(FunctionalTester $I)
    {
        $I->amOnPage('/');
        $I->see('Laravel');
    }
  • テストを簡単に実行するために、 package.json を設定する。package.jsonを開いて、"scripts"の設定を以下のようにする
  "scripts": {
    "prod": "gulp --production",
    "dev": "gulp watch",
    "test": "composer exec codecept run"
  },
  • 以下でテストを実行できる
npm test

以上で、Codeceptionの組み込みは完了です。Codeceptionでのテストは、テスト用のデータベース上で実施され、毎回マイグレーションし直すので、まっさらな状態から開始します。

エラーが発生する場合は、データベースの設定が間違えている可能性があるので、 .env.testing のデータベース関連の設定を見直してください。



次へ

Laravel5.3 で auth を作成した時に変更される内容

Laravel5.3で php artisan make:auth を実行すると、どこが変わるかを確認しました。

routes/web.php

以下のルート設定が routes/web.php ファイルに追加されます。

Auth::routes();

Route::get('/home', 'HomeController@index');

追加されるファイル

以下のファイルがプロジェクトに追加されます。

  • app/Http/Controllers/HomeController.php
  • resources/views/auth/login.blade.php
  • resources/views/auth/passwords/email.blade.php
  • resources/views/auth/passwords/reset.blade.php
  • resources/views/auth/register.blade.php
  • resources/views/home.blade.php
  • resources/layouts/app.blade.php

まとめ

artisan の make:auth で追加されるのは、/home ページのためのルートとコントローラーと、認証関連のビューでした。ミドルウェアなどは、 auth を実行しなくても、あらかじめ Laravel5.3 をインストールした段階で、 vendorフォルダー下に作成されていました。

既存のものを書き換えるのは依存関係を追うのが大変なので、 Sentinel を組み込む場合は、新規にミドルウェアを作成した方が良さそうです。うまくいったら、追記します。

Laravel5.3 テストでテスト専用のデータベースを使う

(submitFormでは、フォームを指定すべきなのがわかりづらい例になっていたので修正 2016/11/1)

Laravelアプリの開発時に、テストする時とそうでない時で、別のデータベースを使うようにする方法です。これをやっておけば、テスト時に好き勝手にデータベースをいじれるので便利です。

PHP 用のテストフレームワークである Codeception を導入すると、テスト時に使用する環境設定ファイルを指定できるので、そこでデータベースの情報を変更できます。 Codeception は Laravel を始めとする様々なフレームワークに対応していて導入しやすく、テスト用の機能も充実しているので導入して損はありません。

Laravel5.3でプロジェクトを作成して、 Codeception を組み込んで、データベースのテストを行うまでの簡単な流れです。

前提

  • macOS Sierra 10.12
  • PHP 5.6.19
  • MySQL 5.7.16
  • Laravel 5.3
  • Componser 1.2.1

以上はインストール済みとします。


Laravelプロジェクトと Codeception の設定

  • ターミナルを起動して、プロジェクトを作成するフォルダーを開く
  • 以下で、プロジェクトを作成。プロジェクト名は適宜変更のこと
laravel new lara53-codeception
cd lara53-codeception
  • 続けて、 Codeception を組み込む
composer require codeception/codeception --dev

Codeceptionのセットアップ

以下を実行することで、Codeception のセットアップができます。

composer exec codecept bootstrap

これにより、 tests ディレクトリーと設定ファイル[codeception.yml]が作成されます。加えて、Acceptanceテスト(実際のWebブラウザーを使ったテスト), Functionalテスト(実際のWebブラウザーは使わない機能テスト), Unitテスト(単体テスト) の3つのスイート(suites)用の設定ファイルも作成されます。

この時点で、以下でテストが動きます。

composer exec codecept run

まだテスト内容がないので、「No tests executed!」と表示されます。


データベースを準備

MySQLを起動していなかったら、新しいターミナルを開いて、以下で起動します。

sudo mysqld_safe

Laravelプロジェクトのフォルダーにいるターミナルで、以下でテスト用のデータベースを2つ作成します。

  • 以下で、MySQLに接続
mysql -u root -p
  • パスワードを入力
  • 以下で、テスト用と、通常用のデータベースの2つを作成
create database lara53_codeception;
create database lara53_codeception_test;
  • それぞれのアクセス用のユーザーを作成
create user
    'lara53_code'@'localhost' identified by 'YourPassword1',
    'lara53_code_test'@'localhost' identified by 'YourPassword2';
  • それぞれのデータベースにアクセス権を設定
GRANT ALL ON lara53_codeception.* TO 'lara53_code'@'localhost';
GRANT ALL ON lara53_codeception_test.* TO 'lara53_code_test'@'localhost';

設定を作成

テスト用の環境ファイルを用意するために、以下で .env を複製します。

cp .env .env.testing

.env.testing のデータベース設定をテスト専用のものにしておけば、テスト時はテスト専用に作ったデータベース上でテストができるので、テストと他の作業のデータを切り離すことができます。

  • .gitignore を開いて、 .env.testing を追加する
  • .env をエディターで開いて、通常の利用データベースへの接続を設定する。以下、このページの例に沿った内容。適宜、自分の環境の値に変更のこと
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=lara53_codeception
DB_USERNAME=lara53_code
DB_PASSWORD=YourPassword1
  • 上書き保存する
  • .env.testing をエディターで開いて、通常の利用データベースへの接続を設定する。以下、このページの例に沿った内容。適宜、自分の環境の値に変更のこと
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=lara53_codeception_test
DB_USERNAME=lara53_code_test
DB_PASSWORD=YourPassword2
  • APP_ENV を testing にしておく
  • 上書き保存

データベースを作成

簡単に動作を試すために、ログイン機能を実装して、それでテストをしてみましょう。以下で、ユーザー登録できるようにします。

php artisan make:auth

http://localhost:8000/ をリロードしたり、開くと、右上に LOGIN と REGISTER のリンクが表示されます。続いて、以下でデータベースを作成します。

php artisan migrate

以上でできるのは、 .env で定義されたデータベースに対してです。以下で、登録状況を表示してみましょう。

mysql -u root -p
<rootのパスワードを入力>
use lara53_codeception;
show tables;

以上で、[migrations][password_resets][users]の3つのテーブルが作成されていることが確認できます。念のため、テスト用のデータベースにはまだテーブルが登録されていないことを確認しましょう。

use lara53_codeception_test;
show tables;

こちらはまだ作成していないので、「Empty set」と表示されます。 exit で mysql から抜けます。


通常のユーザーを登録しておく

  • http://localhost:8000 を開いたら、[REGISTER]を押す
  • 項目を入力して、ユーザーを登録する。メールアドレスは仮のもので良い

テスト用の設定を作成

Codeception で Laravel5 のテストが動くように設定をします。Codeception には以下のようなテスト方法があります。

  • Webブラウザーの動作を模して機能のテストを行う Functionalテスト
  • Seleniumを使って実際にWebブラウザー上で機能のテストを行う Acceptance テスト
  • PHPの関数ベースで単体テストを行う Unit テスト
  • ステータスレスのWebAPIのテストを行う API テスト
  • などなど

ここでは、 Functional テストで、テスト用のデータベースへのユーザー登録を試します。

Functionalテストの設定

Laravel5 - Codeception - Documentation を参考に設定を作成します。

  • tests/functional.suite.yml をエディターで開く
  • 以下のように設定する
class_name: FunctionalTester
modules:
    enabled:
        - Laravel5:
            environment_file: .env.testing
            run_database_migrations: true
        - \Helper\Functional
  • 上書き保存する

テストを作成する

04-FunctionalTests - Codeception - Documentation を参考にテストを作成します。

  • ターミナルから、以下を実行
composer exec codecept g:cest functional User
  • tests/functional/UserCest.php ができるので、エディターで開く(Cestとは、 Codeception のテスト用のファイルのこと)
  • tryToTest()メソッドに、以下のコードを追加
    public function tryToTest(FunctionalTester $I)
    {
        // ユーザー数が0であることを確認する
        \PHPUnit_Framework_Assert::assertEquals(0, App\User::all()->count());

        // ユーザー登録ページを表示
        $I->amOnPage('/register');
        // 必要な情報を設定して、ユーザー登録フォームのsubmitを押す(2016/11/1に変更)
        $I->submitForm('#registerUserForm', [
            'name' => 'TestUser',
            'email' => 'test@test.com',
            'password' => 'testtest',
            'password_confirmation' => 'testtest'
        ]);
        // ユーザーの登録に成功したら、以下のメッセージが表示される
        $I->see('You are logged in!');

        // ユーザー数が増えて1になっていることを確認する
        \PHPUnit_Framework_Assert::assertEquals(1, App\User::all()->count());
    }

submitFormで登録フォームを指定できるようにIDを追加します。

  • resources/views/auth/register.blade.php をエディターで開く
  • <form から始まる行を探して、以下のようにID属性を追加する
                    <form id="registerUserForm" class="form-horizontal" role="form" method="POST" action="{{ url('/register') }}">

以上で、テスト用のデータベースにユーザーを登録したことが確認できます。

通常のデータベースに変更がないことを確認する

通常のデータベースの中身を確認して、テストによる影響を受けていないことを確認します。ターミナルで以下を入力します。

mysql -u root -p
<ルートパスワードを入力>
use lara53_codeception;
select * from users;

データに変更がないことが確認できるはずです。


まとめ

以上で、 Laravel5.3 で Codeception を使ったテストが動くようになりました。Functionalテストで使える関数は以下で調べてください。

また、PHPUnitにあるアサーションは、 \PHPUnit_Framework_Assert::assertTrue(true); のようにすれば利用できます。PHPUnitアサーションは以下で調べてください。

CodeceptionのQuick Startをやってみる

PHP用のテストフレームワーク Codeception を使ってみます。Laravelのテストで .env を切り替える方法を調べると、片っ端から Codeception が使われていたので導入しようと思います。

公式ページ

Codeception

ダウンロードと設定

基本的な手順はこちらに載っています。→ Quick Start Codeception

利用したいプロジェクトフォルダーで以下を実行します。

composer require "codeception/codeception"
alias codecept='./vendor/bin/codecept'

インストール

以下で、テストに必要な環境を生成します。

codecept bootstrap

設定ファイルの codeception.yml や tests フォルダーが出来上がります。

テストの作成

最初の受け入れテスト(acceptance test)を作成しましょう。受け入れテストは、ユーザーがページを訪れる操作をエミュレートします。

codecept generate:cept acceptance Welcome

Codeceptionのシナリオテストのことを、 Cept と呼びます。

ここまで出来たら、最初のテストが実行できます。以下でテストを走らせましょう。

codecept run

最初に作成されるテストには、チェック項目がないのでテストは成功します。

テストファイルの編集

tests/acceptance/WelcomeCept.php に出力されたテストファイルを以下のように編集しましょう。

<?php
$I = new AcceptanceTester($scenario);
$I->wantTo('ページにアクセスできるか確かめます');
$I->amOnPage('/');
$I->see('tanaka\'s Programming Memo');

上書き保存をしたら、テストを動かしてみましょう。

codecept run

これは失敗します。アクセス先のURLを設定していないからです。

テストの設定ファイルを作成する

tests/acceptance.suite.yml を開いて、以下のように編集して、 URL を設定します。

class_name: AcceptanceTester
modules:
    enabled:
        - PhpBrowser:
            url: http://am1tanaka.hatenablog.com/
        - \Helper\Acceptance

保存をしてテストをしましょう。

codecept run

今度は成功します。


あとは、動かしたい環境に応じた使い方を調べていきましょう。

Laravel5.3でのテストのメモ

自分向けのメモです。

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

5.2から5.3になる段階で、データベースとモックに関する記述が増えていました。


実行時の注意点

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

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関数内で parent::setUp() として、親のsetUpを呼び出すこと

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

Application Testing - Laravel - The PHP Framework For Web Artisans

  • Laravelは、HTTPリクエストを生成したり、出力をテストしたり、フォームを埋めたりするのが楽になるAPIを提供する
  • visit()メソッドは、GETリクエストをアプリケーションに発行する
  • see()メソッドは、アプリケーションの戻り値から指定した文字列が含まれるかをアサートする
  • dontSee()メソッドは、指定の文字列が戻り値に含まれないことをアサートする
  • visitRoute()メソッドは、ルート名でGETリクエストを生成できる
$this->visitRoute('profile');

$this->visitRoute('profile', ['user' => 1]);

アプリケーションとの連携

リンクをクリックする
  • visit()で画面を表示
  • click('<クリックしたい文字列>')
  • seePageIs('遷移先のURL')
<a href="/about-us">About Us</a>
  • 上記のHTMLがあった場合、以下のテストが使える
public function testBasicExample()
{
    $this->visit('/')
         ->click('About Us')
         ->seePageIs('/about-us');
}
  • seePageIs()の代わりに、 seeRouteIs() も使える
->seeRouteIs('profile', ['user' => 1]);
フォームの操作
  • type(<入力内容>, <フォームのname>)で、指定のnameの入力フォームに指定のテキストを入力する
  • select(<選択内容>, <フォームのname>)で、ラジオボタンやドロップダウンの選択
  • check(<フォームのname>)で、指定のチェックボックスにチェック
  • uncheck(<フォームのname>)で、指定のチェックボックスのチェックを外す
  • attach(<ファイルのパス>, <フォームのname>)で、指定のファイルをアップロード対象にする
public function testPhotoCanBeUploaded()
{
    $this->visit('/upload')
         ->attach($pathToFile, 'photo')
         ->press('Upload')
         ->see('Upload Successful!');
}
  • press(<ボタンやテキストや要素のname>)で、該当するものを押す
JSONテスト
  • json, get, post, put, patch, deleteメソッドで、指定のURLに、指定のパラメータをJSONで渡した呼び出しができる
  • seeJson()で、指定のデータが戻り値に含まれるかをチェックする。「含むか」なので、完全一致じゃなくてもテストは成功する
  • JSONの完全な一致をチェックしたい場合は、seeJsonEquals()メソッドを利用する
  • 戻り値の内容ではなく、構造をチェックしたい場合は、seeJsonStructure()メソッドを使う。指定していないキーがあっても、指定した構造が含まれていればテストは成功する
  • 何らかのキーに、指定の構造が含まれるかをチェックする場合は、 * を使う。ネストも可能
SessionとAuthentication
  • withSession()メソッドで、セッションを設定できる。ページを訪れる前に、テストしたい値をセッションに設定しておくことができる
  • actingAs()で、カレントユーザーを設定できる。事前に、ModelFactoryで、新規のユーザーモデルを作成して、それを引数に渡してユーザーを作成できる。Factoryでテスト用ユーザーを登録して、それをユーザーとして認証する例
<?php

class ExampleTest extends TestCase
{
    public function testApplication()
    {
        $user = factory(App\User::class)->create();

        $this->actingAs($user)
             ->withSession(['foo' => 'bar'])
             ->visit('/')
             ->see('Hello, '.$user->name);
    }
}
  • actingAs()の第2引数を指定すると、毎回認証が必要な保護認証に対して、保護名を設定できる
ミドルウェアの無効化
  • WithoutMiddlewareトレイトを使うと、ミドルウェアを無効化して、テストを簡易にできる
    • クラス内に、use WithoutMiddleware;を宣言すると、そのテスト全てでミドルウェアが無効になる
<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    use WithoutMiddleware;

    //
}
    • 特定のテストのみで無効にしたい場合は、テストメソッドの中で、$this->withoutMiddleware();を呼び出す
<?php

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->withoutMiddleware();

        $this->visit('/')
             ->see('Laravel 5');
    }
}
カスタムHTTPリクエスト
  • カスタムリクエストを作成して、戻り値のIlluminate\Http\Responseオブジェクトを取得したい場合は、call()メソッドを使う
public function testApplication()
{
    $response = $this->call('GET', '/');

    $this->assertEquals(200, $response->status());
}
  • POST, PUT, PATCHリクエストに必要な入力データは、配列で渡す
$response = $this->call('POST', '/user', ['name' => 'Taylor']);
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を持っていないかを判定
  • assertSessionMissing($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ディレクトリーに加えることができる
ファクトリーの States
  • Statesとして、個別の修正を定義して、様々な組み合わせでモデルファクトリーに適用できる
  • Userモデルに delinquent というstate を持たせて、デフォルトの属性値を設定するなどができる
  • account_status属性に'delinquent'という値を持たせるためのステータスである delinquent を宣言する例
$factory->state(App\User::class, 'delinquent', function ($faker) {
    return [
        'account_status' => 'delinquent',
    ];
});

Factoryの利用

モデルの作成
  • ファクトリーを定義したら、テスト関数でfactory(モデルクラス)->make();とすれば、ファクトリーから生成したモデルのインスタンスを得られる
public function testDatabase()
{
    $user = factory(App\User::class)->make();

    // Use model in tests...
}
  • factory()の第2引数に数を渡すと、指定の数のインスタンスを生成する
  • 同じく、ファクトリー名を渡すと、該当するファクトリーを生成する
// Create three App\User instances...
$users = factory(App\User::class, 3)->make();

// Create an "admin" App\User instance...
$user = factory(App\User::class, 'admin')->make();

// Create three "admin" App\User instances...
$users = factory(App\User::class, 'admin', 3)->make();
States を適用する
  • モデルに state を適用することができる
$users = factory(App\User::class, 5)->states('deliquent')->make();
  • 多数の state をモデルに適用するには、その名前を以下のように指定する
$users = factory(App\User::class, 5)->states('premium', 'deliquent')->make();
属性の上書き
  • モデルのいくつかのデフォルト値をオーバーライドしたい場合、makeメソッドに配列を渡す
  • 指定した値のみが上書きされて、それ以外の値はファクトリーに設定されているデフォルト値が設定される
$user = factory(App\User::class)->make([
    'name' => 'Abigail',
]);
モデルを生成して保存する
  • create メソッドは、モデルインスタンスを作成した上で、Eloquentの save メソッドで保存する
    // Create a single App\User instance...
    $user = factory(App\User::class)->create();

    // Create three App\User instances...
    $users = factory(App\User::class, 3)->create();
  • makeメソッドと同様に配列を渡すと値を設定することができる
リレーションしたデータの作成
  • create()してモデルを作成すると collection のインスタンスが返されるので、 each() メソッドなどを使って作成されたばかりのモデルのインスタンスを取り出して、リレーションさせる他のモデルを生成することができる
$users = factory(App\User::class, 3)
           ->create()
           ->each(function ($u) {
                $u->posts()->save(factory(App\Post::class)->make());
            });
リレーションと属性のクロージャ
  • ファクトリーの定義で、モデルにリレーションを設定するのにクロージャー属性を使うこともできる
  • 新しい Post を作成するのと同時に、新しい User のインスタンを作成する方法は以下の通り
$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->title,
        'content' => $faker->paragraph,
        'user_id' => function () {
            return factory(App\User::class)->create()->id;
        }
    ];
});
  • これらのクロージャーは、引数として、そのクロージャーを含むファクトリーの属性の配列を受け取る
    • Postを生成した時に User も作成
    • user_type のクロージャーは $post 配列を受け取って、そこから user_id で生成した User の IDを取り出して検索して、Userに設定されているtypeと、Postのuser_typeをリレーションさせている
$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->title,
        'content' => $faker->paragraph,
        'user_id' => function () {
            return factory(App\User::class)->create()->id;
        },
        'user_type' => function (array $post) {
            return App\User::find($post['user_id'])->type;
        }
    ];
});

モック

イベントのモック

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

Fakeの利用

  • Eventファサードの fake メソッドを利用することでもモック化をして、すべてのイベントリスナーを無効にできる
  • それから、イベントが発生したかや、どのようなデータを返したかをチェックすれば良い
<?php

use App\Events\OrderShipped;
use App\Events\OrderFailedToShip;
use Illuminate\Support\Facades\Event;

class ExampleTest extends TestCase
{
    /**
     * Test order shipping.
     */
    public function testOrderShipping()
    {
        Event::fake();

        // Perform order shipping...

        Event::assertFired(OrderShipped::class, function ($e) use ($order) {
            return $e->order->id === $order->id;
        });

        Event::assertNotFired(OrderFailedToShip::class);
    }
}

ジョブのモック

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

  • $this->expectsJobs(App\Jobs\PurchasePodcast::class); などのようにあらかじめ呼び出しておくことで、指定のジョブが呼び出されたことを確認する。行うのは確認のみで、ジョブ自体を実行することはしない
  • このメソッドは、DispatchesJobsトレイトか、dispatch()メソッドによって発行されたジョブにのみ反応する。Queue::push()で直に送信されたジョブは対象外
  • ジョブが呼び出されなかったことを確認するには、 $this->doesntExpectJobs(ShipOrder::class) などのようにする
  • $this->withoutJobs() をテストメソッド内で呼び出すと、そのテスト中に予約されたすべてのジョブは破棄される
Fakeの利用
  • Queueファサードの fake メソッドで、キューに積まれたジョブをモック化できる
  • その後、キューにジョブが積まれたかを確認したり、それらが受け取るデータを検査すれば良い
  • アサーションは、fakeを呼び出したあと実行する

メールのFake

  • Mailファサードの fake メソッドを使うと、メールの送信を抑制できる
  • その後、ユーザーに送られるはずの mailables をアサーションしたり、受け取ったデータを検査する
<?php

use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;

class ExampleTest extends TestCase
{
    public function testOrderShipping()
    {
        Mail::fake();

        // Perform order shipping...

        Mail::assertSent(OrderShipped::class, function ($mail) use ($order) {
            return $mail->order->id === $order->id;
        });

        // Assert a message was sent to the given users...
        Mail::assertSentTo([$user], OrderShipped::class);

        // Assert a mailable was not sent...
        Mail::assertNotSent(AnotherMailable::class);
    }
}

NotificationのFake

  • Notificationのfakeも使い方は同様

Mocking - Laravel - The PHP Framework For Web Artisans

Facadeのモック

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

  • shouldReceive()メソッドを呼び出すと、Mockeryクラスのモックのインスタンスが返される
  • Laravelのservice containerにより管理されるので、そのままクラスを利用するよりもテストしやすい
<?php

class FooTest extends TestCase
{
    public function testGetIndex()
    {
        Cache::shouldReceive('get')
                    ->once()
                    ->with('key')
                    ->andReturn('value');

        $this->visit('/users')->see('value');
    }
}
  • Requestファサードをモックにすると、テストを実行する時にcall()やpost()メソッドのようなHTTPヘルパーメソッドもキャンセルされてしまうので、モックにするべきではない

macOS Sierra で MySQLを動かす

MySQL5.7系は、macOS Sierraでは5.7.15以降にしないと動かないようです。

Does macOS sierra support any version of mysql? | Apple Developer Forums

MySQL5.7.11から5.7.16にアップデートした時の手順です。

MySQLを停止

  • あらかじめ、MySQLを停止しておく(動いていないはずだが、念のため)
mysqladmin shutdown

バックアップ

サーバーが動いていないので mysqldump は使えません。とりあえず、データフォルダーをコピーしておきます。

sudo cp -R /usr/local/var/mysql ~/mysql5.7.11.backup

ダウンロード

  • MySQL :: MySQL Community Downloadsを開く
  • MySQL Community Server(GPL)の DOWNLOAD をクリック
  • 下にスクロールして、適切なプラットフォームを選択して、[DMG Archive]を[Download]する
  • ログインするか、[No thanks, just start my download.]をクリック
  • ダウンロードが完了するのを待つ

インストール

  • ダウンロードした .dmg ファイルをダブルクリックする
  • .pkg ファイルをダブルクリックする
  • Installerダイアログが表示されたら[Continue]>[Agree]>[Install]で進める
  • [Install for all users of this computer]を選択して[Continue]>[Install]
  • temporaryパスワードが画面に表示されるので、書き留めておく。インストール後、そのパスワードでログインしたのち、新しいパスワードを設定する
  • インストールが完了したら[Close]を押す

パスを設定

  • 以下で、設定を呼び出す
cd ~
vi .bashrc
  • export PATHに、 /usr/local/mysql/bin が含まれていなかったら追加する

アップグレード

  • mysqld_safeでサーバーを起動
sudo mysqld_safe --user=mysql --datadir=/usr/local/var/mysql
  • サーバーが起動して待機状態になる。新しいターミナルを開く
  • 以下でアップグレードを実行する
sudo mysql_upgrade -u root -p
  • 一時パスワードでアップグレードを実行する
  • 成功したら、以下で一旦シャットダウンしてから、再起動する
mysqladmin -u root -p shutdown
sudo mysqld_safe --user=mysql --datadir=/usr/local/var/mysql
  • 使えるターミナルで、以下を実行
mysql -u root -p
  • パスワードを設定する
SET PASSWORD = PASSWORD('some password')
exit

以上で、mysqld_safeでサーバーを起動すれば完了です。

データをデフォルトの場所に移動

毎回データフォルダーを指定するのが面倒なので、データをバックアップして、デフォルトの場所に読み込み直します。

  • 現在のデータをバックアップ
mysqldump -u root -p
  --add-drop-table --routines --events
  --all-databases --force > data-for-upgrade.sql
  • MySQLをデフォルトのデータフォルダーで再起動
mysqladmin -u root -p
sudo mysqld_safe
  • バックアップしたデータを使ってリストアして、データをアップグレード
mysql -u root -p --force < data-for-upgrade.sql
sudo mysql_upgrade -u root -p

以上で完了です。この後は、以下でMySQLを簡単に起動できます。

sudo mysqld_safe

シャットダウンは以下の通りです。

mysqladmin -u root -p shutdown

Laravel(5.3) SocialiteでTwitterのOAuthを試す

Laravelの公式パッケージに TwitterGitHubなどで認証を行える Socialite があります。Twitter にログインして、ユーザー情報(ユーザーID、ユーザー名、アバターなど)を表示してみます。

情報源:GitHub - laravel/socialite

前提

  • macOS Sierra
  • PHP5.6以降
  • MySQL 5.7.11
  • node、Laravel、Composerはインストール済み

プロジェクトを準備

以下の手順で、プロジェクトを作成します。

  • ターミナルを起動して、プロジェクトを作成したいフォルダーに移動する
  • 環境を更新
composer global update
npm update -g npm
npm update -g
  • 以下を実行して、Laravelプロジェクトを作成し、プロジェクトフォルダーに移動して、サーバーを起動する
laravel new laravel-socialite-test
cd laravel-socialite-test
php artisan serve

以上で準備完了です。Webブラウザで http://localhost:8000 にアクセスして、Laravelと表示されれば成功です。

Twitterにアプリを登録

試しにTwitterのアカウントを利用してみます。それをやるにはTwitterに連携させるアプリを登録する必要があります。以下、登録手順です。

  • Twitterにログインする
  • APIを登録するには、携帯の情報が必要なので、登録していない場合は以下の手順で登録する
    • プロフィールから設定を選択
    • 左のメニューから[モバイル]を選択
    • 携帯の番号を入力して[続ける]をクリック
    • 携帯に届いた認証コードを入力する
  • Twitter Application Management を開く
  • [Create New App]を押す
  • 以下を入力
    • Name にアプリ名を入力。SocialiteTest など
    • Description にアプリの内容を入力。Laravelの練習 など
    • Website に、アプリのトップページのURLなどを入力
      • ローカルホストは登録できなさそうなので、HPを持っていればそのURLを。なければGitHub Pagesなどで作成してください
    • Callback URL は、テスト用に http://localhost:8000/auth/twitter/callback としておく
    • Developer Agreementを確認したら[Yes, I have read and agree・・・]にチェックを入れる
    • [Create your Twitter Application]を押す

以上で、登録情報が表示されます。[Keys and Access Tokens]タブに必要な情報が表示されますので、選択してページをそのまま開いておいてください。

Socialiteを組み込む

Socialiteの読み込み

ターミナルで以下を実行して Socialite を読み込みます。

composer require laravel/socialite

Laravelに設定を加える

次にLaravelに設定をします。

  • config/app.php を開く
  • 'providers' の定義に、以下を追加
        // Laravel Socialite
        Laravel\Socialite\SocialiteServiceProvider::class,
  • 'aliases' の定義に、以下を追加
        'Socialite' => Laravel\Socialite\Facades\Socialite::class,
  • app.php を上書き保存
  • config/services.php を開く
  • returnする配列に、 facebook, twitter, linkedin, google, github, bitbucket というキーを定義して、必要な設定を定義する。Twitter を利用する場合は以下の通り
    'twitter' => [
        'client_id' => '登録情報の[Consumer Key (API Key)]の右の文字列をここに貼り付ける',
        'client_secret' => '登録情報の[Consumer Secret (API Secret)]の右の文字列をここに貼り付ける',
        'redirect' => 'http://localhost:8000/auth/twitter/callback',
    ],

コントローラーの作成

OAuthプロバイダーには2つのルートが必要です。一つ目はOAuthを提供するページにリダイレクトするルート、二つ目は認証が完了した時にコールバックで呼ばれるルートです。Socialite ファサードを利用して、認証した情報にアクセスができます。

  • ターミナルで以下を実行して、認証のためのコントローラーを作成する
php artisan make:controller Auth/AuthController
  • app/Http/Auth/Controllers/AuthController.php を開く
  • 以下のように修正する
<?php

namespace App\Http\Controllers\Auth;

use Illuminate\Http\Request;

use Socialite;
use App\Http\Requests;
use App\Http\Controllers\Controller;

class AuthController extends Controller
{
    /**
     * ユーザーをTwitterの認証ページにリダイレクトする
     *
     * @return Response
     */
    public function redirectToProvider()
    {
        return Socialite::driver('twitter')->redirect();
    }

    /**
     * Twitterからユーザー情報を取得する
     *
     * @return Response
     */
    public function handleProviderCallback()
    {
        $user = Socialite::driver('twitter')->user();

        $response =
            "id: ".$user->id
            ."<br/>Nickname: ".$user->nickname
            ."<br/>name: ".$user->name
            ."<br/>Email: ".$user->email
            ."<br/>Avater: <img src='".$user->avatar."'>"

            . "<br/><br/>";

        // OAuth Two Providers
        $response .= print_r($user, true);

        return $response;
    }
}

上記のコントローラーをルートに追加します。

  • routes/web.php を開く
  • 以下のルートを追加する
Route::get('auth/twitter', 'Auth\AuthController@redirectToProvider');
Route::get('auth/twitter/callback', 'Auth\AuthController@handleProviderCallback');

上書き保存をしたら、app.php を書き換えたので、サービスを起動し直します。

  • サービスを動かしていたターミナルで [control]+[C]で停止
  • php artisan serve でサービスを再起動

http://localhost:8000/auth/twitter にアクセスします。ここまで間違えがなければ Twitter の認証ページが開きます。[連携アプリを認証]を押すと認証が完了して、 http://localhost:8000/auth/twitter/callback にリダイレクトされます。このルートで AuthController.php の handleProviderCallback メソッドが呼ばれて、 Twitter から取り出したユーザー情報が画面に表示されます。

テストが終了したら

テストで作成したアプリが不要になったら、以下の手順で削除してください。

その他のプロバイダー

facebook, twitter, linkedin, google, github, bitbucket のサービスについては、基本的には同様の方法でアクセスできると思います。

上記以外のサービスを使いたい場合は、以下から探してください。