Mockeryでのモックの作り方を調べてみた

この記事は個人ブログの転載になります。

toyo.hatenablog.jp

とある方から、「なんで静的メソッドはモックできないんですか?」ときかれたときに、「そういえば、Mockがどういう原理で動いているかいまいち知らないなー」と思ったので、モックがどのように作られているのかを調べてみました。

僕は普段はMockeryを使用しているので、Mockeryでのモックの生成のされ方をまとめます。

docs.mockery.io

Mockeryは色々な種類のモックが作れるので、今回は一番ベーシックな「スタブやモック」の生成過程を追ってみました。

コードでいうと、以下のような感じのやつ。

<?php
Mockery::mock(Post::class);

以下ではこのコードを例にとって説明していきます。

大まかな流れ

雑にまとめると、以下です。

  1. Postクラスを継承したモッククラスの定義を作成する
  2. 定義したモッククラスをロードする
  3. ロードしたモッククラスのインスタンスを生成する

です。もっと Refrection Class が関わってくるのかなと身構えていたのですが、とてもシンプルでした。

モッククラスの定義

Mockeryではモッククラスごとにクラスの定義を行っています。
つまり、

<?php

// あくまで疑似コードです
class MockedPost {
}

みたいなコードを、都度生成しているということです。

モッククラスのテンプレート

ただし、モッククラスには、 shouldRecieveandReturn などのメソッドが共通して必要です。
そのため、モッククラスの定義のテンプレートのようなものが存在しています。
このテンプレートが、 Mockery\Mock です。

github.com

このクラスの中で shouldRecieve などの定義も書かれています。
つまり、 Mockery::mock(Post::class) で生成したインスタンスの振る舞いは、このクラスの内容を読めばわかるということです。

テンプレートの書き換え

Mockeryではモッククラスごとにクラスの定義を行っているため、クラス名の衝突については考えないといけません。
また、PHPには型が存在しているため、それぞれのモッククラスは、引数で渡されたクラスの型である必要があります。
今回でいうと、生成されたモッククラスは Post 型である必要があります。

なので、テンプレートを書き換える必要があります。
テンプレートのファイルを file_get_contentsで読み込み、文字列として変数に入れます。
その文字列を str_replace などを用いて書き換えていきます。

元のテンプレートは以下のような感じです。

<?php

// ...略

class Mock implements MockInterface
{
    // ...略
}
クラス名の書き換え

クラス名が衝突するとエラーになるため、ユニークなクラス名が必要です。
そのため、テンプレートのクラス名を書き換える必要があります。

クラス名は Mockery_0_Post という命名になります。

0 の部分は連番です。
たとえばモックを2個生成した場合は、以下のようになります。

<?php

// Mockery_0_Post クラスのインスタンス
$post1 = Mockery::mock(Post::class);

// Mockery_1_Commentable クラスのインスタンス
$post2 = Mockery::mock(Commentable::class);

よって、クラス定義は以下のような状態になります。

<?php

// ...略

class Mockery_0_Post implements MockInterface
{
    // ...略
}
型情報の追加

生成されたモッククラスはPost型である必要があります。
ただ、この段階でモッククラスの型は Mockery_0_Post, MockInterface だけです。
そのため、Postクラスを継承することで解決します。
つまり、ドキュメントにも書いてありますが、 final キーワードのついているクラスのモックは(継承できないので)作成できません。1

クラス定義は以下のような状態になります。

<?php

// ...略

class Mockery_0_Post extends Post implements MockInterface
{
    // ...略
}
メソッド呼び出しの抑制

継承をすることで型を付与することはできますが、問題がひとつ残ります。
それは、Postクラスのメソッドが継承されてしまうということです。

たとえば、PostクラスにgetTitle()というメソッドが存在していたとして、Postをモックしたインスタンスを呼ぶと、そのままPost::getTitle()が呼ばれてしまいます。
別の挙動に差し替えるためにモックを使いたいので、これではモックの意味がありません。

モッククラスの定義に、Postで定義されているすべてのメソッドを再定義(オーバーライド)することで、この問題に対応します。
Postで定義されているすべてのメソッドの取得は、Postクラスのリフレクションクラスインスタンスを作成し、そのインスタンスgetMethods メソッドを使用することで実現できます。

https://www.php.net/manual/ja/reflectionclass.getmethods.php

ちなみに、メソッドの内容はほぼ固定で、以下が入ります。

<?php

$argc = func_num_args();
$argv = func_get_args();
$ret = $this->_mockery_handleMethodCall(__FUNCTION__, $argv);
return $ret;

最終的に、クラス定義は以下のようになります。

<?php

// ...略

class Mockery_0_Post extends Post implements MockInterface
{
    // ...略
    
    public function getTitle(): string {
        $argc = func_num_args();
        $argv = func_get_args();
        $ret = $this->_mockery_handleMethodCall(__FUNCTION__, $argv);
        return $ret;
    }
}

クラス定義のロード

クラス定義はできましたが、これは現状ただの文字列です。
これをどうにか読み込まないと、クラスとして使えません。

「どうやって読み込むんだろう・・・?」と思っていましたが、

<?php
eval("?>" . $definition->getCode());

github.com

のように、evalを使用して読み込んでいる感じでした。

PHPevalのドキュメントには

PHP 開始タグを含めてはいけません。

とありますが、クラス定義の先頭はPHP開始タグで始まっているので、 ?> を先頭につけて相殺しているようです。

