【Laravel】ABテスト機能をつくる

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、プログラマーに限らずですが、多くの人はスキルアップやキャリアアップが頭の中にあったりするんじゃないでしょうか。

かくいう私も、より「自分の価値」を高くできたらいいなと思い(興味がわけば、ですが😂)いろいろと新しい分野に触れるようにしています。

そして、そんな中Laravelでひとつ作ってみたい機能が頭に浮かびました。

それが・・・・・・

ABテスト(Split Testing)

です。

ABテストとは、簡単に言うと「ある2つ(以上)の画像を用意し、どちらの方がより多くクリックされたか?」を調べるテストのことです。

そしてテストの結果を見れば、よりウケがいいコンテンツを知ることができるというわけですね。

ということで、今回は「セールやってます!」的な画像で ABテストを実装してみます。

ぜひ何かの参考になりましたら嬉しいです😊
(最後にソースコード一式をダウンロードできますよ👍)

「もしかして
シュレッダーのくずで
いい枕できるんじゃ??」

開発環境: Laravel 8.x

やりたいこと

今回は2種類の画像を用意して、「よりクリックされるのはどっち❓❓」をテストしてみたいと思います。

利用する画像は次のとおりです。

A画像(black-friday-a.png)

B画像(black-friday-b.png)

画像は以下からダウンロードさせていただきました。(サイズは加工しています)感謝!

【元画像】
https://pixabay.com/illustrations/black-friday-christmas-1898114/
https://pixabay.com/illustrations/black-friday-christmas-1878945/

なお、毎回ランダムで画像が切り替わるとテスト結果の信頼性が低くなってしまうので、各ブラウザごとに「常に同じ画像を表示」します。

そのため、Cookieを使った以下の手順で実装します。

  1. 表示した時点で Cookie に「どちらの画像を表示したか」のデータをセットする
  2. 次回アクセスしてきたときは Cookie の値をチェックして前回と同じ画像を表示する

なお、実際に「テストページから移動してきたかどうか❓❓」はリンクにパラメータを含めて判別するようにします。

では、楽しんでやってみましょう❗

ABテストの結果を保存するテーブルをつくる

では、まずABテストの結果を保存しておくテーブルsplit_testingsをつくります。

以下のコマンドを入力してください。

php artisan make:model SplitTesting -m

すると、モデルと同時にマイグレーション(データベース・テーブルの設計図)が作成されるので以下のように変更してください。

