A/B テストの基盤を構築した話

こんにちは。エンジニアの濱田( @hamakou108 )です。

先日M&Aクラウドのサイト上で A/B テストを本格的に導入していこうという話になり、そのシステム基盤を構築する機会がありました。 今回は A/B テストの基盤構築に至った経緯とどのように実装したのかについて紹介したいと思います。

A/B テストの基盤構築に至った経緯

M&Aクラウドの開発チームでは事業上の目標到達に向けた施策実施とその効果測定を継続的に行っています。

tech.macloud.jp

A/B テストの導入によって一度に複数の案を検証することが可能となるのに加え、外的要因に左右されにくいデータが得られるため、より効果的に検証サイクルを回すことができると考えています。

さて A/B テストを実施する方法として、 Google Optimize などのツールを利用することも選択肢として挙げられます。 しかしクライアントサイドでページを差し替えるパターンではページフリックのような UX 低下を招く可能性があること、振り分けたユーザー集団が特異性を持たないように振り分けロジックを自分たちで開発・保守したいというニーズもありました。 このような理由により、ユーザーの振り分けやページの差し替えといったロジック基盤を自前で開発することになりました。

設計と実装

A/B テスト基盤の開発において工夫した主なポイントは次のようなものです。

  • セッションの特定、および偏りのないユーザー振り分け
  • Laravel と Nuxt.js の両方で同じように機能すること
  • A/B テストの開始と終了のイベントの集計

これらについて順番に解説します。

セッションの特定、および偏りのないユーザー振り分け

あるユーザーに対して行ったページの出し分けとアクションの結果を紐付けるために、そのセッションを一意に特定する必要があります。 これを実現するために A/B テスト用の Cookie を発行して、ユーザー(クライアントのブラウザ)ごとに一意な ID を割り当てています。

テストパターンの振り分けを行う際は、まずこのユーザー ID ClinetId とテスト名 TestName を元に 0 から 1 までのランダムな実数値を出力し、その値を元に以下のようなロジックでテストパターン Varinat のどれかに振り分けています。

<?php
function chooseVariant(
    TestName $testName,
    ClientId $clientId,
    array $variantList
): Variant {
    // TestName と ClientId を元に 0 から 1 までの実数値に変換する。
    $frequencyPercentage = FrequencyPercentage::makeFromString(
        $testName->rawValue() . $clientId->rawValue()
    );

    $max = new VariantPercentage(0);

    foreach ($variantList as $variant) {
        /** @var Variant $variant */
        $min = new VariantPercentage($max->rawValue());
        $max = $max->add($variant->getPercentage());

        // どの Variant を何%の確率で出現させるかあらかじめ定義してある。
        // 例えば VariantA と VariantB にそれぞれ50%を設定した場合、
        // 数値が 0-0.5 なら VariantA に、 0.5-1 なら VarinatB に振り分けられる。
        if (
            $frequencyPercentage->isGreaterThanOrEqualTo($min)
            && $frequencyPercentage->isLessThanOrEqualTo($max)
        ) {
            return $variant;
        }
    }

    throw new LogicException('A/B テストのパターンを一意に決定できませんでした。');
}

任意の文字列を 0 から 1 までの数値に変換するロジックの実装(上記コードの FrequencyPercentage::makeFromString の中身)に関しては Qiita に記事を投稿しているので、是非のぞいてみてください。

qiita.com

Laravel と Nuxt.js の両方で同じように機能すること

M&Aクラウドは大部分が Laravel で実装されていますが、一部のフロントエンドは Nuxt.js への移行が進んでいます。 Nuxt.js 移行の詳細に関しては次の記事をご覧いただければと思います。

tech.macloud.jp

tech.macloud.jp

tech.macloud.jp

プロジェクトは今も進行中のため、現時点ではフロントエンドに Laravel と Nuxt.js が混在する状態となっています。 したがって例えば出し分け対象のページを Laravel 、ユーザーアクション後に訪れるページを Nuxt.js が生成しているといったケースも考えられ、このようなケースでも A/B テストが機能するように配慮する必要がありました。 このためどちらのシステムが発行した Cookie であるかに依らず、それぞれのシステムで同じように Cookie を取り扱えるような工夫をしています。 こちらの実装の詳細に関する記事も Qiita に投稿しているので、是非のぞいてみてください。

qiita.com

A/B テストの開始と終了のイベントの集計

A/B テストの結果の集計は Google Tag Manager (以下 GTM と表記)と Google Analytics (以下 GA と表記)を用いて行っています。

GTM の方には特定のイベントを受け取ったらそのデータを GA のイベントデータの形式にフォーマットして送信するように設定しておきます。 クライアントサイドの JS からは以下のようにして GTM にイベントを飛ばすことができます。

<script>
  window.dataLayer.push({
    event: 'ABTesting',
    testCaseDefinition: testName,
    valiant: variant,
    testState: 'start'
  })
</script>

開始(対象ページへのアクセス)または終了(アクションの実行)のタイミングで GTM にイベントを飛ばすことで、 A/B テストの試行回数とアクションの結果を GA 上で確認することができるようになります。

まとめ

M&Aクラウドにおける A/B テストの基盤作成の方法について紹介しました。 A/B テストを自前で実装することを検討している方の参考になれば幸いです。