(編集より: この記事は、 Indeed が提供する、 A/B テストのオープンソース・フレームワークである Proctor についての連載シリーズの1回目となります。)
Indeed における A/B テスト
「 We Help People Get Jobs (人々の仕事探しのお手伝いをする)」 ― これが Indeed のミッションだ。
「求職者にとっての最善とは何か?」
常日頃掲げるこの問題に対し、私たちは何でもテストし結果を測定する事で、答えを見つけ出す。
Indeed ではあらゆるプロダクトの新機能や改善点を、全てテストするように努めている。そうする事で、それらの変化の影響を測り、ミッション達成に確実に役立つようにしているのだ。
2013年 1月に、トム・バーグマンとマット・シュメルは、自社開発した A/B テスト・フレームワークである Proctor について @IndeedEng テックトークを行い、そこで、 Proctor がオープンソースとして利用可能になった事を発表した。
以来、 Proctor Web アプリのオープンソース化も実現している。こちらは Proctor のテスト定義を管理する Web アプリケーションだ。
10 月のテックトークで、トムはシンプルな A/B テストの例を挙げていた。テスト内容は、背景色変更が、 UX を向上するか判断する、というもの。
図 1 は、コントロールグループ A とテストグループ B を表している。 A は、 Find Jobs (求人検索)ボタンに変更を加えていないもの。一方 B は、ボタンの背景色が青に変更されている。
Logrepo についてのテックトークや記事でも説明されているように、 Indeed ではあらゆるログを取る。プロダクトを分析し、そこから学び、改善するためだ。
上に挙げたシンプルなテストを行うために、まず Indeed サイトにアクセスした各ユーザーのグループ( A または B )とその後派生するクリック数のログを取った。そして、自社の分析ツールを使用したところ、明らかになったのは、この場合はテストグループの方が、より多くの検索、そしてさらなるユーザー・エンゲージメントをもたらす、という事だった。
上記の例は、1つのテストの挙動だが、通常僕らは 1 件のテストにつき代替案となる複数の挙動をテストする。このテストでは、おそらく背景色を 2 つ以上は試すかと思われる。
また、複数のアイデアを同時にテストするのも可能だ。図 2 の例を見ると、一方はボタンのテキストのテストで、もう一方は背景色のテストとなっている。
このように、機能の中である特定の部分に対して、複数の変数 (テキストや色) をテストすることは、 「多変量テスト」 として知られている。
Indeed ではA/B テストを何年も取り入れている。その経験が、 Proctor 開発に役立った。10 月に行われたテックトークでは、Proctor の設計についてや、 A/B テスト以外でのProctor の用途について、トムとマットがさらに詳しく語っている。
今回の記事では、 Proctor の主要な機能とコンセプトを中心に、 Indeed の基本的なProctor の利用法を述べていく。
Proctor その機能とコンセプト
標準表現
Proctor は標準の JSON 表現を使用したテスト定義を提供しており、テスト定義の修正が、コードとは独立にデプロイできるようになっている。
テスト定義の集合全体は「テスト・マトリックス」と呼ぶ。テスト・マトリックスは 1 つのファイルとして、複数のアプリケーションに分散が可能である。これにより、テスト管理の小回りが効くようになって、複数のアプリケーション間で安定してテスト定義を共有できるようになる。
図 3 はとてもシンプルなバージョンのボタン・テストを表している。この図では、 50% のユーザーがコントロールグループ A ( バケット 0 ) に割り当てられ、残りの 50% はテストグループ B(バケット1)に割り当てられている。
"buttontst": { "description": "backgroundcolortest", "salt": "buttontst", "buckets": [ { "name": "control", "value": 0, "description": "current button treatment (A)" }, { "name": "altcolor", "value": 1, "description": "test background color (B)" } ], "allocations": [ { "ranges": [ { "length": 0.5, "bucketValue": 0 }, { "length": 0.5, "bucketValue": 1 } ] } ], "testType": "USER" }
この例が解りやすくなるように、ここで簡単に Proctor に関連する用語をまとめる。
- 全てのテストにはテストタイプ (testType) が存在する。もっとも一般的なタイプはユーザー (USER) である。これは、僕らがユーザー識別子を使ってテストのバリエーションをマップする事を意味している。テストタイプについて、後でさらに詳しく述べたいと思う。
- 各テストはバケット (buckets) の配列とアロケーション (allocations) の配列からなる。
- バケットは Proctor のテスト定義内のバリエーションまたはグループを指す。各バケットは短い名前 (name) 、整数値 (value) 、そして解りやすい説明 ( description ) を含んでいる。
- アロケーションはバケットのサイズを範囲 (range) の配列として指定する。各範囲は 0 から 1 の間の長さで、バケット用にバケットの値 (bucketValue) への参照を持つ。1つのアロケーションの範囲の合計は1になる必要がある。ルールを使用すれば、複数のアロケーションを持つ事ができる(これも詳しくは後述)。
Proctor Web アプリ
Proctor Web アプリを使用する事で、テスト定義をWeb ブラウザから管理・デプロイする事ができる。
このアプリは、色々な方法でカスタマイズでき、以下のようなシステムやツールとの統合を可能にしてくれるのだ。
- テスト変更履歴を維持するバージョン管理システム
- テスト変更のワークフローを管理するための課題トラッキングシステム
- ビルドやデプロイシステムなどのその他の外部ツール
JSON のテスト仕様から Java コードを生成する
Proctor 内のテスト仕様は テスト定義から独立した JSON ファイルである。これは、テスト定義のフルセットの中から、アプリケーションがどのテストとバケットを認識しているかを宣言できるようにしている。
これらは、ビルドプロセスではJava コード生成の際に使用可能だ。実行時には、テストマトリックスの関連部分を読み込む際に使用できる。
コード生成は任意だが、コンパイル時のタイプセーフを提供してくれるので、テストとバケット名を含む文字列リテラルを持つコードを捨ててしまう必要がなくなる。
また、生成されたクラスは Java コードやテンプレート言語のテストを書くのを簡単にしてくれる( 図5はJSPでの例 )。
さらに、生成された Java のオブジェクトは、ユーザーがそのテストグループに割り当てられているかどうかを JSON や XML などの形式へとシリアライズする事ができる。
<c:if test="${groups.buttontstAltColor}"> .searchBtn { background-color: #2164f3; color: #ffffff; } </c:if>
図5 : テストグループ・メンバーシップに基づいたJSPテンプレート内の条件付き CSS
ルールに基づくコンテクスチュアル・アロケーション
Proctor のルール定義の言語を使用すると、ランタイム・コンテキストに対してルールを評価することができる。そうする事で、システムはテストならびにテスト・アロケーションを適用する事ができる。
例えば、テスト全部が、特定のセグメントのユーザーにのみ利用可能になるように定義することが可能になる。または、セグメントに応じて、テストグループのアロケーションを調整する事もできる。ある国では、テストを A/B で 50% ずつで行い、その他全ての国で A/B/C/D で 25% ずつにすることもできる。
ルールに基づいたグループの割り当てを行うと、柔軟にテストをロールアウトし、評価することができるようになるのだ。
"allocations" : [ { "rule" : "'US' == country && 'en' == userLanguage", "ranges": [ { "length": 0.5, "bucketValue": 0 }, { "length": 0.5, "bucketValue": 1 } ] }, { "rule" : null, "ranges" : [ { "length" : 1.0, "bucketValue" : -1 } ] } ]
図6: 米国英語使用者向け 50/50 テストその他ユーザーには無効なテスト ( bucket -1 ) である。
ペイロード
テスト定義において、テストグループにデータのペイロードを添付可能である事は、コードをシンプルにしてくれる。
図の 7 と 8 では、テストしているボタンの色をテスト定義の中でどのようにペイロードとして指定できるか、指定された色にテンプレートからどうやってアクセスできるかを具体的に示している。
この例では、テンプレートコードの総量は減ってはいないが、もし複数のテストバリエーションがあって、それぞれ別の色を指定していたら、ペイロードを使用することでコードの行数は減るだろう。
"buckets": [ { "name": "control", "value": 0, "description": "current button treatment (A)", "payload": { "stringValue": "#dddddd" } }, { "name": "altcolor", "value": 1, "description": "test background color (B)", "payload": { "stringValue": "#2164f3″ } } ]
<style> .searchBtn { background-color: ${groups.buttontstPayload}; } </style>
柔軟なテストタイプ
Proctor はテストタイプに関して柔軟なコンセプトを持っている。
バケットの決定は以下に基づいて可能だ。
- ユーザー(通常トラッキング・クッキーの値によって定義される)
- アカウント ID (デバイス間で固定可能)
- Eメールアドレス
またはリクエスト間で完全にランダムにすることもできる。
さらに、Proctor はテストタイプをカスタマイズして拡張することも可能だ。
例えば、ページ URL やコンテンツ・カテゴリのような、コンテキストあるいはコンテンツに基づく attributeでテストグループを決めたいときには、カスタマイズしたテストタイプは便利である。
バイアスのない独立したテスト
一様分布のハッシュ関数を使用して、 Proctor は インプットとなる識別子 ( 例.ユーザーID )を整数にマップする。テストにバケットを割り当てるためである。
バケットに対する範囲の割り当てが、各バケットを定義する整数の範囲を決定するのだ。図 9 は 50/50 の割合のコントロール/テストの例だ。ハッシュ関数が一様なので、バケットの指定はバイアスのないものになるはずである。
図 9 : ハッシュ関数使用のため、整数の範囲にマップされた 50/50 のコントロール/テスト・バケット
さらに、 Proctor のテストは独立している。つまり、あるテストで特定のテストグループに属していることは、他のテストでどのテストグループに属するかということに全く相関がないのだ。
この独立性は、各テストに異なる salt を割り当てることで成立している。この salt はハッシュ関数への input として識別子と一緒に使用される。
各テスト定義に異なる salt を含める事で、次の2つの高度な機能が使用可能だ。
- 同じ salt を複数のテスト間で共有し、異なるテスト間で意図的にバケットをそろえる(相互に依存させる)ことができる。ただし、共有の salt は “&” で始まらなければならない。実際には、この方法で 2 つのテストをそろえる必要があるケースは殆ど目にしない。
- salt を変更する事で、テストの配分の「シャッフル」が可能になる。というのは、ハッシュ関数からまったく異なる output がかえってくるからだ。このシャッフルを行う事で、テスト内に偶発的なバイアスがないか再確認する事ができる。
Indeed における Proctor
Indeed のプロダクト開発に対するデータドリブンなアプローチにおいて、Proctor は重要な役割を担うようになった。
現在本番環境には、 100 件以上のテストと 300 件以上のテストのバリエーションが存在する。
次回のポストでは Indeed での Proctor 使用法をより詳細に説明していく。