モックインスタンスの生成

さて、モッククラスの定義をし、定義の読み込みもできたので、あとはインスタンスを生成するだけです。

ただし、Postクラスにコンストラクタが宣言されていた場合を考えないといけません。

<?php

class Post
{
    private string $title;

    public function __construct(string $title)
    {
        $this->title = $title;
    }
    
    public function getTitle(): string
    {
        // ...略
    }
}

このような場合、モッククラスはPostクラスを継承しているので、インスタンスを生成するためには $title が必要になります。
モックとしては、コンストラクタの処理は実行させたくない場合が多いと思います。
また、その際はそもそも引数を渡す意味もなく、引数の生成のためのコード2も書きたくないですよね。
なので、コンストラクタを呼ばずにインスタンスを生成します。

そのため、モッククラスのリフレクションクラスのインスタンスを作成し、 newInstanceWithoutConstructor メソッドを使用して、コンストラクタ無しにモックインスタンスを作成します。

https://www.php.net/manual/ja/reflectionclass.newinstancewithoutconstructor.php

これで無事にモックのインスタンスを生成できました。

まとめ

僕はまだモックを知らなかった頃は

<?php

$mock = new class extends Post
{
    public function getTitle(): string
    {
        return 'dummy';
    }
};

のように無名クラスを使用して書いていました。

今回は有名なモックライブラリの中を調べてみたわけですが、基本的な発想は同じなんだなと思いました。
簡単な発想にどこまで向き合えるか、が有名ライブラリへの道なのかもしれません。

おまけ1: インターフェースのモック

さて、Postはクラスでしたが、インターフェースのモックの場合がどのようになるかも追ってみました。

<?php

interface Commentable
{
    function writeComment(string $content): void;
}

Mockery::mock(Commentable::class);

基本的にはクラスの場合と同じです。

ただし、クラス定義の部分で extends していた部分は implements になります。

- class Mockery_0_Post extends Post implements MockInterface
+ class Mockery_0_Commentable implements MockInterface, Commentable

その他は大きな違いはありませんでした。

おまけ2: なぜ静的メソッドはモックできないのか

モックの生成過程が見えてくれば、静的メソッドのモックができない理由もみえてきます。

<?php

function getAllPosts(): array
{
    return Post::all();
}

たとえばこのようなコードがあったとします。
Postのモックインスタンスを作成することは可能です。
Postのモッククラスの静的メソッドの内容を差し替えることも可能でしょう。

ただし、モッククラスは Mockery_0_Post クラスであり、 Post クラスではないのです。
つまり、 Mockery_0_Post::all() は自由に定義できても、 Post::all() の内容はそのままです。

そのため、 Post::all() をそのままモックすることはできません。

エイリアスモック

これを解決するために、Mockeryでは「エイリアスモック」という機能があります。

<?php
Mockery::mock('alias:' . Post::class);

このように書くと、モッククラスの定義が以下のように変化します。

- class Mockery_0_Post extends Post implements MockInterface
+ class Post extends \stdClass implements MockInterface

引数で渡したクラス名がそのままモッククラスのクラス名になりました。

当然、すでにPostクラスが読み込まれている場合には、クラス名が衝突してエラーになります。
なので、エイリアスモックは、Postクラスが読み込まれる前に読み込む必要があります。3

つまり、「本物のコードが読み込まれる前に、モックのコードを読ませて、本物のコードが読み込まれないようにしちゃえ」という作戦なわけですね。

エイリアスモックとフレームワーク

エイリアスモックを使えば静的メソッドもモック可能」なわけですが、実コードではなかなか上手くいかないことが多いです。

というのは、たいていのプロダクトではLaravelなどのフレームワークを使用していると思います。
そして、テストコードを書く際も、フレームワークの機能を使用して書く場合が多いでしょう。
そのため、テストの実行時には、フレームワークの初期化などを行われますが、その際に、モックしたいクラスが読み込まれてしまうことが多いのです。
たとえばLaravelの場合だと、サービスプロバイダによってユーザが定義したクラスが読み込まれます。
その場合、個々のテストケースに処理が渡ってきたときにはすでにモックしたいクラスが読み込まれてしまっているため、 エイリアスモックを使えないことが多いのです。

エイリアスモックという手段は存在しているものの、「基本的には静的メソッドはモックできない」と考えたほうが良いかもしれません。


  1. ドキュメントにもありますが、finalキーワードがついている場合は プロキシパーシャルモック を作成できます。

  2. 今回はstringなので生成が楽でいいのですが、オブジェクトを引数に取る場合は、実引数に指定するオブジェクトを事前に生成する必要があり、コードが冗長になります。

  3. ここらへんの挙動についてはPHPのオートロードについて理解しておく必要があります。

php-fpmのアクセスログにリクエストされたURIを書き込む

backcheck事業部の前田です。
今回は簡単なTips程度の話です。
結論だけ見たい人は結果の見出しへ

問題

私はよくnginx+php-fpmの構成で、Laravelを使います。
その際に、php-fpm側でもアクセスログを出しているのですが、デフォルトの設定だと以下のようになります。

172.18.0.7 -  31/Jul/2020:14:48:45 +0900 "GET index.php" 200
172.18.0.7 -  31/Jul/2020:14:48:47 +0900 "GET index.php" 200
172.18.0.7 -  31/Jul/2020:14:48:58 +0900 "PATCH index.php" 204

