ロコガイド テックブログ

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

Swiftで書く シンプルなA/Bテスト

f:id:hatuyuki4:20201113182951p:plain

こんにちは@hatuyuki4です。この記事はロコガイド Advent Calendar6日目です。健康診断結果がすこぶる悪かったので、お魚中心の食生活を送っています。

トクバイでは新機能をリリースしたり、既存機能を改善したりする際に、A/Bテストをしながらユーザに公開することが多いです。今回はモバイルアプリでスムーズにA/Bテストを行うために、実施しているtipsについて書きたいと思います。

モバイルアプリでのA/Bテスト

モバイルアプリでA/Bテストを行おうと思ったら、まずFirebaseの A/B Testingの利用を検討すると思います。しかし A/B Testingは登場して以来β版が続いていますし(2020年12月現在)、計測まで時間がかかることもあり、なかなかスピーディな検証をするのには向いていません。ただし、数日後の定着率を自前で計測するにはログ計測基盤を自前で用意する必要があるため、その辺りのコストとのバランスを考えると有効なツールともいえます。

そのためトクバイでは、アプリ側に一工夫入れることでシンプルで使いやすいA/Bテスト機能を実装しています。

シンプルなA/Bテスト

トクバイでは A/B Testingではなく通常の Firebase RemoteConfigを利用してA/Bテストを行っています。通常RemoteConfigでのA/BテストはParameterが際限なく増えていったり、Conditionsの管理が煩雑になったりと難点が多いのですが、アプリ側にも実装を加えることで、シンプルで扱いやすいA/Bテストを実現できます。

1. Remote Config上にA/Bテストの条件だけをjsonで記載する
2. 1つのjsonに複数のTestGroup(テスト条件)を作成する
3. アプリ内で疑似乱数を生成し、保存する
4. Remote Configから取得したTestGroupの条件から、アプリでパターンの出し分けを行う

RemoteConfig実装例

RemoteConfigでは1つのParameterに辞書型の配列を用意しておきます。titleは行うA/Bテストを識別するためのもの、scopeは対象ユーザの設定(後述)、targetsにA/Bテストで出し分ける条件を書きます。targets内のrateは合計で100になるように記述します。これがそのまま何%のユーザに公開するのかという値になります。また合計が100であれば3つ以上同時にテストを行えます。

RemoteConfigのConditionsでは公開範囲をいじらず、全ユーザに同じ配列を渡します。これにより、Parameterも1つですみ、RemoteConfig側の設定が煩雑になることを防げます。

[
  {
    "title": "map_search",
    "scope": "all",
    "targets": [
      {
        "target": "A",
        "rate": 50
      },
      {
        "target": "B",
        "rate": 50
      }
    ]
  }
]

アプリ実装例

RemoteConfigからJsonを受け取ったら、アプリ側では1つのA/BテストをABTestingGroup、 A/Bテストの掲出条件をABTestingTargetという構造体に変換します。Scopeには現在allnew_userのみを受け付けているので、この2つの値を定義した列挙型で持たせておいてます。(Scopeの利用については後述します)。

struct ABTestingGroup: Codable {

    enum Scope: String, Codable {
        case all
        case newUser = "new_user"
    }

    let title: String
    let scope: Scope
    let targets: [ABTestingTarget]
    
    // 1~100の値がどのターゲットに属するのか返す
    func testingTarget(random: Int) -> String? {
        var targetCount = 0
        for testingTarget in targets {
            targetCount += testingTarget.rate
            if random <= targetCount {
                return testingTarget.target
            }
        }
        return nil
    }
}

struct ABTestingTarget: Codable {
    let target: String
    let rate: Int
}

そして取得できたABTestingGroupごとに 1~100までの乱数を生成し、それをabtesting_\(title)という名前でUserDefaultsに保存します(titleは利用したユーザグループから取ってきます)。そう、RemoteConfig側で掲出制御をするのではなく、アプリ側で振り分けるようにしているのですね。

乱数を保存したら、その乱数がTestTargetのどのスコープに収まるのかチェックして、出し分けを判定するという流れになります。

// Testing Groupごとに1~100までのランダムな値を保存する
private func setRandomValue(_ title: String) {
    setValue(title, value: Int.random(in: 1...100))
}

// Testing Groupのtitleを使って値を保存する
func setValue(_ title: String, value: Int) {
    UserDefaults.standard.set(value, forKey: "abtesting_\(title)")
}

// Testing Groupのtitleを使って値を取得する
func randomValue(_ title: String) -> Int {
    return UserDefaults.standard.integer(forKey: "abtesting_\(title)")
}

// どのターゲットかどうか判定する
func isTarget(_ title: String, target: String) -> Bool {
    guard let testingGroup = testingGroups.first { $0.title == title }
    let value = randomValue(title)
    guard let testingTarget = searchGroup(by: title)?.testingTarget(random: randomValue(title)) else { return false }
    return testingTarget.target.rawValue == target.rawValue
}

Advance!

デバッグ機能

アプリの検証をしていると、A/Bテストの各パターンを素早く切り替えて動作確認したくなると思います。今回ご紹介した例では、A/Bの判定ロジックをRemoteConfigやバックエンドではなくアプリ内部に実装しているため、パターンの切り替えもアプリ内のUserDefaultsの値を変更するだけでできてしまいます。

そのためトクバイアプリの開発メニューにもパターン切り替え機能が実装されており、誰でもすべてのパターンを素早くチェックできるようになっています。

f:id:hatuyuki4:20201113181314p:plain

(UserDefautlsの値を0~100の値に動かすだけなので、Sliderを利用しています)

新規ユーザ限定リリース

時には、A/Bテスト内容を既存ユーザに提示したくない場合があります。例えばオンボーディングに関する施策であるとか、ドラスティックな変更であるとかを、新規にインストールしたユーザにのみ提示したい時などです。そのため先述の scope を使い、アプリ内で新規ユーザかどうかを判定し、その50%にだけ公開するという機能も実装しています。これはRemote ConfigのConditionsだけでは出し分けできませんね。

ちなみに、新規ユーザかどうかというのは、ユーザがアプリを初めて起動した時のバージョンを内部で保持し、それと現在のバージョンとを比較しています。これにより大掛かりな検証も、ためらわず行うことができます。

まとめ

新しい機能をリリースするとき、それがユーザにとっていいものかどうかは実際に出してみないとわからないのが実情です。そんな中でやるべきことは、実際にA/Bテストを行って、仮説が正しいのか間違っていたのか考察するサイクルを可能な限り早めることです。

ロコガイドでは今後も、ユーザが使いやすくなるようにA/Bテストなどを繰り返しながら、よりよいアプリをリリースしていきたいと思います。