20200110のlaravelに関する記事は11件です。

Laravel: Cookiesが含んでいるテスト

背景

LaravelのCookieの処理

Laravelでは、ユーザーの安全を守るため、MiddlewareでCookieの情報を暗号化され、認証コードで署名しています。CookieがLaravelのフレームワークで暗号化されていない場合(例えばクライエントで弄ったCookieや暗号化されていないCookie)、そのCookieが無効になって、リクエストから削除されます。

PHPUnitでテスト行うとき

LaravelCookieの暗号化処理はすごく良い機能と思いますが、残念ながらテストでリクエスト関数のcallpostなどにCookieを渡せばこの暗号化の処理は自動的に行われていません。

つまり、下記のようにテストでCookieをリクエストに追加して、

HogeControllerTest.php
       public function testHoge()
       {
            $cookie = ['hoge_test' => 'sugehoge'];
            $resp = $this->call(
                'get',
                '/hoge',
                [],
                $cookie
            );
        }

それで下記のようにコントローラーでCookieの値を取得しようとするとnullが出力されてしまします。

HogeController.php
    public function testCookie(Request $request)
    {
        dd(Cookie::get('hoge_test'));
        // null
    }

Cookieを含んでいるテストする方法

Laravel 6.x 以上

このPR で簡単にCookieの処理ができるように関数を追加がしました。

withCookies()

テストでリクエストを呼ぶときwithCookies($array)関数を追加して、['キー' => '値']の配列を引数としてわたすだけで、Cookieの暗号化の処理が行われます。

注意点

PRによると

To remain backwards compatible, the call method does not honor cookies set by these new methods.

後方互換のためこの新しい追加した機能はcall()関数対応ではありません!つまりこの機能使いたいときリクエストの種類の名前のpost()get()などの関数を使う必要があります。

HogeControllerTest.php
   public function testHoge()
   {
        $cookie = ['hoge_test' => 'sugehoge'];
        // withCookiesの関数追加
        // call()ではなく、get()にする
        $resp = $this->withCookies($cookie) 
            ->get('/hoge', []);
   }

テスト実行すれば、sugehogeがちゃんと出力されます。

ちなみに、Cookieを一つだけ設定するwithCookie($name, $value)の関数も用いて、Cookieのキーを引数としてわたせば、withCookies($array)と同じようにCookieが正しく設定されます。

すごく便利な機能ですね!

Laravel 5.x 以上

勿論、下記に書いてある項目の方法はLaravel 6.x でも使う可能で、それがこのみなら使っても構いません!

Middlewareを無効にする

Cookieの暗号化はEncryptCookiesのミドルウエアで行われているので、それを無効すれば、テストが無事に走るはず。Laravelのテストではミドルウエアを無効にするWithoutMiddlewareのトレイトが用いていて、それが使えます。

HogeControllerTest.php
   use WithoutMiddleware;

   public function testHoge()
   {
        // middlewareを無効
        $this->withoutMiddleware(EncryptCookies::class);
        $cookie = ['hoge_test' => 'sugehoge'];
        // post()じゃなくて、call()つかいます
        $resp = $this->call('get', '/hoge', [], $cookie);
    }

今回テストをはしると,sugehoge、がちゃんと出力されます!

Cookieを暗号化する

テストで暗号化が行われていないので、Cookieの値を暗号化するとテストが走るはず。Laravelのヘルパー関数encrypt()で暗号化処理ができる。

HogeControllerTest.php
   public function testHoge()
   {
        // Cookieの値の暗号化行う
        $cookie = ['hoge_test' => encrypt('sugehoge', false)];
        $resp = $this->call('get', '/hoge', [], $cookie);
    }

テストをはしると,sugehogeが出力されます。

注意点

encrytp('値', false)の関数にfalseを渡さないと値がserializeされ、"s:12:"sugehoge";"が出力されてしまいます。なので、falseを渡すのを忘れないで下さい。

終わり

いくつかの方法があります。
これから自分のテストにつかってみてください!

参照のリンク

https://readouble.com/laravel/6.x/ja/requests.html#cookies
https://readouble.com/laravel/5.1/ja/testing.html#disabling-middleware
https://github.com/laravel/framework/pull/30101
https://laravel.com/api/6.x/Illuminate/Contracts/Encryption/Encrypter.html
https://github.com/laravel/framework/issues/12032

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel-Enum 導入

概要?

Laravel-Enum の使い方をまとめておこうと思い。記事を書く所存。

準備

laraevl のバージョン

$ php artisan --version
Laravel Framework 5.8.16

使えるようにする

以下のコマンドを実行すると、Laravel 上で Enum(列挙型)が使用できるようになる。
Enumって、ラジオボタンとかセレクトボックスの中身とかに使えて便利だと思う。

$ composer require bensampo/laravel-enum

使いたい

コマンドからクラスを作成する

コマンドを実行して、Enum クラスを作成する

$ php artisan make:enum TestText
Enum created successfully.

正常に作成すると、app/EnumsのしたにTestText.php ファイルが作成される.
作成したクラスを以下のように編集する。

<?php

namespace App\Enums;

final class TestText extends Enum
{
    const TEST = "test";
    const TEXT = "text";
}

日本語化対応もできる

app/resources/lang/ja/enums.phpを作成するよ。

<?php
// php artisan で作成したクラス
use App\Enums\TestText;

return [

    TestText::class=> [
        TestText::TEST => 'テスト',
        TestText::TEXT => 'テキスト',
    ],
];

ただ上記のファイルの恩恵を受けるためには、app/config/app.phplocaleを日本語に設定しておく必要があるよ。

<?php

return [
    // 略
    'locale'          => 'ja',
    // 略
];

とりあえず View で使ってみる。

例えば、ラジオボタンとか。
toSelectArraykey=>valueで返してくれるので便利。
これはlalavel-enum のメソッド。便利。

<div class="input-group col-12 col-md-6">
    {{ Form::label("test_list_label", "テストラジオ", ['class'=> 'required']) }}
    <div class="radios">
        @foreach (\App\Enums\TestText::toSelectArray() as $value => $item)
            <div class="radio">
                {{ Form::radio('test_list_item', $value, $value === 'test', [
                    'id'    => "test_list_item--{$value}",
                ]) }}
                {{ Form::label("test_list_item--{$value}", $item, []) }}
            </div>
        @endforeach
    </div>
</div>

Mysql のデータベースのあれこれもあとで書くんよ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelにあるEloquentのfindとfirstOrFailにハマったけど、解決した話

はじめに

以下のような使い方をしてしまい、想定した結果にならずハマりました

// $idには1が入っている
$user = User::find($id)->firstOrFail();

知ってしまった今、なんてこと無い話になりますが、未来の自分に向けて備忘録を残しておきます

Laravelのバージョン

$ php artisan -V
Laravel Framework 5.8.29

前提

  • 以下のモデルは作成済み
    • App/User.php

結論

// $idには1が入っている
$user = User::find($id)->firstOrFail();

上記のような使い方をすると、以下のように2回SQLが実行されていました

select * from users where id = 1;
select * from users limit 1;

そのため、変数には2回目のSQLの実行結果が入っており、想定した動作になっていませんでした

対策

以下の書き方であれば、同じ動きになるようです

その①

// $idには1が入っている
$user = User::findOrFail($id);

その② firstOrFailを使用する場合(where句のあとはOKでした)

// $idには1が入っている
$user = User::where('id', $id)->firstOrFail();

参考

Laravel 5.8 Eloquent:利用の開始

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel × PHPUnit & Postman でのテストコード(Passport にも対応)

Laravel で開発されているプロダクトにおいてテストコードを書く方法を、 PHPUnit および Postman を主体にしてまとめます。

テスト作成の背景

弊社(株式会社 NoSchool)では、Web フロントエンドに Nuxt を利用しており、また、iOS ネイティブアプリも開発しているため、Laravel を原則 API ベースで開発することが多いです。

API のテストを作成することは、バグのリスクを低減したり、アプリエンジニアとの仕様の共有のために必須となります。2020 年 1 月現在 NoSchool では PHPUnit および Postman を利用していますので、ドキュメントを兼ねてまとめました。

この記事を読むとわかること

  • PHPUnit を使って API 単位でのテストを書く方法
  • PHPUnit を使って Class 単位での単体テストを書く方法
  • Postman を使って API のレスポンスをテストする方法
  • それぞれの比較と、実運用する際に共存する方法