まぁ、すべてのリクエストを意図的にindex.phpに集めてるから、そうなるよねー・・・・という話です。
ただ、実際の運用では「どのファイルにアクセスしたか」よりも、「どのURIでアクセスしたか」が知りたいと思います。
なので、ここの index.php の部分を実際にリクエストされたURIに変更したいと思います。

アクセスログのフォーマット指定

さて、URIをログに書き込むためには、ログのフォーマット形式を変更する必要がありそうです。
php-fpmのアクセスログのフォーマット指定は、php-fpmの設定ファイルで指定できます。

https://www.php.net/manual/ja/install.fpm.configuration.php#access-format

ここで、ログ形式のデフォルト値は

"%R - %u %t \"%m %r\" %s"

と書かれています。が、なんと肝心の各記号の意味が書かれていません!!
色々と探してみると、php-fpmのデフォルトの設定ファイルの中に書かれているようでした。

github.com

説明が大量にあるので、デフォルトのログの部分の説明だけ拾ってみます。

; %R: remote IP address
; %u: remote user
; %t: server time the request was received
;   it can accept a strftime(3) format:
;   %d/%b/%Y:%H:%M:%S %z (default)
;   The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag
;   e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
; %m: request method
; %r: the request URI (without the query string, see %q and %Q)
; %s: status (response code)

・・・ということで、 %rindex.php に該当していることがわかります。
ここを、リクエスURIに置き換えれば良さそうです。

リクエスURIの取得

置き換える場所がわかったので、リクエスURIはどのように取得すれば良いかを調べます。
先ほどの各記号の説明をざっと読んでもそれらしきものは見当たらなかったのですが、じっくり読むと、以下が使えそうです。

; %e: an environment variable (same as $ENV or $SERVER)
;   it must be associated with embraces to specify the name of the env
;   variable. Some examples:
;   - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e
;   - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e

リクエスURI$_SERVER['REQUEST_URI'] に入っているので、 %{REQUEST_URI}e と書けば良さそうです。

結果

まとめると、以下のような修正を行いました。

[www]
;access.format = "%R - %u %t \"%m %r\" %s"
access.format = "%R - %u %t \"%m %{REQUEST_URI}e\" %s"

そしてログを確認すると・・・

172.18.0.7 -  31/Jul/2020:14:48:45 +0900 "GET /api/posts" 200
172.18.0.7 -  31/Jul/2020:14:48:47 +0900 "GET /api/posts/5/comments" 200
172.18.0.7 -  31/Jul/2020:14:48:58 +0900 "PATCH /api/posts/5/comments/12" 204

無事にアクセスログにリクエスURIを残せました!!🎉🎉🎉

GASのWebアプリケーションでGoogleDriveのフォルダ一覧をSelectBoxで出力する

CTO室情報システム担当の吉澤です!

GoogleDriveを操作をする際に、SelectBoxの項目を動的にDrive内からとってくると視覚的に扱いやすいと思い、GASのWebアプリケーションを利用したツールを作成しました。

公式のHtmlServiceクラスのcreateHtmlOutput()を使うと表示できたので、メモとして残します。

developers.google.com

今回はこのマイフォルダの中にあるフォルダのリストをWebアプリケーション上のSelectBoxで選択できるようにします。

f:id:wakanayoshizawa:20200715233123p:plain

Webアプリケーションを開いた時はfunction doGet()を実行します。 createHtmlOutput()で全てのHTMLを上書きしてしまうため、 html内の特定のワードをreplaceしたものを引数として渡します。 今回は<putFolderSelectBox>に生成したSelectBoxのHTMLを渡して表示します。

コード.gs

function doGet() {
  return HtmlService.createHtmlOutput(this.setSelectBox());
}

function setSelectBox(){
  var html = HtmlService.createHtmlOutputFromFile('index').getContent();
  var lists = this.ls();
  var select = '<select name="folderId">';
  for (list of lists){
    select += '<option value="' + list[0] + '">' + list[1] + '</option>';
  }
  select += '</select>';
  return html.replace('<putFolderSelectBox>', select);
}

function ls() {
  var currentFolder = DriveApp.getFolderById('フォルダID');
  var folders = currentFolder.getFolders();
  var files = [];
  while(folders.hasNext()) {
    var folder = folders.next();
    files.push([folder.getId(), folder.getName()]);
  }
  return files;
}

DriveApp.getFolderById();に入れるフォルダIDは、Driveもしくはフォルダにアクセスし、URLからコピペします。

f:id:wakanayoshizawa:20200715232021p:plain

getFolders()でフォルダのListが取れるので、hasNext()で回し、ファイルのIDと名前を取得し、 SelectBoxのHTMLのValueと選択肢に当てはめていきます。

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <putFolderSelectBox>
  </body>
</html>

HTML内のReplaceしたいところに<putFolderSelectBox>を入力します。

f:id:wakanayoshizawa:20200715232032p:plain

公開からWebアプリケーションとして導入を選択します。

このようにフォルダ一覧がSelectBoxとして表示されました。

f:id:wakanayoshizawa:20200715232052p:plain

f:id:wakanayoshizawa:20200715232102p:plain

Selectで選択したものを受け取る場合は、 Formで囲んで、action先はWebアプリケーションのURLに、 コード.gs内でdoPost() を用意すると、引数で受け取ることができます。