database/migrations/****_**_**_******_create_split_testings_table.php

// 省略

Schema::create('split_testings', function (Blueprint $table) {
    $table->id();
    $table->string('name')->comment('テスト名');
    $table->string('view_index')->comment('ビューのインデックス番号'); // つまり、ABどちらの表示か
    $table->uuid('uuid')->comment('UUID'); // 個人の特定用
    $table->boolean('accessed')->default(false)->comment('アクセスされたかどうか');
    $table->timestamps();
});

// 省略

では、以下のコマンドを実行してテーブルを作成しましょう。

php artisan migrate

すると、テーブルは以下のようになります。

Bladeコンポーネントをつくる

では、ABの画像を切り替えるコンポーネントをBladeコンポーネントで作っていきましょう。

使い方は次のようなカンジです。

<x-split-testing name="black_friday_sale_2021" :views="$views"></x-split-testing>

$viewsは、配列のビューのパスです。

コンポーネント・クラスをつくる

まずは以下のコマンドを実行してください。

php artisan make:component SplitTesting

すると、ファイルが作成されるので中身を以下のように変更します。

app/View/Components/SplitTesting.php

<?php

namespace App\View\Components;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\View\Component;
use App\Models\SplitTesting as SplitTestingModel;

class SplitTesting extends Component
{
    private $name, $views;

    public function __construct($name, $views)
    {
        $this->name = $name;
        $this->views = $views;
    }

    public function render()
    {
        $view = $this->getView();
        return view($view);
    }

    private function getKey() {

        return 'split_testing_'. $this->name;

    }

    private function getView() {

        $existing_index = $this->getExistingViewIndex();

        if($existing_index > -1) { // すでに表示済みの場合(前回と同じものを表示する)

            $cookie_index = $existing_index;

        } else {

            $cookie_index = $this->getRandomViewIndex();

        }

        return $this->views[$cookie_index];

    }

    private function getExistingViewIndex() {

        foreach($this->views as $index => $view) {

            $key = $this->getKey();

            if(\Cookie::has($key)) {

                $cookie_data = json_decode(
                    \Cookie::get($key)
                );
                return $cookie_data->index;

            }

        }

        return -1;

    }

    private function getRandomViewIndex() {

        // キャッシュ保存
        $key = $this->getKey();
        $view_index = \Cache::get($key, -1); // 初期値は -1(つまり最初は次の行でゼロになる)
        $view_index++;

        if(!Arr::has($this->views, $view_index)) {

            $view_index = 0;

        }

        \Cache::put($key, $view_index);

        // Cookie 保存
        $uuid = Str::uuid();
        $cookie_values = [
            'index' => $view_index,
            'uuid' => $uuid
        ];

        \Cookie::queue($key, json_encode($cookie_values) , 2628000); // 5年間インデックスを保存

        // DBに画像が表示されたデータを保存
        $split_testing = new SplitTestingModel();
        $split_testing->name = $this->name;
        $split_testing->view_index = $view_index;
        $split_testing->uuid = $uuid;
        $split_testing->save();

        return $view_index;

    }
}

この中でやっていることは以下のとおりです。

  • 過去にABテストで画像を表示しているかチェック
  • 表示していたら、そのときと同じ画像を表示
  • そうでない場合は、ランダムに A ↔ Bで画像を切り替え、そのデータを Cookie に保存(つまり、次回からは同じ画像が表示される)

※ ちなみに、コンポーネント・クラスはLaravel 7.xから導入されました。
詳しくは以下の記事をご覧ください。

📝参考記事: Laravel 7.xの新コンポーネント機能!実例

なお、この中で1点気をつけていただきたいのが以下の部分です。

use App\Models\SplitTesting as SplitTestingModel;

これは今回作成したコンポーネント・クラスとDB用モデルのクラス名が同じため、「重複するクラスがあるよ…😫」とエラーになります。

そのため、モデルのクラス名を一時的に「SplitTestingModel」へ変更して使用しています。

ABテストを設置する

では、先ほどつくったABテストを実際にLaravelに設置していきましょう。

ビューをつくる

作成するビューは以下の3つです。

  • コンポーネントを表示するビュー
  • ABテスト用のビュー「A」
  • ABテスト用のビュー「B」

中身はそれぞれ以下のようになります。

1.コンポーネントを表示するビュー

resources/views/split_testing.blade.php

<html>
<body>
<div id="app">
    <x-split-testing name="black_friday_sale_2021" :views="$views"></x-split-testing>
</div>
</body>
</html>

2.ABテスト用のビュー「A」

resources/views/split_testing/black_friday_sale_a.blade.php

<h1>ただいまセール中!</h1>
<a href="/split_testing/sale?split_testing=black_friday_sale_2021">
    <img src="/images/black_friday_a.png">
</a>

2.ABテスト用のビュー「B」

resources/views/split_testing/black_friday_sale_b.blade.php

<h1>ただいまセール中!</h1>
<a href="/split_testing/sale?split_testing=black_friday_sale_2021">
    <img src="/images/black_friday_b.png">
</a>

ルートをつくる

次に「コンポーネントを表示するビュー」を呼び出すためのルートをつくります。

routes/web.php

// 省略

use App\Http\Controllers\SplitTestingController;

Route::get('split_testing/top', [SplitTestingController::class, 'top']);
Route::get('split_testing/sale', [SplitTestingController::class, 'sale']);

// 省略

設定しているのは次の2つです。

  • top: ABテストの画像を表示するルート(トップページを想定しています)
  • sale: セールのページ(画像がクリックされたらアクセスされたものとしてデータ保存)

コントローラーをつくる

では、ルートでセットしたコントローラーをつくりましょう。
以下のコマンドを実行してください。

php artisan make:controller SplitTestingController

すると、ファイルが作成されるので中身を以下のように変更します。

app/Http/Controllers/SplitTestingController.php

<?php

namespace App\Http\Controllers;

use App\Models\SplitTesting;
use Illuminate\Http\Request;

class SplitTestingController extends Controller
{
    public function top() {

        $views = [
            'split_testing.black_friday_sale_a',
            'split_testing.black_friday_sale_b'
        ];

        return view('split_testing')->with([
            'views' => $views
        ]);

    }

    public function sale(Request $request) {

        if($request->filled('split_testing')) {

            $split_testing_name = $request->split_testing;
            $cookie_key = 'split_testing_'. $split_testing_name;

            if(\Cookie::has($cookie_key)) {

                $cookie_data = json_decode(
                    \Cookie::get($cookie_key)
                );
                $view_index = $cookie_data->index;
                $uuid = $cookie_data->uuid;

                $split_testing = SplitTesting::where('name', $split_testing_name)
                    ->where('view_index', $view_index)
                    ->where('uuid', $uuid)
                    ->first();

                if(!is_null($split_testing)) {

                    $split_testing->accessed = true;
                    $split_testing->save();

                }

            }

        }

        return 'セール中です!';

    }
}

テストしてみる

では、実際にテストしてみましょう❗

まずブラウザで「http://******/split_testing/top」へアクセスします。

