【わずか10分で構築完了】Firebase Remote Config を使ってA/Bテストをやってみよう

この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2017 の投稿記事です。

こんにちは。スタディサプリENGLISHのAndroidとサーバサイドの開発を担当している王です。

みなさんはコンシューマ向けのサービスを運用していると以下のような相談を受けることはありませんか?

  • 複数のデザイン改善案が上がってきて、どちらにするか決めかねるとき
  • データ分析の結果に応じた施策を打つ時、どの施策が本当に効くかを検証したいとき

普通ならここで A/B テストの導入となりますよね?そこで登場するのが Firebase の Remote Config です。今回初めて使ってみましたが、わずか10分で超簡単に導入できました。

そもそも A/Bテストとは?

狭義ではA/Bテストは仮説検定を指す俗称であるが、広義のA/Bテストはインターネットマーケティングにおける施策の良否を判断するために、2つの施策同士を比較検討する行為全般を指す

A/Bテスト - Wikipedia

その名の通り、A/Bテストとは『Aパターンと Bパターンのどちらが良いかを判断するための検証』のことです。実際の検証では主に以下を意識します。

  • テストするパターン
  • パターンの割当て
  • パターンの結果に対するモニタリング

Firebase Remote Config とは

Firebase Remote Config は、アプリのアップデートをユーザーにダウンロードしてもらわなくても、アプリの動作と外観を変更できるクラウド サービスです。
( 中略 )
アップデートを適用するタイミングはアプリ側で制御できます。アプリはアップデートの有無を頻繁にチェックし、パフォーマンスにほとんど影響をおよぼすことなくアップデートを適用することができます。

Firebase Remote Config | Firebase

Remote Config は、アプリがローカルで持つ文言や画像データをリモート ( サーバ ) 側に持たせることで、アプリをアップデートすることなく外観を簡単に変更することが出来ます。値をランダムに割り当てる機能があり、ユーザごとへの出し分けが簡単に出来ます。Firebase Analytics のモニタリング機能と併用すれば簡単にA/Bテストが始められます。

今回はボタンのラベル変更を例にご紹介します。

10分でA/Bテスト環境を構築する

テストの設計

今回はシンプルにボタンのラベルを検証します。登録する試してみるの2パターンを用意し、ユーザ層を均等に分けてそれぞれのパターンをランダムに割り当てます。テストのモニタリングにはFirebase Analytics を利用します。ボタンをクリックする度にFirebaseへパターン名付きのイベントが送られます。

パターンA パターンB
デザイン
割当て 50% 50%
モニタリングイベント startButtonA startButtonB

Firebase 設定 ( 所要時間5分 )

はじめに Firebaseのプロジェクトを新規に作成します。基本的に画面の指示に従って必要事項を入力するだけでOKです。

プロジェクトを新規作成
アプリを追加
google-services.jsonダウンロード
JSON をアプリに置く

次に Remote Config を設定します。こちらも画面の指示に従って入力していくだけです。

条件となる値を追加
新しい条件を定義する
割り当て条件の一覧
パラメータを追加
パラメータ一覧
変更内容を公開

Android デモアプリの実装 ( 所要時間5分 )

プロジェクトに build.gradle を追加します。

buildscript {
  dependencies {
    ...
    classpath 'com.google.gms:google-services:3.1.0'
  }
}

アプリに build.gradle を追加します。

dependencies {
    ...
    compile 'com.google.firebase:firebase-config:11.6.0'
    compile 'com.google.firebase:firebase-core:11.6.0'
}
apply plugin: 'com.google.gms.google-services'

Remote Config からの取得失敗時や、そもそも値が存在しない時に使われるデフォルトの値を設定します。ここの key は Remote Config で設定したパラメータキーと同じものにする必要があります。

<?xml version="1.0" encoding="utf-8"?>
<defaultsMap>
    <entry>
        <key>ab_test__pattern</key>
        <value>Default</value>
    </entry>
    <entry>
        <key>ab_test__start_button</key>
        <value>登録する</value>
    </entry>
</defaultsMap>