GASはまだ慣れていないのですが、Driveの情報をHTMLで視覚的に表現ができるのは便利でいいですね👍

Laravelのコンポーネントクラス/Bladeコンポーネントタグを使ってビューを構築する

こちらのブログは個人ブログと同じ内容です

www.ritolab.com


Laravel ではビューの構築に Blade が利用できテンプレート等を定義できますが、Laravel 7 から、コンポーネントクラスや Blade コンポーネントタグが利用できるようになりました。

従来の実装

今回は、会員ページのトップページを想定して、ステージ制のポイントプログラムを表示する部分のビューを作っていきたいと思います。 まずは一連の処理と、従来とおりに簡単なビューを構築していきます。

app/Http/Controllers/TopController.php
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Enums\Stage;
use App\Models\User;

class TopController extends Controller
{
    public function index()
    {
        // ユーザー情報を取得したつもり
        $user = new User([
            'name'  => 'Test User',
            'point' => 9613,
        ]);

        // ポイントステージのクラス
        $stage = Stage::getStage($user->point);

        return view('top', ['user' => $user, 'stage' => $stage]);
    }
}

メインはビューのため諸々端折っていますが、ユーザの情報を取得して(という想定)、さらにポイントプログラムのステージインスタンスを取得して、それらをビューに渡しています。

routes/web.php
Route::get('/top', 'TopController@index');

次に、ビューを作成します。まずはレイアウト部分、共通のテンプレートです。

resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Sample App</title>
</head>
<body>
    <div class="container">
        @yield('content')
    </div>
</body>
</html>

そして、今回のメインとなるトップページの Blade テンプレートです。

resources/views/top.blade.php
@extends('layouts.app')

@section('content')
    <h2>こんにちは、{{$user->name}} さん</h2>

    <div class="point_stage">
        <h3>会員情報</h3>
        <table>
            <tbody>
            <tr>
                <td>ステージ</td>
                <td><span class="text_strong">{{$stage->value}}</span> 会員</td>
            </tr>
            <tr>
                <td>現在のポイント</td>
                <td><span class="text_strong">{{$user->point}}</span> pt</td>
            </tr>
            </tbody>
        </table>
        @if ($stage->getNextStage())
            <p>あと {{$stage->getRemainingPoints($user->point)}} ポイントで {{$stage->getNextStage()}} 会員です</p>
        @endif
    </div>
@endsection

渡ってきた User や Stage から、現在のポイントやステージを表示したり、次のステージやそのために必要なポイント数を取得して表示しています。

ちなみにブラウザからアクセスすると以下のように表示されます。

f:id:ro9rito:20200713184248p:plain

レイアウトの content の部分にこのメッセージと会員情報が挿入されるわけですが、ここの会員情報部分をコンポーネント化していきます。

コンポーネントクラスと Blade コンポーネントの作成

コンポーネントクラスを作成します。以下の artisan コマンドを実行します。

# コンポーネントクラス作成
php artisan make:component PointStage

artisan コマンドを実行すると、以下のファイルが作成されます。

  • app/View/Components/PointStage.php

  • resources/views/components/point-stage.blade.php

app
├─ View
    └─ Components
        └─ PointStage.php

resources
├─ views
    ├─ components
    │   └─ point-stage.blade.php
app/View/Components/PointStage.php
<?php

namespace App\View\Components;

use Illuminate\View\Component;

class PointStage extends Component
{
    /**
     * Create a new component instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the view / contents that represent the component.
     *
     * @return \Illuminate\View\View|string
     */
    public function render()
    {
        return view('components.point-stage');
    }
}
resources/views/components/point-stage.blade.php
<div>
    <!-- Knowing is not enough; we must apply. Being willing is not enough; we must do. - Leonardo da Vinci -->
</div>

コンポーネントクラス

コンポーネントクラスを実装します。会員情報コンポーネントに必要なデータを受け取り、描画する為に必要なデータを作成するイメージです。

app/View/Components/PointStage.php
<?php

declare(strict_types=1);

namespace App\View\Components;

use App\Enums\Stage;
use App\Models\User;
use Illuminate\View\Component;

class PointStage extends Component
{
    /** @var int 現在のポイント */
    public $currentPoint;

    /** @var string 現在のステージ */
    public $currentStageName;

    /** @var string|null 次のステージ名 */
    public $nextStageName;

    /** @var int|null 次のステージアップに必要なポイント */
    public $pointsNeededForNextStage;

    /**
     * Create a new component instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $stage = Stage::getStage($user->point);

        $this->currentPoint             = $user->point;
        $this->currentStageName         = $stage->value;
        $this->nextStageName            = $stage->getNextStage();
        $this->pointsNeededForNextStage = $stage->getPointsNeededForNextStage($user->point);
    }

    /**
     * Get the view / contents that represent the component.
     *
     * @return \Illuminate\View\View|string
     */
    public function render()
    {
        return view('components.point-stage');
    }
}

User オブジェクトを受け取って、それぞれ描画に必要な値をセットしています。

これまでコントローラや blade テンプレート側で行っていた、値に対する処理や取得がここに集約されている事が確認できると思います。

また、コンポーネントクラスで Stage クラスを取得しているので、これまでコントローラから渡していた Stage オブジェクトが不要になったので削除しました。

app/Http/Controllers/TopController.php
public function index()
{
    // ユーザー情報を取得したつもり
    $user = new User([
        'name'  => 'Test User',
        'point' => 9613,
    ]);

    return view('top', ['user' => $user]);
}