すると「A」の画像が表示されました。

では、DBを確認してみます。

きちんと「A(=インデックスがゼロ)」のデータが登録されています。

では、Cookieの方はどうでしょうか。

こちらもうまく保存されています。(暗号化されているので中身は分かりません)

では、この状態でリロードして同じ画像が表示されるかをチェックしてみましょう。

これもうまくいきました❗

では、今回はテストですので、先ほどのCookiesplit_testing_black_friday_sale_2021)を手動で削除&リロードし、今後は「B」の画像を表示されるかをチェックしてみましょう。

すると・・・・・・

はい❗
うまく画像が切り替わりました。

DBの方でも、今度はインデックスが1のデータが新規登録されています。

では、この状態で次は画像をクリックしてaccessed1に変わるかをチェックしてみます。

ページ移動すると・・・・・・

では、DBのチェックです。

はい❗
想定していたとおりaccessed1true)に変わりました。

成功です😊✨

あとは、AとBがそれぞれどれだけクリックされたかを集計すればどちらがより有効な画像(コンテンツ)かが分かります❗

ちなみに:ABテストの精度についてのまとめ

私はこういったマーケット手法に詳しいわけではないので、まずはじめにいろいろと情報をABテストについて情報を集めることにしました。

すると、ABテストは条件が揃わないと本当に価値がある結果をゲットできないという意見もあったので、自分のためにも以下にまとめておきたいと思います。

検証ってなかなか難しいですね…😭

とにかくデータ数が多くないとダメ

まだアクセスが少ないサイトでやっても結果がかたよってしまうので、逆にやらないほうがいいという場合もあります。

継続してやらないとダメ

ある程度きちんとABテストをしても、時代の流れは常に変わっているので、継続して変更を続けていかないといけないです。

さらに、頑張ってゲットした結果を使って何かを変更すると、それによってまたユーザーが違った行動をとる可能性も出てきます。

大きく変えすぎるとダメ

これも継続と似ていますが、毎回全く違う2つのものでテストするというよりは、ページの中で「良ければ残す、悪ければ捨てる」を続けていき、少しずつよりよい結果を採用していかないと意味がない。(ただ、状況によっては大きく変えなかきゃいけないときもあると思うので、バランスが難しいかもですね😫)

同じページで一度に別の2つ以上のABテストをしちゃダメ

例えば、次の2ペアでテストを実施したとします。

  • A ↔ B で切り替え: ペア1
  • C ↔ D で切り替え: ペア2

すると「ペア1」の結果は、ペア2の変化からも影響を受けるので、こういった場合は以下のように全ての組み合わせを作ってテストしないといけないです。

  • A & C
  • A & D
  • B & C
  • B & D

こうなると、数が多くなりすぎてしまうので同じページでは一箇所だけの方がいいのかもしれませんね。

ダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードすることができます。

【Laravel】ABテスト機能をつくる

※ ただし、マイグレーション等はご自身で実行してください。

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで今回はLaravelでABテストを実装してみました。

正直なところ検証や分析はそれほど詳しいわけではないですが、今後の展望として「経営者的な視点」で見て必要となる機能やコンテンツもご紹介できればいいなと考えています。

なお、実は今回の記事は「バージョン2」になっています。

というのも、当初は「ある商品の画像(複数)の中から人気がある画像を抽出する」というような記事を書きはじめていたのですが、途中で「いや、これ複雑すぎて記事としては不適切だろ…💦」となり、最初からやり直しています。

やはりクリエーターとしては「お蔵入り」になると結構悲しいものがあるので、なんとか再構成して記事を書いてみました。

ぜひ喜んでいただけると嬉しいです。

ではでは〜❗

「痛み止めのお薬って
眠くなるんですね…💦」

このエントリーをはてなブックマークに追加       follow us in feedly