画面に Remote Config からの値を取得します。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    startButton = findViewById(R.id.start_button);
    mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
    // デベロッパーモード設定
    FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
            .setDeveloperModeEnabled(BuildConfig.DEBUG)
            .build();
    mFirebaseRemoteConfig.setConfigSettings(configSettings);
    // 先程作ったデフォルト値指定
    mFirebaseRemoteConfig.setDefaults(R.xml.ab_test_defaults);
    long cacheExpiration = TimeUnit.HOURS.toSeconds(1);
    if (mFirebaseRemoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()) {
        cacheExpiration = 0;
    }
    // Remote Config から値取得
    mFirebaseRemoteConfig.fetch(cacheExpiration)
            .addOnCompleteListener(this, new OnCompleteListener<Void>() {
                @Override
                public void onComplete(@NonNull Task<Void> task) {
                    if (task.isSuccessful()) {
                        // 取得した値をアクティベート。アクティベートしないと、反映しない
                        mFirebaseRemoteConfig.activateFetched();
                        // ボタンに取得した値表示
                        startButton.setText(mFirebaseRemoteConfig.getString("ab_test__start_button"));
                    }
                }
            });
}

ボタンをクリックしたイベントを送る処理を実装します。

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    firebaseAnalytics = FirebaseAnalytics.getInstance(this);
    startButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // `ボタン名+Pattern` をイベント名として送る
            firebaseAnalytics.logEvent("startButton" + mFirebaseRemoteConfig.getString("ab_test__pattern"), new Bundle());
        }
    });
}

いよいよ検証

あっという間にA/Bテスト環境が出来上がりました。それでは実際に動かしてみます。ランダムでパターンが設定されるので、検証端末は複数台用意すると良いでしょう。

アプリを起動してボタンをクリックします。Firebaseのイベントボード一覧を確認してみましょう。ちゃんとイベントが送られてきてるのがわかりますね。

イベント startButtonA と startButtonB の件数が記載されてます

Tips ( 知見 ) いろいろ

割り当て条件について

今回はユーザ層を均等に分けるためにRandom50(0~50)Random100(51~100) の2つの条件を定義しました。しかしユーザがランダムで分割されるということは、表示はパターンBなのに、送信するイベントがパターンAになってしまうのではないでしょうか

Firebaseであればそのような心配はありません。Firebase Remote Config では、条件が定義されるタイミングで DEF というキーが生成され、これで割り当て条件の定義が管理されます。定義後に割り当てられるユーザの分布はキーが変わらない限り固定されるため、一度割り当てられたユーザのパラメータが変動することもありません。

さて、そのDEFですが、Firebase条件定義のところにそれらしき項目が見当たりません… 🤔

どうやら2017年12月時点ではまだ日本語対応が追いついていないらしく、英語に変えてみたら、ちゃんと出てきました 😊。

ユーザの分布を再設定したい場合は、ここのキーを指定しなおせばOKです。

キャッシュについて

long cacheExpiration = TimeUnit.HOURS.toSeconds(1);
if (mFirebaseRemoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()) {
    cacheExpiration = 0;
}
mFirebaseRemoteConfig.fetch(cacheExpiration);

Remote Config から値を取得する時に cacheExpiration を設定しました。取得に成功した値は、 cacheExpiration という有効期限までキャッシュされます。キャッシュ期間はデフォルトで12時間です。もっと短く設定することも出来ますが、頻繁にフェッチするとRemote Configの反応が悪くなるリスクがあるため、適切な時間をご設定ください。また、Remote Configで値設定を間違えると有効期限までずっと効いてしまいますのでご注意ください。

開発環境で頻繁にキャッシュ更新したい場合は、isDeveloperModeEnabled の値を true にすればOKです。

検証について

Firebaseのイベントダッシュボードは、そのままですとリアルタイムで更新されません。開発中ちゃんとイベントが送られたかを検証したい時は、Debug View という便利機能を使うことでイベントや時間などをリアルタイムで観測できます。

Debug View を使う場合は、検証端末のデバッグモードを有効にする必要があります。

adb shell setprop debug.firebase.analytics.app <package_name>

以下のコマンドを実行すれば無効状態に戻ります。

adb shell setprop debug.firebase.analytics.app .none.
Logcatにも記載されます。

最後に

Remote Config を利用することで、おどろくほど簡単にA/Bテストを実施できました。

ついひと月ほど前、Googleは更にA/Bテストを実施する環境のBeta版を公開しました。パラメータ管理やテスト結果の可視化機能などが充実しているとのことです。正式にリリースしたら使い始めようと思います。