PHPUnit

概要

PHPUnit は、PHP のコードベースでテストを記述できるフレームワークです。

PHPUnit でテストを書く目的

PHP のコードベースのため、データベースのサンプルデータを柔軟に生成し、テスト後に破棄すると言ったことが可能になります。そのため、細かいテストケースを実装するのに向いています。

書き方

ディレクトリ構造

弊社ではtestsディレクトリ以下に下記のディレクトリ構造でテストを書いていますので、参考までに。

./Feature 結合テスト(API テスト)を書きます。HTTP のエンドポイント単位でのテストを書きます。API を開発したときは原則必須で対応するテストコードを実装しましょう。

./Unit 単体テストを書きます。クラス単位でのテストを書きますが、工数が多くかかること、設計変更に追従するコスト、および Final Class をモック化できない Mockery の制限などの理由から、あまり実装されることはありません。Rendering まで Laravel で行っている一部ページや、クラス単位でのテストをどうしても書きたい場合はこちらに書きます。

./Fixture テスト用のデータを書きます。アプリ経由の課金で Apple から送信されるレシートなど、細かいデータを表現するときに使います。

./Lib 複数のテストケースの基底となるクラスなどを格納します。

各テストコードの基本

各テストコードは、それぞれのディレクトリ以下にドメインごとに namespace を切って作成してください。また、命名は ◯◯Test.php で作成してください。

ex. ./Feature/Chat/RoomTest.php

また、各テストケースはTests\TestCaseクラスを継承してください。

final class RoomTest extends TestCase

各テストケースの基本

各テストケースは、test◯◯ という名称の関数で作成します。

use Tests\TestCase;

final class RoomTest extends TestCase
{
    protected function setUp() // 後述
    {
        parent::setUp();
    }

    public function testSendMessage()
    {
        // hogehoge
    }

API テスト

一つのテストケースの中で、下記のようなフェーズを順に実行します。

  • テストデータの準備
  • API リクエストの実行
  • レスポンスのステータス、Body のテスト
  • 必要に応じて、変化したデータベースの中身のテスト

テストデータの準備

一般に、各テストケースにおけるテストデータの準備は、setUp()メソッドにて実装します。setUp()は各テストケースの実行直前に呼ばれます。反対に、終わったあとに呼ばれるのはtearDown()です。こちらはデータの後始末などに利用します。

    protected function setUp()
    {
        parent::setUp();

        $this->room = factory(Room::class)->create([
            'user_id' => 1111
        ]);
    }

ここでのポイントは大きく 2 点あります。

まず、parent::setUp()を実行することです。これを実行することで、Laravel アプリケーションを動かすための初期設定が終わります。実行しない場合、例えばconfig()を使った実装等が動かないので必ず実行してください。

詳しい実装は/Illuminate/Foundation/Testing/TestCase.phpに書いてあります。

2 つめのポイントはfactory()グローバルヘルパを利用してテストデータを生成することです。

ファクトリはdatabase/factories以下に作成します。詳しい書き方は実際のコードを読んだり、公式ドキュメントを参照してください。ここで重要なのは以下の 2 点です。

  • factory を create するタイミングでテストデータを Array で渡すことで上書きできる
  • factory 自体に名前をつけることができるため、例えば同じ User Model に対する factory でも違う初期データを持ったインスタンスを作り分けることができる

ファクトリ生成後は、実データがデータベースにインサートされているため、それがある前提で以降のテストを書くことができます。

【補足】テストデータの削除

テストデータを毎回テストのたびに生成していると、データベースがテストデータで溢れかえってしまいます。ここで、DatabaseTransactionsを利用することで当該テストにて作成されたデータは全てテスト終了後に削除されます。

use Illuminate\Foundation\Testing\DatabaseTransactions;

class RoomTest extends TestCase
{
    use DatabaseTransactions;

API リクエストの実行

API リクエストは下記のように実行します。リクエストするメソッドには複数ありますが、個人的にはjsonメソッドが好きです。

$response = $this->json(
    'PUT',
    "/api/chat/{$this->room->id}",
    [
        'body' => 'message
    ],
    $headers
)

【補足】Laravel Passport を利用している場合

Passport を使っていると、都度都度 API リクエストに特定のヘッダを指定しなければなりません。具体的には、Bearerトークンが必要です。

これを解消するための手段を公開してくれている Web ページが有りましたので、ご参照ください。大まかに言えば、setUp()を拡張してそこでUserに対応したアクセストークンを都度都度発行しています。

https://www.whizz-tech.co.jp/1442/

こうすることで、json()実行時の第 4 引数にヘッダーでBearerを渡せば認証済みユーザーとしてテストが実行できます。

また、弊社のネイティブアプリ向けの API の場合、ログアウトユーザーでもクライアント単位でのグラントトークンを Bearer に載せることを必須としているのですが、そのトークンもsetUp()を使って実装できます。/oauth/tokenへ POST を飛ばしてトークンを都度発行すればいいです。

【補足】Cookie 認証の API を利用している場合

Cookie 認証の API を利用していて、かつ認証済みのユーザーで API をリクエストしたいときはactingAsメソッドを利用します。

$response = $this->actingAs($user)
    ->json('PUT', '/api/room', [
    ]);

factory(User::class)->create()でテストユーザーを作成後、そのユーザーで認証された状態で API を叩いた場合のテストなどをするときに有効です。

レスポンスのステータス、Body のテスト

レスポンスのステータスは、下記のようにテストします。

$response->assertStatus(204);

レスポンスの Body は、下記のようにテストします。

$response->assertJson([
    'message_id' => 100
]);

この場合のテストは一例で、具体的な値をテストできるものから、JSON の構造のみテストするものまで色々あります。assertJsonはかなりゆるく、指定したキーを持ってさえいれば、余分なキーが含まれていてもエラーになりません(第 2 引数でより strict にできるようです)。

ここでどうテストするかはテストケースで何をしたいかによるでしょう。ただ、特にネイティブアプリへのレスポンスなど、型にまで気を使わなければいけない場合において、下記のように構造とそれぞれの持っている型を見ることになると思います。

$response->assertJsonStructure([
    'id',
    'user' => [
        'id',
        'name',
    ],
    'created_at',
]);
$this->assertIsInt($response->json('id'));
$this->assertIsInt($response->json('created_at'));
$this->assertIsInt($response->json('user.id'));
$this->assertIsString($response->json('user.name'));

ネストしているキーに、Laravel にあるあるのドット記法でアクセスできるのは便利ですね。

詳しくは公式情報をどうぞ。

https://laravel.com/api/5.7/Illuminate/Foundation/Testing/TestResponse.html
https://phpunit.readthedocs.io/ja/latest/assertions.html#assertisint

必要に応じて、変化したデータベースの中身のテスト

副作用のある API であれば、データベースに変更をもたらすでしょう。

データベースの中身をテストする場合は下記のように記述します。

$this->assertEquals(
    3,
    Room::where('body', 'hogehoge')->count()
);

https://readouble.com/laravel/5.7/ja/database-testing.html を見る限り、assertDatabaseHasというメソッドも用意されています。しかし、この場合はカウントで 2 つ以上のものがあること、というのは確認できませんので、用途に応じて使い分けてください。

このように Eloquent Model を使ってクエリを発行して結果をチェックというのは少々我ながら筋が悪いため、より上手に書ける方法を知っている方は教えていただけると嬉しいです。

単体テスト

単体テストは、前述したとおり結構モック化を頑張らないと厳密な単体テストの実現が難しいです。

もし実装したい場合は、下記のようなコツを念頭に実装してみてください。

ここではユースケース層のテストを例に話します。NoSchool の場合、ユースケース層からは Repository の Interface を呼んでおり、AppServiceProviderで実装クラスにbindingしているという特徴を持っているため、その前提での解説となります。

DI を使っている場合はresolveヘルパでインスタンスを取得

DI している場合は普通にコンストラクタを書けないはずなので、resolveヘルパでインスタンスを取得し、そこからメソッドを実行してテストします。

$sendChatMessageUseCase = resolve(SendChatMessageUseCase::class);

テストしたいクラス内で使っている外部クラスを Mock にしたい場合、DI するのが一番確実っぽい

以下の例はユースケースで使っている Repository をモック化した例です。

$repository = Mockery::mock(new MemberRepository($this->user));
$repository->shouldReceive('findMember')->once()->andReturn(
    $member
);
$repository->shouldReceive('createMember')->once()->andReturn($member);

$this->app->instance(
    'App\Infrastructure\MemberRepository',
    $repository
);

final クラスをモック化するときはalias:記法を使う

final なクラスをモックにするときはalias:を先頭につけて namespace を記述します。なんじゃこりゃ。まあ、そもそも final なクラスをモックにする意味あるのかって話なのですが。

$requestStub = \Mockery::mock('alias:App\Helpers\HttpClient');

といったややこしい仕様が散見されるので、特にfinalなクラスを使うのをやめるか、\Mockeryを使った単体テストはあまり書かないようにするか、というのが工数的なデメリットを鑑みると妥当かなというのが感想です。より良いライブラリや、Mockery でももっと楽に書けるぞ、という情報提供お待ちしております。

テストの実行

テストの実行は、./vendor/bin/phpunitコマンドで実行します。ディレクトリをオペランドに指定することも、ファイルを指定することも、何も指定せずに全てをテストすることも可能です。

[mejileben]$ ./vendor/bin/phpunit ./tests/Feature/Room

上記のコマンドでは、Roomディレクトリ以下のテストケースを全て実行します。

コマンドを覚えたくない方は、composerartisanコマンドとしてエイリアスを貼ってもいいでしょう。

Postman

概要

Postman は、API のドキュメントを書いたり、テストケースを書いてまとめて複数の API をテストできるサービスです。

Swagger のテスト実行機能がついたようなイメージです。Swagger を使ったことがないのでイメージですが。

Postman を導入する目的

Postman を導入することで、API ドキュメントをネイティブアプリエンジニアに Web 経由で共有することができます。NoSchool では API 定義を JSON でエクスポートし、Git 管理しています。これを各自が Import することで API 定義を確認できます。有料プランに加入することで複数人が Web 上で共有を完結できるそうですが、ケチっているのでまだ無料プランです。

Postman 環境設定の概要

詳しくは割愛しますが、主に以下の点に注意が必要です。