Blade コンポーネント

Blade の方も実装していきます。基本的には top.blade.php に記述していたものをこちらに移植する形になります。

resources/views/components/point-stage.blade.php
<div class="point_stage">
    <h3>会員情報</h3>
    <table>
        <tbody>
        <tr>
            <td>ステージ</td>
            <td><span class="text_strong">{{$currentStageName}}</span> 会員</td>
        </tr>
        <tr>
            <td>現在のポイント</td>
            <td><span class="text_strong">{{$currentPoint}}</span> pt</td>
        </tr>
        </tbody>
    </table>
    @if ($nextStageName)
        <p>あと {{$pointsNeededForNextStage}} ポイントで {{$nextStageName}} 会員です</p>
    @endif
</div>

以前の blade テンプレートと違うのは、値が変数に置き換わった事です。

コンポーネントクラスでセットしたメンバ変数はそのままこちらで使用できるので、シンプルに変数を当ててやればよくなります。

Blade コンポーネントタグ

コンポーネントの実装ができたので、これを使用します。<x-component-class-name /> という記法(コンポーネントクラス名をケバブケースにして x- のプレフィックスをつけた名前のタグ)で Blade コンポーネントタグを使用します。

resources/views/top.blade.php
@extends('layouts.app')

@section('content')
    <h2>こんにちは、{{$user->name}} さん</h2>

    <x-point-stage :user="$user" />
@endsection

:user="$user" で、User オブジェクトをコンポーネントクラスへ渡していて、これがコンポーネントクラスのコンストラクタに入ってきます。

単純にコンポーネント化しただけですが、スッキリしました。

あとは再度ブラウザから画面を表示させてみれば、はじめと同じ表示がされ、コンポーネント化の完成です。

メールのビューにコンポーネントクラスを適用させる

画面表示についてはコンポーネントクラスと Blade コンポーネントタグを用いての実装が行えました。では、メールのビューではどうでしょうか。

app/Notifications/UserStageNotification.php
<?php

declare(strict_types=1);

namespace App\Notifications;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class UserStageNotification extends Notification
{
    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  User $user
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail(User $user)
    {
        return (new MailMessage)
            ->from(config('mail.from.address'))
            ->subject('現在のステージをお知らせします')
            ->markdown('mail.user-stage', [
                'user' => $user
            ]);
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}

Notification クラスから email を送信します。メッセージは Markdown で記述します。

resources/views/mail/user-stage.blade.php
@component('mail::message')
# {{$user->name}} 様

いつもご利用いただき誠にありがとうございます。

現在のステージをお知らせいたします。

<x-point-stage :user="$user" />

詳細はメンバーページよりご確認いただけます。
@component('mail::button', ['url' => ''])
ログイン
@endcomponent

@endcomponent

Blade コンポーネントタグを設置しました。

メールを受信してみると、問題なくコンポーネントタグが展開されている事が確認できました。 f:id:ro9rito:20200713185113p:plain

まとめ

コンポーネントクラスを用いると、ビューへ表示する為の値の処理がまとめられるので全体的に整理されていい感じです。

また、ビューへ渡す前の処理で完全に値を整形し切ったりもできますが string とか int にならざるを得ないので、コンポーネントクラスへオブジェクトを渡すことで、コンポーネント単位で利用する情報の制約(型)がつけられるのは良いなと思います。

使い勝手もなかなか良いのでぜひ使ってみてください。

[Github] サンプルソース

振り返り方法の紹介

backcheck事業部の前田です。

backcheck開発チームでは2週間ごとに振り返りを実施しています。
わりといい感じなので、手法と気をつけているポイントを紹介します。

・・・と言いつつも、特に凄いことをやっているわけではなく、とても一般的な方法です。

振り返りの目的

詳細な話を始める前に、振り返りの目的をまとめておきましょう。

なぜ振り返りをするのでしょうか。
それは、プロセスや文化、組織をより良くするためです。
では、なぜプロセスや文化、組織を良くしないといけないのでしょうか。
それは、プロダクトの価値は、プロセスや文化、組織に反映されるからです。

ソフトウェアの価値は「技術力」によってのみ支えられていると、エンジニアは盲信しがちです。
それは間違っていないのですが、プロセスも同等に重要です。
たとえば誰も要らない機能を「とてもすごい技術力」で作ったとしても、プロダクトの価値は全く上がらないでしょう。
むしろ価値としては下がることもあるでしょう。

また、技術力は抜きにして、無駄な作業を減らせばその分、価値のある機能を作る時間を増やせるかも知れません。

成功循環モデルでも、「結果の質を上げるためにはまず関係の質を上げろ」という話が出てきます。

f:id:chiroruxx:20200712124039p:plain

このように、プロセス改善をすることで、より良いものを、より早く、より高品質で作れるようになります。

振り返りの手法

KPTを使用しています。
KPTを使用している理由としては、「一番メジャーだから」です。
なぜメジャーなものが良いのかというと、情報量が多いからです。
KPTについて気になることがあったとき、メンバーは自分で検索をして調べることができます。
逆にFun Done Learnなどのマイナーな方法を使用すると、まだまだ文献が出回っていないので、「調べたけどよくわからなかった」となりがちです。
ファシリテータに知識が充分にあり、振り返りに慣れている場合にはマイナーな方法でも良いかもしれませんが、私のチームは振り返り初心者なので、メジャーな方法を選択しました。

KPTの概要

ざっとKPTの概要をまとめます。
まず、以下のものをメンバーに挙げてもらい、共有します。

Keep: 良かったこと・継続したいこと
Problem: つらかったこと・問題だなと感じたこと

その結果から、

Try: 改善したいこと・試してみたいこと

を導き出し、改善策を見つけます。

振り返りの参加者

開発メンバーとプロダクトマネージャーで行っています。
また、振り返りで参加できる人数のは3~5人までにしています。
2人以下の場合は、あまり相乗効果を期待できないので、形式張らずにラフな話し合いにします。
6人以上になると発言の機会が減ったり、ダラダラした雰囲気が出ます。
その場合は2チームに分け、後で合流して共有をします。

振り返りの時間

5人で2週間分の振り返りで、60分程度です。
始めたばかりの頃は100分程度かかっていました。

振り返りの流れ

以下の流れでやっています。

