🤖

【Flutter】Firebase Remote Config でA/Bテストしよう

2023/12/17に公開

Flutterでなくてもよいのですが、Flutterが好きなので...。
Flutterで、Firebase Remote Configを使用してA/Bテストをする方法についてまとめます。

概要

Firebase Remote Config をざっくりまとめると、リモート(Firebase)に定義したパラメータ値を取得するサービスです。そのパラメータ値をアプリが取得するとき、ユーザーごとにランダムに値が割り当てることができます。
例えば、Bool型のパラメータ値の場合、trueとfalseの2パターンのどちらかがユーザーごとに割り当てられます。そして、パラメータ値が割り当てられるユーザーの割合をコントロールすることができます。例えば、50%のユーザーにはtrue、あとはfalse、というような具合です。

これを使用してA/Bテストができます。

アプリ側のアーキテクチャを考える

ここでは、UIの配置が変わったりと、値の変更だけでは対応できない場合を想定しています。
値の変更だけで対応できるのは、たとえば、ボタンの色だけ、文言だけ違うなどの場合です。そのような場合には、Remote Configにその値のパターンを設定して、取得できた値をそのまま使用するだけでよいと思います。

ネイティブアプリで、UI配置を変える場合などをA/Bテストしたい場合、新機能が含まれているコードと既存のコードが混在してしまいます。そこで自分の開発しているアプリでは以下のようにしました。

.
├── features
│   ├── ab_testing
│   │   ├── new_feature
│   │   │   └── home
│   │   │       ├── pages
│   │   │       │   └── ab_home_page.dart
│   │   │       └── widgets
│   │   │           └── ab_feature_button.dart
│   ├── home
│   │   ├── pages
│   │   │   └── home_page.dart
│   │   └── widgets
│   │       └── feature_button.dart

A/Bテストで実装を分ける際に、ファイルごとコピーして、そのWidgetの呼び出し元で出しわけをします。
わかりやすいようにA/Bテストの新機能の方はファイル名にプレフィックス ab_ を付けています。
そしてコピーしたファイルを修正します。

各ディレクトリの意味は以下です。

  • features/home
    → 既存の実装。従来の機能を利用するグループ(コントロールグループ)に配信。
  • features/ab_testing/new_feature/home
    → 新しい実装。修正した機能を利用するグループ(テストグループ)に配信。

既存の実装側で、Remote Configのパラメータ値を見て、動的に新しい実装(Widget)に差し替えるようにしました。

この方法のメリットデメリットは以下かと思います。

メリット

A/Bテストの結果がわかり、どちらかパターンに決定する際に修正が簡単。たとえばテストグループ(新しい実装)が採用された場合、 ab_〜系のファイルの中身を、既存実装のファイルの中身に差し替えるだけでOKです。コントロールグループ(既存の実装)が採用された場合、ab_〜系のファイルを全部消して、呼び出し元の切り分け処理を削除するだけです。

デメリット

もしA/Bテスト中に修正を加えたい、となって、その修正がA/Bテストをしているファイルに含まれる場合、既存と新しい実装両方に両方に手を加える必要があり、少し面倒です。ですが、基本的にA/Bテストする変更点は1点だけという原則があるので、このような理由で修正必要なことはあまりないかなと思います。

実装とA/Bテスト実施

実施方法を簡単にまとめます。

Remote Configの設定を初期化

まずはRemoteConfigの設定を初期化します。

await FirebaseRemoteConfig.instance.setConfigSettings(
  RemoteConfigSettings(
    fetchTimeout: const Duration(minutes: 1),
    minimumFetchInterval: kReleaseMode
      ? const Duration(hours: 1)
      : const Duration(minutes: 1),
  ),
);

本番と開発で最小フェッチ間隔を変えています。値変更をリアルタイム更新するのが推奨されているようなのですが、それをしない場合にも、開発モードでは短時間に何度も値をフェッチできるようにしています。

フェッチしたパラメータ値が、アプリで使われるかどうかは、その優先順位によります。
https://firebase.google.com/docs/remote-config/parameters?hl=ja#parameter_value_priority

ここでは、フェッチするときのタイムアウト時間と時間間隔を設定しています。フェッチ時間間隔以内に、何回フェッチ処理を呼び出しても、フェッチしないようです。

デフォルト値をセットする

図のように true / false だけのパラメータを1件だけ登録しているとします。
50%のユーザーはデフォルト値、残りの50%のユーザーは true を使用するという設定です。

このデフォルト値をアプリ側で登録します。タイミングは前項のRemote Configの設定初期化の直後とかでよいと思います。

await FirebaseRemoteConfig.instance.setDefaults({
  "feature_parameter_flag": false,
});

値のフェッチとアクティベートをする

fetchAndActivate というメソッドで、フェッチしてすぐ有効化してくれます。
その後、具体的なパラメータ値を取得すると(ここでは feature_parameter_flag )、ランダムに割り振られた値が取得できます。

try {
  final rc = FirebaseRemoteConfig.instance;
  // リモート設定を取得して適用
  final activated = await rc.fetchAndActivate();
  log('RemoteConfig activated: $activated');
  // リモート設定を返す
  return RemoteConfig(
    parameter: rc.getBool("feature_parameter_flag"),
  );
} catch (e, s) {
  log(e.toString(), stackTrace: s);
}
// 失敗したのでデフォルト値を返す
return RemoteConfig(parameter: false);

リアルタイムで値の更新を取得することもできるようですが、ここでは省略します。

A/Bテストの結果を観察する

A/Bテストの結果をみていて、なんだこれ?、と思ったのは「p値」です。

それでp値を調べてみると、これがまた結構わかりにくいのですが、こちらのサイトはとてもわかりやすかったです。
https://kyozon.net/list/marketing/lpo/article/5e4e492a0c97a917232343/

まず前提として、A/Bそれぞれのパターンの結果に違いがない(UIは違うけどコンバージョン率は同じなど)と仮定します。

その前提で、テストした結果、Bパターンの方がコンバージョン率が15%よかったなど、違いが出たとします。
その違いが偶然出る確率のことをp値と言います。

この確率(=p値)が5%未満のとき、ほとんど偶然ではないので(≒必然)、A/Bテストの変更に効果があると判定していい、ということだそうです。数学の背理法という考え方だそうで、わかりにくいですね...。

とりあえず、上記のA/Bテストのp値が0.05未満になったら、A/Bテストを終了してどちらかのパターンに決定していいということです。

その他の参考サイト

https://firebase.google.com/docs/remote-config/get-started?hl=ja&platform=flutter
https://dcross.impress.co.jp/docs/column/column20200722/001844.html

Discussion