  • 秘匿情報は Environment として管理し、GitHub には上げない。または暗号化する
  • ローカル環境で HTTPS を利用しているとき、設定画面から SSL のチェックを無効化しないとオレオレ証明書でエラーを吐いてしまう

Postman を使った API テストのしかた

Postman で API 定義を作成し、試しに「Send」ボタンを押すと、レスポンスが返ってきます。

ここまで終わったら、「Tests」タブを開き、下記のようにテストを書いていきます。

var jsonData = pm.response.json();

pm.test("id is number", function() {
    pm.expect(jsonData.id).to.be.a("number");
});

pm.test("room.id is 1540", function() {
    pm.expect(jsonData.room.id).to.eq(1540);
});

Postman のテストケースはJavaScriptのライブラリであるChaiを利用しているため、JavaScript ベースで書くことができます。

まずjson()メソッドでレスポンスの JSON を取得します。

次にpm.test()でテストケースを作成します。expect()でテストしたい値を JSON から抜き取り、その後は英文を書くかのようにメソッドチェーンで.to.be.aなどとつないでいき、最後に型名か具体的な値を書くことでテストが実行されます。

ここでは.to.beをつけることは必須ではないのですが、そのほうが英文っぽく書けるので好ましいと思っています。それだけです。Rspec 等と思想が似ていると思います。

Postman のテストでは具体的な値等までテストすることは難しいので、基本的にレスポンスの型のみテストすることが大半です。

Collection を作る

さて、API テストをいくつか作成すると、Collection というものにまとめることができます。Collection は、例えば Q&A サイトの場合、「質問したあとに回答がつき、それをベストアンサーにする」といった複数の連続した API のテストをすることが可能です。

テストを実行する

テストの実行は、クライアントアプリケーション上からもできるし、CLI から Node.js 等のコマンドベースでも実行可能です。

Postman を利用するメリットは?

API テストの表現力という意味では PHPUnit で頑張るほうが身の丈に合ってそうですが、例えば検証環境など、HTTP 越しでアクセスしたい場合は Postman でリクエストの向き先を Localhost から検証環境に向けて実行することができるなど、手軽さという意味では Postman に軍配があがるイメージです。

ただ、CI を組み込んだ場合などは結局 PHPUnit を検証環境で実行することなど簡単でしょうから、Postman でテストを頑張るメリットは薄れ、Swagger 等と同様で API の定義を複数の社員間で共有することがメイン目的となるでしょう。

個人的には Passport を使っている環境でも PHPUnit ベースでテストできることが衝撃でした。先の記事を公開した方に感謝です。

まとめ

PHPUnit も Postman も両方 API ベースでのテストの記述が可能ですが、PHPUnit のほうがより細かい制御が可能です。Postman は API 定義をまとめるのに特化して、PHPUnit でできる限りテストを書いていくのが 1 つの良い共存方法かと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel × PHPUnit & Postman でのテストコードまとめ(Passport にも対応)

Laravel で開発されているプロダクトにおいてテストコードを書く方法を、 PHPUnit および Postman を主体にしてまとめます。

テスト作成の背景

弊社(株式会社 NoSchool)では、Web フロントエンドに Nuxt を利用しており、また、iOS ネイティブアプリも開発しているため、Laravel を原則 API ベースで開発することが多いです。

API のテストを作成することは、バグのリスクを低減したり、アプリエンジニアとの仕様の共有のために必須となります。2020 年 1 月現在 NoSchool では PHPUnit および Postman を利用していますので、ドキュメントを兼ねてまとめました。

この記事を読むとわかること

  • PHPUnit を使って API 単位でのテストを書く方法
  • PHPUnit を使って Class 単位での単体テストを書く方法
  • Postman を使って API のレスポンスをテストする方法
  • それぞれの比較と、実運用する際に共存する方法

環境

  • Laravel 5.7(古い...)
  • PHPUnit 7.0
  • Mockery 1.2

PHPUnit

概要

PHPUnit は、PHP のコードベースでテストを記述できるフレームワークです。

PHPUnit でテストを書く目的

PHP のコードベースのため、データベースのサンプルデータを柔軟に生成し、テスト後に破棄すると言ったことが可能になります。そのため、細かいテストケースを実装するのに向いています。

書き方

ディレクトリ構造

弊社ではtestsディレクトリ以下に下記のディレクトリ構造でテストを書いていますので、参考までに。

./Feature 結合テスト(API テスト)を書きます。HTTP のエンドポイント単位でのテストを書きます。API を開発したときは原則必須で対応するテストコードを実装しましょう。

./Unit 単体テストを書きます。クラス単位でのテストを書きますが、工数が多くかかること、設計変更に追従するコスト、および Final Class をモック化できない Mockery の制限などの理由から、あまり実装されることはありません。Rendering まで Laravel で行っている一部ページや、クラス単位でのテストをどうしても書きたい場合はこちらに書きます。

./Fixture テスト用のデータを書きます。アプリ経由の課金で Apple から送信されるレシートなど、細かいデータを表現するときに使います。

./Lib 複数のテストケースの基底となるクラスなどを格納します。

各テストコードの基本

各テストコードは、それぞれのディレクトリ以下にドメインごとに namespace を切って作成してください。また、命名は ◯◯Test.php で作成してください。

ex. ./Feature/Chat/RoomTest.php

また、各テストケースはTests\TestCaseクラスを継承してください。

final class RoomTest extends TestCase

各テストケースの基本

各テストケースは、test◯◯ という名称の関数で作成します。

use Tests\TestCase;

final class RoomTest extends TestCase
{
    protected function setUp() // 後述
    {
        parent::setUp();
    }

    public function testSendMessage()
    {
        // hogehoge
    }

API テスト

一つのテストケースの中で、下記のようなフェーズを順に実行します。

  • テストデータの準備
  • API リクエストの実行
  • レスポンスのステータス、Body のテスト
  • 必要に応じて、変化したデータベースの中身のテスト

テストデータの準備

一般に、各テストケースにおけるテストデータの準備は、setUp()メソッドにて実装します。setUp()は各テストケースの実行直前に呼ばれます。反対に、終わったあとに呼ばれるのはtearDown()です。こちらはデータの後始末などに利用します。