  1. (場の設定)
  2. 前回のTryの復習
  3. Keepの共有
  4. Problemの共有
  5. 改善したい項目を決める
  6. Tryの作成
  7. まとめ

ひとつずつ見ていきましょう。

場の設定

振り返りに限らず、ミーティングをする場合は「場の設定」を行います。
アイスブレイクなんかがそうですね。

アジャイル・レトロスペクティブ」という本ではアイスブレイクは振り返りの場の設定に望ましくないと書かれていますが、私は全然アリだと思います。
むしろ紹介されている「チェックイン」の手法を使用したりすると、日本人にあまりない文化のため、メンバーの頭に「?」が浮かんだ状態になったりします。

私はここでの目的は

  • リラックスする
  • 発言しやすくする

などを置いています。
特に「一度も発言していない状態から発言する」というのは心理的にやりにくいので、場の設定のタイミングで全員一度は何かしらの発言をしてもらうようにしています。
「ooさんは今日のお昼、何食べました?」とかでオッケーです。

・・・と、長々と書きましたが、私のチームでは省略することが多々あります。
私のチームでは振り返りは、いわゆる「ミーティングデー」の一環として行われるためです。
振り返りの前のミーティングで既に場が設定されている場合、場の設定はスキップしてしまいます。

前回のTryの復習

前回の議事録を見ながら、

  • 前回のTryをやったか
  • 前回のTryをやってどのように変化したか

を確認します。
ここでのゴールを「Tryをやったかどうか」と誤認してしまう人が多いのですが、ここでのゴールは「Tryをやった結果、改善されたか」です。
ここで「毎回Tryを忘れる」という場合は、Tryをやる仕組みや習慣づくりについて話したほうが良いでしょう。
「忙しくてできなかった」という場合は、「木こりのジレンマ」になっている可能性があります。
Tryのコストが高いか、業務との優先順位をどのように付けるかについて話しましょう。

Keepの共有

以下の手順で行っています。

  1. それぞれでKeepを書き出す(3分)
  2. Keepの共有

以下、注意している点です。

Keepの対象

そのまま和訳すれば「継続」ですが、ここでは単純に「良かったこと」も挙げてもらっています。

振り返りは「反省会」ムードになりがちです。
良かったことを先に挙げてもらうことで、振り返り全体の空気感を明るくし、前向きに振り返りをできる環境にする側面もあります。

Keepの書き方

なるべく付箋など、文字数に上限を決められるもので書きます。
Keepに文字を書きすぎてしまうと、後で全体を俯瞰して見づらくなってしまいます。
「最小限でかつ伝わる」書き方で書くようにします。

Keepを書くとき

そのまま開始するとなぜか「他の人と話してはいけない」という暗黙のルールが形成されるので、 「他の人と相談しても全然OKです!!」ということは明示的に伝えます。

共有方法

順番を決めて、最初の人が1枚共有したら次の人が1枚共有し、さらに次の人が・・・と続けていきます。最後の人まで回ったら最初の人に戻り、全員がすべてのKeepを共有するまで行います。

共有のタイミングでは、長く話しがちになります。
「その人のすべてのKeepを共有してから次の人が共有する」という手法を取ると長く話しやすくなってしまうので、「1枚共有したら次の人」で、必要・重要なことのみを共有してもらいます。

Problemの共有

以下の手順で行っています。