    protected function setUp()
    {
        parent::setUp();

        $this->room = factory(Room::class)->create([
            'user_id' => 1111
        ]);
    }

ここでのポイントは大きく 2 点あります。

まず、parent::setUp()を実行することです。これを実行することで、Laravel アプリケーションを動かすための初期設定が終わります。実行しない場合、例えばconfig()を使った実装等が動かないので必ず実行してください。

詳しい実装は/Illuminate/Foundation/Testing/TestCase.phpに書いてあります。

2 つめのポイントはfactory()グローバルヘルパを利用してテストデータを生成することです。

ファクトリはdatabase/factories以下に作成します。詳しい書き方は実際のコードを読んだり、公式ドキュメントを参照してください。ここで重要なのは以下の 2 点です。

  • factory を create するタイミングでテストデータを Array で渡すことで上書きできる
  • factory 自体に名前をつけることができるため、例えば同じ User Model に対する factory でも違う初期データを持ったインスタンスを作り分けることができる

ファクトリ生成後は、実データがデータベースにインサートされているため、それがある前提で以降のテストを書くことができます。

【補足】テストデータの削除

テストデータを毎回テストのたびに生成していると、データベースがテストデータで溢れかえってしまいます。ここで、DatabaseTransactionsを利用することで当該テストにて作成されたデータは全てテスト終了後に削除されます。

use Illuminate\Foundation\Testing\DatabaseTransactions;

class RoomTest extends TestCase
{
    use DatabaseTransactions;

API リクエストの実行

API リクエストは下記のように実行します。リクエストするメソッドには複数ありますが、個人的にはjsonメソッドが好きです。

$response = $this->json(
    'PUT',
    "/api/chat/{$this->room->id}",
    [
        'body' => 'message
    ],
    $headers
)

【補足】Laravel Passport を利用している場合

Passport を使っていると、都度都度 API リクエストに特定のヘッダを指定しなければなりません。具体的には、Bearerトークンが必要です。

これを解消するための手段を公開してくれている Web ページが有りましたので、ご参照ください。大まかに言えば、setUp()を拡張してそこでUserに対応したアクセストークンを都度都度発行しています。

https://www.whizz-tech.co.jp/1442/

こうすることで、json()実行時の第 4 引数にヘッダーでBearerを渡せば認証済みユーザーとしてテストが実行できます。

また、弊社のネイティブアプリ向けの API の場合、ログアウトユーザーでもクライアント単位でのグラントトークンを Bearer に載せることを必須としているのですが、そのトークンもsetUp()を使って実装できます。/oauth/tokenへ POST を飛ばしてトークンを都度発行すればいいです。

補足: https://laravel.com/docs/5.7/passport#testing に書かれているactingAsを使った実装は何故か動きませんでした

【補足】Cookie 認証の API を利用している場合

Cookie 認証の API を利用していて、かつ認証済みのユーザーで API をリクエストしたいときはactingAsメソッドを利用します。

$response = $this->actingAs($user)
    ->json('PUT', '/api/room', [
    ]);

factory(User::class)->create()でテストユーザーを作成後、そのユーザーで認証された状態で API を叩いた場合のテストなどをするときに有効です。

レスポンスのステータス、Body のテスト

レスポンスのステータスは、下記のようにテストします。

$response->assertStatus(204);

レスポンスの Body は、下記のようにテストします。

$response->assertJson([
    'message_id' => 100
]);

この場合のテストは一例で、具体的な値をテストできるものから、JSON の構造のみテストするものまで色々あります。assertJsonはかなりゆるく、指定したキーを持ってさえいれば、余分なキーが含まれていてもエラーになりません(第 2 引数でより strict にできるようです)。

ここでどうテストするかはテストケースで何をしたいかによるでしょう。ただ、特にネイティブアプリへのレスポンスなど、型にまで気を使わなければいけない場合において、下記のように構造とそれぞれの持っている型を見ることになると思います。

$response->assertJsonStructure([
    'id',
    'user' => [
        'id',
        'name',
    ],
    'created_at',
]);
$this->assertIsInt($response->json('id'));
$this->assertIsInt($response->json('created_at'));
$this->assertIsInt($response->json('user.id'));
$this->assertIsString($response->json('user.name'));

ネストしているキーに、Laravel にあるあるのドット記法でアクセスできるのは便利ですね。

詳しくは公式情報をどうぞ。

https://laravel.com/api/5.7/Illuminate/Foundation/Testing/TestResponse.html
https://phpunit.readthedocs.io/ja/latest/assertions.html#assertisint

必要に応じて、変化したデータベースの中身のテスト

副作用のある API であれば、データベースに変更をもたらすでしょう。

データベースの中身をテストする場合は下記のように記述します。

$this->assertEquals(
    3,
    Room::where('body', 'hogehoge')->count()
);

https://readouble.com/laravel/5.7/ja/database-testing.html を見る限り、assertDatabaseHasというメソッドも用意されています。しかし、この場合はカウントで 2 つ以上のものがあること、というのは確認できませんので、用途に応じて使い分けてください。

このように Eloquent Model を使ってクエリを発行して結果をチェックというのは少々我ながら筋が悪いため、より上手に書ける方法を知っている方は教えていただけると嬉しいです。

単体テスト

単体テストは、前述したとおり結構モック化を頑張らないと厳密な単体テストの実現が難しいです。

もし実装したい場合は、下記のようなコツを念頭に実装してみてください。

ここではユースケース層のテストを例に話します。NoSchool の場合、ユースケース層からは Repository の Interface を呼んでおり、AppServiceProviderで実装クラスにbindingしているという特徴を持っているため、その前提での解説となります。

DI を使っている場合はresolveヘルパでインスタンスを取得

DI している場合は普通にコンストラクタを書けないはずなので、resolveヘルパでインスタンスを取得し、そこからメソッドを実行してテストします。

$sendChatMessageUseCase = resolve(SendChatMessageUseCase::class);

テストしたいクラス内で使っている外部クラスを Mock にしたい場合、DI するのが一番確実っぽい

以下の例はユースケースで使っている Repository をモック化した例です。

$repository = Mockery::mock(new MemberRepository($this->user));
$repository->shouldReceive('findMember')->once()->andReturn(
    $member
);
$repository->shouldReceive('createMember')->once()->andReturn($member);

$this->app->instance(
    'App\Infrastructure\MemberRepository',
    $repository
);

final クラスをモック化するときはalias:記法を使う

final なクラスをモックにするときはalias:を先頭につけて namespace を記述します。なんじゃこりゃ。まあ、そもそも final なクラスをモックにする意味あるのかって話なのですが。

$requestStub = \Mockery::mock('alias:App\Helpers\HttpClient');

といったややこしい仕様が散見されるので、特にfinalなクラスを使うのをやめるか、\Mockeryを使った単体テストはあまり書かないようにするか、というのが工数的なデメリットを鑑みると妥当かなというのが感想です。より良いライブラリや、Mockery でももっと楽に書けるぞ、という情報提供お待ちしております。

テストの実行

テストの実行は、./vendor/bin/phpunitコマンドで実行します。ディレクトリをオペランドに指定することも、ファイルを指定することも、何も指定せずに全てをテストすることも可能です。

[mejileben]$ ./vendor/bin/phpunit ./tests/Feature/Room

上記のコマンドでは、Roomディレクトリ以下のテストケースを全て実行します。

コマンドを覚えたくない方は、composerartisanコマンドとしてエイリアスを貼ってもいいでしょう。

Postman

概要

Postman は、API のドキュメントを書いたり、テストケースを書いてまとめて複数の API をテストできるサービスです。

Swagger のテスト実行機能がついたようなイメージです。Swagger を使ったことがないのでイメージですが。

Postman を導入する目的

Postman を導入することで、API ドキュメントをネイティブアプリエンジニアに Web 経由で共有することができます。NoSchool では API 定義を JSON でエクスポートし、Git 管理しています。これを各自が Import することで API 定義を確認できます。有料プランに加入することで複数人が Web 上で共有を完結できるそうですが、ケチっているのでまだ無料プランです。

Postman 環境設定の概要

詳しくは割愛しますが、主に以下の点に注意が必要です。

  • 秘匿情報は Environment として管理し、GitHub には上げない。または暗号化する
  • ローカル環境で HTTPS を利用しているとき、設定画面から SSL のチェックを無効化しないとオレオレ証明書でエラーを吐いてしまう

Postman を使った API テストのしかた

Postman で API 定義を作成し、試しに「Send」ボタンを押すと、レスポンスが返ってきます。

ここまで終わったら、「Tests」タブを開き、下記のようにテストを書いていきます。

var jsonData = pm.response.json();

pm.test("id is number", function() {
    pm.expect(jsonData.id).to.be.a("number");
});

pm.test("room.id is 1540", function() {
    pm.expect(jsonData.room.id).to.eq(1540);
});

Postman のテストケースはJavaScriptのライブラリであるChaiを利用しているため、JavaScript ベースで書くことができます。

まずjson()メソッドでレスポンスの JSON を取得します。

次にpm.test()でテストケースを作成します。expect()でテストしたい値を JSON から抜き取り、その後は英文を書くかのようにメソッドチェーンで.to.be.aなどとつないでいき、最後に型名か具体的な値を書くことでテストが実行されます。

ここでは.to.beをつけることは必須ではないのですが、そのほうが英文っぽく書けるので好ましいと思っています。それだけです。Rspec 等と思想が似ていると思います。

Postman のテストでは具体的な値等までテストすることは難しいので、基本的にレスポンスの型のみテストすることが大半です。

Collection を作る

さて、API テストをいくつか作成すると、Collection というものにまとめることができます。Collection は、例えば Q&A サイトの場合、「質問したあとに回答がつき、それをベストアンサーにする」といった複数の連続した API のテストをすることが可能です。

テストを実行する

テストの実行は、クライアントアプリケーション上からもできるし、CLI から Node.js 等のコマンドベースでも実行可能です。

Postman を利用するメリットは?

API テストの表現力という意味では PHPUnit で頑張るほうが身の丈に合ってそうですが、例えば検証環境など、HTTP 越しでアクセスしたい場合は Postman でリクエストの向き先を Localhost から検証環境に向けて実行することができるなど、手軽さという意味では Postman に軍配があがるイメージです。

ただ、CI を組み込んだ場合などは結局 PHPUnit を検証環境で実行することなど簡単でしょうから、Postman でテストを頑張るメリットは薄れ、Swagger 等と同様で API の定義を複数の社員間で共有することがメイン目的となるでしょう。

個人的には Passport を使っている環境でも PHPUnit ベースでテストできることが衝撃でした。先の記事を公開した方に感謝です。

まとめ

PHPUnit も Postman も両方 API ベースでのテストの記述が可能ですが、PHPUnit のほうがより細かい制御が可能です。Postman は API 定義をまとめるのに特化して、PHPUnit でできる限りテストを書いていくのが 1 つの良い共存方法かと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelをPHPUnitとPostman でテストする手法まとめ(Passport にも対応)

Laravel で開発されているプロダクトにおいてテストコードを書く方法を、 PHPUnit および Postman を主体にしてまとめます。

テスト作成の背景

弊社(株式会社 NoSchool)では、Web フロントエンドに Nuxt を利用しており、また、iOS ネイティブアプリも開発しているため、Laravel を原則 API ベースで開発することが多いです。

API のテストを作成することは、バグのリスクを低減したり、アプリエンジニアとの仕様の共有のために必須となります。2020 年 1 月現在 NoSchool では PHPUnit および Postman を利用していますので、ドキュメントを兼ねてまとめました。

この記事を読むとわかること

  • PHPUnit を使って API 単位でのテストを書く方法
  • PHPUnit を使って Class 単位での単体テストを書く方法
  • Postman を使って API のレスポンスをテストする方法
  • それぞれの比較と、実運用する際に共存する方法

環境

  • Laravel 5.7(古い...)
  • PHPUnit 7.0
  • Mockery 1.2

PHPUnit

概要

PHPUnit は、PHP のコードベースでテストを記述できるフレームワークです。

PHPUnit でテストを書く目的

PHP のコードベースのため、データベースのサンプルデータを柔軟に生成し、テスト後に破棄すると言ったことが可能になります。そのため、細かいテストケースを実装するのに向いています。

書き方

ディレクトリ構造

弊社ではtestsディレクトリ以下に下記のディレクトリ構造でテストを書いていますので、参考までに。

./Feature 結合テスト(API テスト)を書きます。HTTP のエンドポイント単位でのテストを書きます。API を開発したときは原則必須で対応するテストコードを実装しましょう。

./Unit 単体テストを書きます。クラス単位でのテストを書きますが、工数が多くかかること、設計変更に追従するコスト、および Final Class をモック化できない Mockery の制限などの理由から、あまり実装されることはありません。Rendering まで Laravel で行っている一部ページや、クラス単位でのテストをどうしても書きたい場合はこちらに書きます。

./Fixture テスト用のデータを書きます。アプリ経由の課金で Apple から送信されるレシートなど、細かいデータを表現するときに使います。

./Lib 複数のテストケースの基底となるクラスなどを格納します。

各テストコードの基本

各テストコードは、それぞれのディレクトリ以下にドメインごとに namespace を切って作成してください。また、命名は ◯◯Test.php で作成してください。

ex. ./Feature/Chat/RoomTest.php

また、各テストケースはTests\TestCaseクラスを継承してください。

final class RoomTest extends TestCase

各テストケースの基本

各テストケースは、test◯◯ という名称の関数で作成します。

use Tests\TestCase;

final class RoomTest extends TestCase
{
    protected function setUp() // 後述
    {
        parent::setUp();
    }

    public function testSendMessage()
    {
        // hogehoge
    }

API テスト

一つのテストケースの中で、下記のようなフェーズを順に実行します。

  • テストデータの準備
  • API リクエストの実行
  • レスポンスのステータス、Body のテスト
  • 必要に応じて、変化したデータベースの中身のテスト

テストデータの準備

一般に、各テストケースにおけるテストデータの準備は、setUp()メソッドにて実装します。setUp()は各テストケースの実行直前に呼ばれます。反対に、終わったあとに呼ばれるのはtearDown()です。こちらはデータの後始末などに利用します。

    protected function setUp()
    {
        parent::setUp();

        $this->room = factory(Room::class)->create([
            'user_id' => 1111
        ]);
    }

ここでのポイントは大きく 2 点あります。

まず、parent::setUp()を実行することです。これを実行することで、Laravel アプリケーションを動かすための初期設定が終わります。実行しない場合、例えばconfig()を使った実装等が動かないので必ず実行してください。

詳しい実装は/Illuminate/Foundation/Testing/TestCase.phpに書いてあります。

2 つめのポイントはfactory()グローバルヘルパを利用してテストデータを生成することです。

ファクトリはdatabase/factories以下に作成します。詳しい書き方は実際のコードを読んだり、公式ドキュメントを参照してください。ここで重要なのは以下の 2 点です。