  1. それぞれでProblemを書き出す(3分)
  2. Problemの共有

基本的にはKeepの共有と同じことに注意していきます。
Problemではそれに追加して以下を注意します。

解決策は出さない

問題を考えると一緒に解決策も提示したくなりますが、ここでは解決策は出さないようにします。
基本的に1人で考えた解決策よりも、複数人で考えた解決策のほうが優れています。
三人寄れば文殊の知恵ですね。

なので、解決策はTryを作成する際に、チームで考えるようにします。

特定の人への攻撃

(私のチームでは発生してませんが・・・)

「ooさんのせいで」など書かれているものが共有された場合は要注意です。
基本的に「チーム vs 問題」という構図を目指していくので、このような場合は、その問題の背景にあるプロセスや慣習の問題を考えるように話の方向性を導きます。

あまりにこのケースが多いようであれば、振り返り後に個人的に説得をするか、全体で「なぜ個人を攻撃してはいけないか」を説明し、ルール化したほうが良いです。

改善したい項目を決める

挙がったKeep、Problemから改善したい項目を決めます。
ひとり3票で、得票数の多い1~2つについてTryを考えていきます。

このプロセスはTryを絞るだけでなく、「個人の関心事」を「チームの関心事」に変化させる役割もあります。
たまにTryを考える際に「私のためにみんなに負担かけさせたくないです」のような事を仰る方がいます。
投票という行為を通じて「あなたのためじゃなくてチームのためにやるんですよ」という流れを作ります。

以下、注意点です。

KeepもOK

改善というとProblemに目が行きがちですが、Keepに票を入れても全然OKです。
Keepが選ばれた場合は、「それをより良くするためにはどうするか」がTryになります。

Tryにできるのは2つまで

挙がった問題すべてに対処したくなりますが、グッとこらえて対応するものを絞ります。

すべてに対応しようとすると、ひとつひとつの精度が落ち、「Tryだけどやれなかった」ものが多くなりがちになります。
また、人間は大量の変化には馴染みづらいので、文化や慣習にならずにただ「Tryを実行しただけ」になってしまいます。
さらに、プロセス改善をしすぎると、その分、業務する時間が減り、「プロセス改善してたので業務が進みませんでした」のような事象が発生します。

そのため、なるべくやる価値の高い変化だけに絞り込みます。

もし選ばれなかったものの中に重要なものが紛れている場合、次回にも同じものがKeepかProblemに挙がってくるはずです。 そしてそれが本当に重要なのであれば、次回、投票で選ばれ、改善されるはずです。

Tryの作成

投票で選ばれたものについて、「どのようにすれば改善できるか」をチームで考え、改善項目を出していきます。

ここでは、以下の流れで行っています。

  1. 投票で選ばれたものの事象についての深堀りと理解
  2. Tryを作成する

以下、注意点です。

事象についての共有理解をつくる

事象については誰かが観測してKeep, Problemに挙げたものです。
そして、投票を通じてチームがその事象に関心を持っていることがわかっています。

KeepやProblemを共有する場ではなるべく必要最小限の共有に抑えていたので、ここで詳細な情報をチームで把握します。
この事象を挙げた人に詳しく話してもらったり、他の人からその事象がどのように見えているかについて話すと良いです。
必要であれば「5つのなぜ」や「根本原因分析」などのテクニックを使用するのも良いでしょう。

「チーム vs 問題」を意識する

「ooさんのせいで」のような話になると裁判になってしまうので、なるべく「チーム vs 問題」になるようにします。
「私の能力が低かったので」も同じです。チームの話にシフトさせていきましょう。

基本的に、人間性や能力などは前提条件なので覆すことができません。仕組みやプロセスを調整して問題解決できるようにしていきます。

発散して収束させる

一般的に、「最初に出された解決策はあまり役に立たない」と言われています。
Tryを作成する際は、ブレストの要領でまず広く解決策を募ります。
ここでの敷居はなるべく下げたほうが良いため、ファシリテータは率先して敷居を下げられるような解決策を出していくと良いでしょう。

ある程度、解決策が出揃ったら、解決策をまとめていきます。
実現不可能なものや効率性の悪いものを落としたり、複数のアイデアを合体させたりします。

最終的に、各事象に対してのTryは1~2つ、全体でのTryは3つまでに収まるように調整します。

ベイビーステップを意識する

作成したTryが大きすぎる場合(次の振り返りまでの完了しない場合)は、そのTryを分解し、「最初の一歩」の部分をTryとします。

振り返りのまとめ

最後に、Tryのもととなった事象とTryの内容をまとめ、振り返りを終了します。

記事のまとめ

backcheck開発チームで行っている振り返りの方法をまとめてみました。

改めてKPTの構図を見てみると、「デザインのダブルダイヤモンド」のような、2回の発散と収束があることがわかりますね。

KPTは一見簡単そうに見えますが、結構考えるべきポイントが多く、難しかったりします。
この記事がみなさんのKPTライフの参考になれば幸いです。

Vue.js $emit 使わないで props で method 渡したほうが良くない?

これは、個人ブログ からの転用です ROXXに入って学んだことの1つです。

概要

Vue.js で 親コンポーネントの method 実行させたい場合、$emit 使ってイベントを発火させるより、
props に method をコールバックとして登録しておいて実行させたほうが以下のメリット上げられるので「こっちのほうが良くね?」って話です

  • props の成約をつけられる(requird, etc)
  • $emit の文字列を管理しなくていい
  • IDEで補完が効く

実際のコード

ボタン押したらカウントアップしていくようなやつ f:id:akki_megane:20200708203420p:plain

呼び出し側の親コンポーネント

<template>
    <div>
        <div>{{ count }}</div>
        <child
                :handle-add-number="addCount"
                @addNumber="addCount"
        />
    </div>
</template>

<script>
    import child from "./child";