  • factory を create するタイミングでテストデータを Array で渡すことで上書きできる
  • factory 自体に名前をつけることができるため、例えば同じ User Model に対する factory でも違う初期データを持ったインスタンスを作り分けることができる

ファクトリ生成後は、実データがデータベースにインサートされているため、それがある前提で以降のテストを書くことができます。

【補足】テストデータの削除

テストデータを毎回テストのたびに生成していると、データベースがテストデータで溢れかえってしまいます。ここで、DatabaseTransactionsを利用することで当該テストにて作成されたデータは全てテスト終了後に削除されます。

use Illuminate\Foundation\Testing\DatabaseTransactions;

class RoomTest extends TestCase
{
    use DatabaseTransactions;

API リクエストの実行

API リクエストは下記のように実行します。リクエストするメソッドには複数ありますが、個人的にはjsonメソッドが好きです。

$response = $this->json(
    'PUT',
    "/api/chat/{$this->room->id}",
    [
        'body' => 'message
    ],
    $headers
)

【補足】Laravel Passport を利用している場合

Passport を使っていると、都度都度 API リクエストに特定のヘッダを指定しなければなりません。具体的には、Bearerトークンが必要です。

これを解消するための手段を公開してくれている Web ページが有りましたので、ご参照ください。大まかに言えば、setUp()を拡張してそこでUserに対応したアクセストークンを都度都度発行しています。

https://www.whizz-tech.co.jp/1442/

こうすることで、json()実行時の第 4 引数にヘッダーでBearerを渡せば認証済みユーザーとしてテストが実行できます。

また、弊社のネイティブアプリ向けの API の場合、ログアウトユーザーでもクライアント単位でのグラントトークンを Bearer に載せることを必須としているのですが、そのトークンもsetUp()を使って実装できます。/oauth/tokenへ POST を飛ばしてトークンを都度発行すればいいです。

補足: https://laravel.com/docs/5.7/passport#testing に書かれているactingAsを使った実装は何故か動きませんでした

【補足】Cookie 認証の API を利用している場合

Cookie 認証の API を利用していて、かつ認証済みのユーザーで API をリクエストしたいときはactingAsメソッドを利用します。

$response = $this->actingAs($user)
    ->json('PUT', '/api/room', [
    ]);

factory(User::class)->create()でテストユーザーを作成後、そのユーザーで認証された状態で API を叩いた場合のテストなどをするときに有効です。

レスポンスのステータス、Body のテスト

レスポンスのステータスは、下記のようにテストします。

$response->assertStatus(204);

レスポンスの Body は、下記のようにテストします。

$response->assertJson([
    'message_id' => 100
]);

この場合のテストは一例で、具体的な値をテストできるものから、JSON の構造のみテストするものまで色々あります。assertJsonはかなりゆるく、指定したキーを持ってさえいれば、余分なキーが含まれていてもエラーになりません(第 2 引数でより strict にできるようです)。

ここでどうテストするかはテストケースで何をしたいかによるでしょう。ただ、特にネイティブアプリへのレスポンスなど、型にまで気を使わなければいけない場合において、下記のように構造とそれぞれの持っている型を見ることになると思います。

$response->assertJsonStructure([
    'id',
    'user' => [
        'id',
        'name',
    ],
    'created_at',
]);
$this->assertIsInt($response->json('id'));
$this->assertIsInt($response->json('created_at'));
$this->assertIsInt($response->json('user.id'));
$this->assertIsString($response->json('user.name'));

ネストしているキーに、Laravel にあるあるのドット記法でアクセスできるのは便利ですね。

詳しくは公式情報をどうぞ。

https://laravel.com/api/5.7/Illuminate/Foundation/Testing/TestResponse.html
https://phpunit.readthedocs.io/ja/latest/assertions.html#assertisint

必要に応じて、変化したデータベースの中身のテスト

副作用のある API であれば、データベースに変更をもたらすでしょう。

データベースの中身をテストする場合は下記のように記述します。

$this->assertEquals(
    3,
    Room::where('body', 'hogehoge')->count()
);

https://readouble.com/laravel/5.7/ja/database-testing.html を見る限り、assertDatabaseHasというメソッドも用意されています。しかし、この場合はカウントで 2 つ以上のものがあること、というのは確認できませんので、用途に応じて使い分けてください。

このように Eloquent Model を使ってクエリを発行して結果をチェックというのは少々我ながら筋が悪いため、より上手に書ける方法を知っている方は教えていただけると嬉しいです。

単体テスト

単体テストは、前述したとおり結構モック化を頑張らないと厳密な単体テストの実現が難しいです。

もし実装したい場合は、下記のようなコツを念頭に実装してみてください。

ここではユースケース層のテストを例に話します。NoSchool の場合、ユースケース層からは Repository の Interface を呼んでおり、AppServiceProviderで実装クラスにbindingしているという特徴を持っているため、その前提での解説となります。

DI を使っている場合はresolveヘルパでインスタンスを取得

DI している場合は普通にコンストラクタを書けないはずなので、resolveヘルパでインスタンスを取得し、そこからメソッドを実行してテストします。

$sendChatMessageUseCase = resolve(SendChatMessageUseCase::class);

テストしたいクラス内で使っている外部クラスを Mock にしたい場合、DI するのが一番確実っぽい

以下の例はユースケースで使っている Repository をモック化した例です。

$repository = Mockery::mock(new MemberRepository($this->user));
$repository->shouldReceive('findMember')->once()->andReturn(
    $member
);
$repository->shouldReceive('createMember')->once()->andReturn($member);

$this->app->instance(
    'App\Infrastructure\MemberRepository',
    $repository
);

final クラスをモック化するときはalias:記法を使う

final なクラスをモックにするときはalias:を先頭につけて namespace を記述します。なんじゃこりゃ。まあ、そもそも final なクラスをモックにする意味あるのかって話なのですが。

$requestStub = \Mockery::mock('alias:App\Helpers\HttpClient');

といったややこしい仕様が散見されるので、特にfinalなクラスを使うのをやめるか、\Mockeryを使った単体テストはあまり書かないようにするか、というのが工数的なデメリットを鑑みると妥当かなというのが感想です。より良いライブラリや、Mockery でももっと楽に書けるぞ、という情報提供お待ちしております。

テストの実行

テストの実行は、./vendor/bin/phpunitコマンドで実行します。ディレクトリをオペランドに指定することも、ファイルを指定することも、何も指定せずに全てをテストすることも可能です。

[mejileben]$ ./vendor/bin/phpunit ./tests/Feature/Room

上記のコマンドでは、Roomディレクトリ以下のテストケースを全て実行します。

コマンドを覚えたくない方は、composerartisanコマンドとしてエイリアスを貼ってもいいでしょう。

Postman

概要

Postman は、API のドキュメントを書いたり、テストケースを書いてまとめて複数の API をテストできるサービスです。

Swagger のテスト実行機能がついたようなイメージです。Swagger を使ったことがないのでイメージですが。

Postman を導入する目的

Postman を導入することで、API ドキュメントをネイティブアプリエンジニアに Web 経由で共有することができます。NoSchool では API 定義を JSON でエクスポートし、Git 管理しています。これを各自が Import することで API 定義を確認できます。有料プランに加入することで複数人が Web 上で共有を完結できるそうですが、ケチっているのでまだ無料プランです。

Postman 環境設定の概要

詳しくは割愛しますが、主に以下の点に注意が必要です。

  • 秘匿情報は Environment として管理し、GitHub には上げない。または暗号化する
  • ローカル環境で HTTPS を利用しているとき、設定画面から SSL のチェックを無効化しないとオレオレ証明書でエラーを吐いてしまう

Postman を使った API テストのしかた

Postman で API 定義を作成し、試しに「Send」ボタンを押すと、レスポンスが返ってきます。

ここまで終わったら、「Tests」タブを開き、下記のようにテストを書いていきます。

var jsonData = pm.response.json();

pm.test("id is number", function() {
    pm.expect(jsonData.id).to.be.a("number");
});

pm.test("room.id is 1540", function() {
    pm.expect(jsonData.room.id).to.eq(1540);
});

Postman のテストケースはJavaScriptのライブラリであるChaiを利用しているため、JavaScript ベースで書くことができます。

まずjson()メソッドでレスポンスの JSON を取得します。

次にpm.test()でテストケースを作成します。expect()でテストしたい値を JSON から抜き取り、その後は英文を書くかのようにメソッドチェーンで.to.be.aなどとつないでいき、最後に型名か具体的な値を書くことでテストが実行されます。

ここでは.to.beをつけることは必須ではないのですが、そのほうが英文っぽく書けるので好ましいと思っています。それだけです。Rspec 等と思想が似ていると思います。

Postman のテストでは具体的な値等までテストすることは難しいので、基本的にレスポンスの型のみテストすることが大半です。

Collection を作る

さて、API テストをいくつか作成すると、Collection というものにまとめることができます。Collection は、例えば Q&A サイトの場合、「質問したあとに回答がつき、それをベストアンサーにする」といった複数の連続した API のテストをすることが可能です。

テストを実行する

テストの実行は、クライアントアプリケーション上からもできるし、CLI から Node.js 等のコマンドベースでも実行可能です。

Postman を利用するメリットは?

API テストの表現力という意味では PHPUnit で頑張るほうが身の丈に合ってそうですが、例えば検証環境など、HTTP 越しでアクセスしたい場合は Postman でリクエストの向き先を Localhost から検証環境に向けて実行することができるなど、手軽さという意味では Postman に軍配があがるイメージです。

ただ、CI を組み込んだ場合などは結局 PHPUnit を検証環境で実行することなど簡単でしょうから、Postman でテストを頑張るメリットは薄れ、Swagger 等と同様で API の定義を複数の社員間で共有することがメイン目的となるでしょう。

個人的には Passport を使っている環境でも PHPUnit ベースでテストできることが衝撃でした。先の記事を公開した方に感謝です。

まとめ

PHPUnit も Postman も両方 API ベースでのテストの記述が可能ですが、PHPUnit のほうがより細かい制御が可能です。Postman は API 定義をまとめるのに特化して、PHPUnit でできる限りテストを書いていくのが 1 つの良い共存方法かと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CircleCI MySQL8.0 認証方式の変更

CircleCIの設定でLaravelのマイグレーションが上手くいかずにハマりました、、、

環境

mysql ver 8.0

エラー

マイグレーションStep時にエラーになります

#!/bin/bash -eo pipefail
php artisan migrate

In Connection.php line 669:

  SQLSTATE[HY000] [2054] The server requested authentication method unknown t  
  o the client (SQL: select * from information_schema.tables where table_sche  
  ma = circle_test and table_name = migrations and table_type = 'BASE TABLE')  


In Connector.php line 70:

  SQLSTATE[HY000] [2054] The server requested authentication method unknown t  
  o the client                                                                 


In Connector.php line 70:

  PDO::__construct(): The server requested authentication method unknown to t  
  he client [caching_sha2_password]                                            



Exited with code exit status 1

原因

MySQL8.0.4 デフォルトの認証方式変更

MySQL8.0.4以降のログイン認証方式はcaching_sha2_passwordがデフォルトであり、
接続ライブラリがcaching_sha2_passwordに未対応のため接続不可

参考: MySQL8.0 認証方式を変更する(Laravel5)

解決策

.circleci/config.ymlcommand: [--default-authentication-plugin=mysql_native_password]追記

.circleci/config.yml
docker:
  - image: circleci/mysql:8.0
    command: [--default-authentication-plugin=mysql_native_password]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerで始めるLaravel講座 - Docker編

はじめに

気づけばオリンピックイヤーである2020年がしれっと始まりましたが皆様いかがお過ごしでしょうか。
さて、今日は「Dockerで始めるLaravel講座」のDocker編です。
今日はサクサクっとどうやってDockerを使うのかを学んでいきましょう。


アジェンダ

今日はこんな順番でお話していきます。

  1. Dockerをはじめてみよう
  2. docker-composeでより本番に近づけてみよう

Dockerをはじめてみよう


Kitematicを入れてみよう

Dockerがまずピンときてないときは、まずはピンときてしまうことです。
そのためにDockerには Kitematic(カイトマティック) という素晴らしいGUIダッシュボードツールがあります。

最新版はこちらからダウンロードできます。
https://github.com/docker/kitematic/releases


なんとかかんとかインストールして開いてみるとこんな画面になると思います。
image.png


せっかくなので、ここからApache(httpd)でも立ち上げてみましょう。
Search for Docker images ~ と書かれている検索ボックスに httpd と入れてみましょう。
するとこんな感じになると思います。
image.png


では、ここで左上のofficial httpdと書かれているボックスの右下にあるCREATEをクリックしてみましょう。
すると何やらダウンロードを始めて、謎のコンソール画面が出てくると思います。


そう、何を隠そう、これで実はApacheのDockerコンテナが立ち上がっている状態なのです。
試しに右上のHome Settings と書かれているタブをSettingsに切り替えてから中のタブをHostname/Portsに切り替えてConfigure Portslocalhost:xxxxと書かれているところをクリックしてみましょう。


こんな感じです。
image.png


するとブラウザにIt works!と表示されているはずです。
おめでとう! Apacheが立ち上がっていますね!


せっかくApache立ち上がったのでApacheの中に入ってみましょうか。
Kitematicに戻ってみると EXEC って書いてあるところがあるのでポチーしてみましょう。
image.png


するとなにやら黒い画面が現れてきます。
そう、これでDockerで立ち上げた仮想環境の中に入っているのです。

せっかくなので、入ってる気分を体験してみましょう。


こちらのコマンドを順に打ち込んでみてください。

$ apt update
$ apt install -y vim
$ cd htdocs
$ vi index.html

すると、さっき表示していた It Works! の文字を表示していたHTMLファイルが開けます。
こいつをちょちょいと弄ってみてさっきのブラウザをリロードしてみましょう。

表示、変わりましたか?


ではもう用済みなので、このコンテナは消してやりましょう。
Kitematicのここをクリックしてみてください。
image.png


ローカルのファイルをDockerコンテナに反映させよう

コンテナでただ遊ぶ分にはさっきの方法でもいいのですが、ちゃんと開発で使おう!と思うといちいちコンテナの中に入ってvimでファイルを開いて編集……というやり方でやってたら全然効率がよくありません。
やっぱ「Dockerやめて素直にXAMPP使うぜ」になってしまいます。


じゃあどうしたらいいのか。
VirtualBoxとかVagrantで仮想環境を使った開発をしたことある人ならもうピンときていると思いますが「共有フォルダ」機能を使えばいいのです。
ちゃんとDockerにもありますよ!
ただこれ、Kitematicからは設定できないので、コマンドラインから実行する必要があります。


Kitematicの画面の左下に DOCKER CLI と書かれたボタンが有るの分かりますか? そこをポチーしてみましょう。
image.png


実行する環境によって出てくるものが若干違うと思いますが、これまた黒い画面が現れてきます。
Windowsならコマンドプロンプトが、Macならターミナルの画面が出てきてると思います。
多分なんとなくわかったと思いますが、実はKitematicなど使わなくてもDockerは使えます。

あ、知ってましたかね?


ではデスクトップに docker-test というフォルダを作って中に index.html を作ってみてください。
index.htmlの内容は何でもいいです。
HTMLの書き方がわからなければ下をコピペしてください。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Test</title>
</head>
<body>
    やったねたえちゃん、家族が増えたよ
</body>
</html>

できたらターミナルの画面でなんとかかんとかデスクトップまでたどり着いてください。
そこで下記コマンドを入力してみてください。

docker run -v $(pwd)/docker-test:/usr/local/apache2/htdocs -d -p 8080:80 httpd

今度はブラウザで http://localhost:8080 を開いてみてください。
さっき作ったやつになりましたよね?


docker-composeでより本番に近づけてみよう


そもそもなんでDockerを使うのか

割と肝心の話をしてなかったような気がしますね。
当たり前なのですが、Web開発におけるローカル開発環境の手段はDockerだけではありません。
大まかには以下の3通りのどれかになるかと思います。