    export default {
        components: {child},
        data: () => ({
            count: 0
        }),
        methods: {
            addCount(number) {
                return this.count += number
            }
        }
    }
</script>


コンポーネント

<template>
    <button @click="countUpProps()">count up props</button>
    <button @click="countUpEmit()">count up emit</button>
</template>

<script>
    export default {
        props: {
            handleAddNumber: {
                type: Function,
                required: true // required をつけて必須に
            }
        },
        methods: {
            // Prop で渡した function を実行
            countUpProps() {
                this.handleAddNumber(1)
            },
            // emit を使って function を実行
            countUpEmit() {
                // emit の event は 文字列で管理
                this.$emit('addNumber', 1)
            }
        }
    }
</script>


props で定義しておけばこんなふうにIDEで補完が効きます f:id:akki_megane:20200708203603p:plain


こんなかんじで、props を使うと、
requird で縛れて、method の渡し忘れを防げたり、
$emit の文字列管理しなくていいかつ、IDEで補完が効くのでその分typo が防げる という点で開発しやすくなるかなと思います。

TS でやると

Vue も Vue3 から、TypeScript を正式にサポートということで未来を見据えて、
同じ処理を Vue3 + TypeScript で書いてみます


呼び出し側の親コンポーネント

<template>
    <div>
        <p>{{ count }}</p>
        <child
                :handle-add-num="addNumber"
                @addNumber="addNumber"
        />

    </div>
</template>

<script lang="ts">
    import {defineComponent, ref} from 'vue';
    import child from "./child.vue";
    import AddNumberInterface from "../types/AddNumberInterface"; //function の Interface

    export default defineComponent({
        components: {
            child
        },
        setup() {

            const count = ref<number>(0)

            //Interface を指定して関数を定義
            const addNumber: AddNumberInterface = (num: number) => {
                return count.value += num
            }

            return {
                //data
                count,

                //function
                addNumber
            }
        }
    })
</script>


props で渡す関数の Interface 定義

export default interface AddNumberInterface {
    (num: number): number
}


コンポーネント

<template>
    <div>
        <button @click="countUpProps()">count up props</button>
        <button @click="countUpEmit()">count up emit</button>
    </div>

</template>

<script lang="ts">
    import { PropType, defineComponent , SetupContext} from 'vue';
    import AddNumberInterface from "../types/AddNumberInterface";

    type Props = {
        handleAddNum: AddNumberInterface; //Prop の Interface を定義
    }

    export default defineComponent({
        props: {
            handleAddNum: {
                // PropType を使って Prop の type に使いたい関数のInterface を指定
                type: Function as PropType<AddNumberInterface>,
                required: true
            }
        },
        setup(props: Props, context: SetupContext) {
            const countUpProps = () => {
                props.handleAddNum(1)
            }

            const countUpEmit = () => {
                context.emit('addNumber', 1)
            }

            return {
                countUpProps,
                countUpEmit,
            }
        }
    })
</script>


PropType(これは Vue2.6 からあったはず?) と TypeScript を使うことにより、props の Typeを独自のInterfaceに変更することができ、
どんな functionを渡せばいいか、明示することができました!
「Vue ぽくない」とか言われそうですが、 という概念が好きな私にとっては、とても書きやすく感じました。

まとめ

公式のリファレンスや、色々な書籍でもVueで親のmethod を実行した場合は、$emit を使う、
というのは当たり前のように記載されていますが、実際書き比べてみたり、実際のコードを運用してみた観点からしても、
props で method を渡したほが、明示的かつケアレスミスを減らすことができて、とても良いと感じています。
$emit を使う意義を感じなくなってきているので、$emit を使う理由や、もっとよい方法があれば教えてもらえるとありがたいです。
未来のことはわかりませんが、Vue3 からは TypeScript サポートされより型を意識した開発をするように今後なっていくのだとしたら、 上記のような書き方はさらに恩恵を受けそうだなと思っています。

ISMSの内部監査に向けて行ったこと

CTO室情報システム担当の吉澤です!

前回ISMSとPマークの違いについて 書かせていただきました。 今回も引き続き紹介させていただきます。

 

先日、ISMSの内部監査を行いました。ROXXでは2回目、私のキャリアとしては初の監査となりました。

今回の記事ではこの内部監査にお話します。

監査に必要なタスクの洗い出し


監査に備え、必要なタスクを洗い出しました。

マインドマップを利用したので、イメージをつかんでいただければと思います。

 

f:id:wakanayoshizawa:20200618101425p:plain

このマインドマップで全体のタスク量を把握しつつ順に準備を進めていきました。

 

各部署へのヒアリング内容

具体的な業務は各部署ごとにヒアリングをしながら項目の確認をすすめていきます。

ヒアリング内容は主に3点を担当しました。

3点とはいえ業務としては多岐にわたっていたので、作業として最も時間を要したリスク管理について書いてみたいと思います。

リスク管理について リスクアセスメントでは、生じる恐れがあるすべてのリスクを想定します。

次に、リスクごとに発生頻度、業務・会社への影響度を設定していきます。

そして、発生頻度と影響度が高いリスクについて、対策を年内実施する計画としてタスクに落とし込みます。

今回は内部監査の日程が控えていたため、発生頻度と影響度が高いと想定されたもののうち、内部監査までに対応できそうなものを中心にすすめてきました。

承認フローの見直しなど内部監査に間に合わない、長期的に実施するべきもの については年次の計画の中で対応してく予定です。

 

内部監査を実施した印象

 

私の実務としてはリスク管理に関してオペレーションに最も時間がかかったのですが、内部監査では、情報区分、教育、手順やプライバシーポリシー及び情報セキュリティ方針の運用状況について指摘される事項が多かったです。

事前の自分のイメージと現実のずれも感じとても勉強になりました。

最後に ROXXではAgent Bank、Back Checkとサービス及び組織が急拡大しています。

このような状況の中でISMSの内部監査を体験できたことは、自分にとって大きな経験となりました。

ISMSについては引き続き本番の審査、年次計画もあるので、継続的な運用に努めていきます。