  • ローカルのOSにApache/MySQL/PHP(Perl)などのミドルウェアを直接入れて開発する
  • VirtualBoxなどのVM型仮想環境を使う
  • Dockerなどのコンテナ型仮想環境を使う

それぞれにメリット・デメリットはあるのですが、大体のケースに置いてDockerはおすすめです。


かんたんにメリット・デメリットまとめるとこんな感じですかね。

方法 メリット デメリット
直ミドルウェア(XAMPPなど) かんたん(何なら最初から入ってることも) 環境が汚れてしまうので複数案件まわせない。たくさん建てるのは無理
VM(Vagrantなど) どこでも安定して動く。 おもい。たくさん立てるのが大変(できないことはない)
コンテナ(Dockerなど) サクサク立てられる。一度にたくさん立てられる 一部できないことがある

つまり、たくさん立てないといけないときはほぼDocker一択という選択になります。

僕が初めてDockerを仕事で作ったときはこんな構成でした。

image.png
APIサーバーと管理画面サーバーが同じDBにアクセスする構成ですね。


こんな感じの構成をちゃちゃっと作るのにはDockerの一機能である docker-compose というのが威力を発揮します。
いや、まあ、 kubernetes とかもあるっちゃあるのですが、それはまたどこかでやりましょう。


ではさっき作ったtest-dockerフォルダの中にこんな内容の docker-compose.yml を作ってみてください。

version: '2.1'
services:
  mysql:
    image: mysql:5.6
    volumes:
      - './mysql/var/log/mysql:/var/lib/mysql'
    ports:
      - '4306:3306'
    environment:
      MYSQL_ROOT_PASSWORD: rootPass11111111
      MYSQL_DATABASE: laratest
      MYSQL_USER: larauser
      MYSQL_PASSWORD: larapass
  api:
    image: httpd
    links:
      - mysql
    ports:
      - '8081:80'
    volumes:
      - './api:/usr/local/apache2/htdocs'
  admin:
    image: httpd
    links:
      - mysql
    ports:
      - '8082:80'
    volumes:
      - './admin:/usr/local/apache2/htdocs'

できたらこんどは、apiとadminディレクトリを作ってそこにindex.htmlをおいてみましょう。
内容はちょこっとだけ変えたほうが良いですね。

image.png
こんな感じになりましたか?


では、docker-composeを起動してみましょう。

こちらのコマンドを打ち込んでみてください。

$ docker-compose up -d

すると楽しいことが起きていると思います。


Kitematicを見てみると一気に3つコンテナが立ち上がってるのがわかると思います。
こうすることによってマルチコンテナ構成を簡単に作れるのです。


では立ち上げたコンテナ郡を一気に消してみましょう。

$ docker-compose down

次回はこのDocker環境を使ってLaravelのアプリケーションを作っていきたいと思います。

おつかれさまでした!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel マイグレーションerror(自分用メモ)

Laravelのマイグレーションするときにおこったエラーを書きます(自分用メモ)

エラー内容

$ php artisan migrate 
 Illuminate\Database\QueryException  : SQLSTATE[HY000] [2054] The server requested authentication method unknown to the client (SQL: select * from information_schema.tables where table_schema = forge and table_name = migrations)

原因

Laravelの.envファイルで指定したmysqlのデーターベース名、user名が、
自分のmysqlになかったorz...

解決法

user && データベースを作成

まずmysqlにrootでログインしてユーザー名とそのユーザーのパスワードを指定

mysql -uroot -p(自分で設定したパスワード)

mysql> create user 'forge' identified by '(お好きなパスワード)';

次に作ったユーザーに権限を与える

grant all on forge. * to 'forge';

mysqlを抜ける

exit;

次に作ったuserにログイン

mysql -uforge -pお好きなパスワード;

データベース作成

create database forge;

ユーザ認証方式を変更

mysql> SELECT user, host, plugin FROM mysql.user;
+------------------+-----------+-----------------------+
| user             | host      | plugin                |
+------------------+-----------+-----------------------+
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | caching_sha2_password |
| mysql.sys        | localhost | caching_sha2_password |
| root             | localhost | caching_sha2_password |
+------------------+-----------+-----------------------+
これでパスワードを変更
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '好きなパスワード';

最後にenvファイルを変更

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=forge
DB_USERNAME=forge
DB_PASSWORD=(自分のパスワード)

参考文献

MySQL8.0でLaravelのmigrationがエラーになる

Laravel での「php artisan migrate」ができないとき、解決法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SPA案件(Vue.js)を1ヶ月ほどやってみて感じた違和感と知見(個人メモ)

※この記事は、Vue.jsを批判するものではございません。

最近とあるSPA案件(Laravel, Vue)で少しの期間Vue.jsの実装をしたのですが、
そのとき、「その案件でSPAを採用するメリットはあまりない、むしろコスト増なのではないか」
と思ったので、そのアウトプットをしておこうと思います。
なお、内容は薄めなのと、読まれることをあまり意識しておりません。

案件状況

  • 納期まであまり時間がない。
  • でもSPAで構築することは強く心に決めている。

「強く心に決めている」...なぜ

  • 早いと聞いてる。
  • 既存アプリ(Laravel Blade)で、描画にめちゃくちゃ時間のかかるものがある。
  • 既存アプリの一部にVueを組み込んでみた感触が良い感じだった。(実際にメリットがあった。)

SPAのメリット/デメリット

ちゃんと教えてくれる記事たちです。
「それってSPAでやる必要ある?」第1章:パフォーマンスチューニング
シングルページアプリケーション(SPA)の導入メリット&デメリット
SPA(シングルページアプリケーション)ってそもそも何?メリット・デメリットも踏まえて解説!

メリットを自分の言葉で言うと
画面に表示されている部分にのみコストを割くができる
例えば
- データ通信が限定的
- ライフサイクルでレンダリングのタイミングを簡単に調整できる
- 必要部分のみ再描画する

デメリットは、
ビジネスインパクトがあるほど早くならないのでは?
その割に、(Laravel Blade構成と比較して)実装コストが高くない?
開発者少ないよね。(運用/保守コスト高くなるよね。)

自分が考えるVue.jsの実装コストの高さ

フロントに設計の概念が入ることがもっともコストが高いと感じます。
ルーティングやデータの持ち方、コンポーネント設計などを考えなければならないことは、
明らかにこれまでと比べて実装スピードを落とす要因になると考えます。
(逆に楽しさでもありますが。)

感じた違和感

メリット、デメリットを把握せず、また、既存アプリの描画が遅い原因を考えず、
SPAにすれば早くなる!というのは違うと思います。

実際、当案件についても、
工期が短い。
Vue.jsを使える開発者が外部エンジニアのみ。
既存アプリが遅いのは、5年前くらいに書かれ、誰のレビュー受けていない、何万行のJSコードの問題である可能性が高く、SPAうんぬんの問題ではない。

という部分から、Vue.jsでの開発に違和感を感じました。

メリットを享受できる部分に絞って、考えて採用すべき

デメリットもあるので、ちゃんと考えて導入すべき。
また部分的にVueの導入を検討する場合でも、jQueryでサクッと片付く場合はそっちの方が実装/保守コストが低い可能性もあると思います。
Laravelではhtmlを返すこともできますしね。

参考: jQueryとAjaxとLaravelでページネーションを実装する

一方で、得られた知見

Vue.jsはコードをシンプルに保てるようになっており、その点はすごく良いと感じました。

開発者的メリット

  • HTML, JavaScript, CSSを1つのファイルに記述するというのがシンプルで良い
  • scopedが便利
  • コードが複雑化しにくい

そして思ったのです。
これって普通のアプリケーションにある程度応用できそう。

実際に考えてみる

ちょっと考えてみました。方針としては、

  • 仕様に応じてBladeのディレクトリ構成をシンプルに考える
  • JavaScript, CSSはBladeのディレクトリ構成に合わせる。
  • 影響範囲は対応するBladeファイル内にとどまるように気をつける。

Bladeファイルのディレクトリ構成

サンプルです。実際には画面仕様などにより異なるはずです。

  • ルール
    • views/直下は、layouts/および、メニューに対応するディレクトリ(menus/を作ろうと思ったが階層が深くなりすぎるのが嫌なので却下)
    • メニューごとのディレクトリには、ページに対応するbladeファイルがある
    • メニューごとのディレクトリには、components/があり、ページを構成する部品を格納する。
    • formやbuttonなどの部品はparts/に格納する。
    • ページに対応するファイルからは、components/配下のファイルをincludeする。
    • components/配下のファイルからは、parts/配下のファイルををincludeする
    • ページに対応するファイルから、parts/配下の部品をincludeしない
    • 同階層のファイルをincludeしない
resources/
- views/
    - layouts/
        - app
        - components/
            - header
            - footer
    - home/
        - index
        - components/
            - eye_catching
            - new_product
    - product/
        - index
        - show
        - edit
        - components/
            - product
            - details
    - account/
        - show
        - edit
        - components/
            - table
    - parts/
        - forms/
            - radio
            - checkbox
        - buttons/
            - search
            - submit
            - delete
        - flash_message

JavaScriptとCSS(Sass)は、Bladeファイルと同じディレクトリ構成

JavaScript, CSS(Sass)はBladeファイルと同じディレクトリ構成とし、
sectionディレクティブを利用しつつ、対応するBladeファイルに記述するようにします。

resources/
- js
    - modules/
        - confirm
    - layouts/
        - app
        - components/
            - header

- sass
    - layouts/
        - app
        - components/
            - header

!!!この辺りの説明が不十分なので今夜修正します。!!!

home/index.blade.php
@section('css')
  <script src={{ mix('/js/home/index.js') }}>
@endsection

@section('js')
  <script src={{ mix('/js/home/index.js') }}>
@endsection

なお、CSSは原則そのファイルにしかスタイルが適用されないよう、
スコープを限定するように記載します。

home/index.scss
// home/index.blade.php
// <div class="home-index">
//   <div class="test-class">
//     コンテンツ
//  </div>
// </div>

.home-index { // スコープを限定する
  .test-class {

  }
}

こんな感じでしょうか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 初期設定 メモ

Laravel 初期設定

タイムゾーン変更

laravel/config/app.php
# 70行目付近
'timezone' => 'UTC',

'timezone' => 'Asia/Tokyo',

日本語に言語変更

laravel/config/app.php
#83行目付近
'locale' => 'en',

'locale' => 'ja',
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む