A/B тестирование в Android-приложениях

A/B тестирование в Android-приложениях

Наверняка у каждого разработчика/заказчика возникало по несколько идей реализации того или иного функционала, интерфейса или чего он там еще мог придумать. Проблема в том, что сразу же выбрать подходящий основной массе пользователей вариант реализации, мягко говоря, проблематично... И если в уже состоявшемся проекте еще можно на основе аналитики предположить о предпочтениях своих пользователей, то на старте остается только гадать. Но в таком нелегком выборе нам поможет проведение A/B тестирования.

A/B тестирование — это крайне необходимый инструмент современного маркетинга, он позволяет определить, какой вариант UI является наиболее эффективным для большинства пользователей. А еще с помощью данного инструмента можно балансировать разного типа константы в ваших приложениях, к примеру, в играх можно производить балансировку уровней сложности.

Я долго искал способ реализации данного вида тестирования для мобильных приложений, даже пытался построить велосипед, а все потому, что, как мне кажется, Google плохо описывает прячет возможности своих сервисов в кипе устаревшей документации и тому подобного. Когда мне наконец-то удалось откопать материалы по Google Tag Manager, счастью моему не было предела. Он предложил столько много новых возможностей что стало даже страшновато.

A/B тестирование в Android-приложениях

Google Tag Manager дает возможность изменять настройки приложения, не прибегая к изменениям в коде и последующей компиляции и распространению проекта между тестерами/пользователями. Это осуществляется благодаря так называемым «контейнерам», которые хранят несколько вариантов карт с настройками, и одна из них обязательно является дефолтной. Приложение при первом старте, имея доступ к сети, подтягивает те настройки, которые будут соответствовать одной из тестовых групп пользователей. Пользователей можно делить на группы в процентном соотношении, а также указать процент пользователей, которые будут принимать участие в тестировании, от общего количества пользователей ресурса или приложения.

Как же нам интегрировать сие благо в проект?

По сути не так уж и сложно. Для начала произведем некие манипуляции со своими аккаунтами. Чтобы отслеживать результаты эксперимента, нам понадобится аккаунт Google Analytics, для реализации самого контейнера нужен аккаунт Google Tag Manager. В Google Analytics нам необходимо создать представление для своего приложения (если это ранее не было сделано) и определить цели для эксперимента в разделе «Цели». По сути, это все, что будет касаться аналитики.

A/B тестирование в Android-приложениях

Далее авторизуемся в Google Tag Manager и создаем аккаунт, под которым в дальнейшем будут создаваться контейнеры для наших приложений. Уже в аккаунте создадим контейнер:

A/B тестирование в Android-приложениях

A/B тестирование в Android-приложениях

По завершении нам предложат воспользоваться устаревшим sdk, мы любезно от него откажемся, так как все инструменты для работы с контейнером уже внедрены в Google Play Services lib, которую мы будем использовать в своем проекте.

A/B тестирование в Android-приложениях

Прежде всего нам нужно установить связь между своими аккаунтами. В дальнейшем это позволит использовать цели из аккаунта Google Analytics.

A/B тестирование в Android-приложениях

Установив связь, можно приступать к настройке макроса. Макрос, в данном случае, является набором правил, согласно которым наш контейнер становится доступным для пользователей, либо деактивируется согласно правилам распространения и вариаций данных.

Давайте создадим простой макрос для воображаемой игры. Допустим, он будет делить нашу огромную аудиторию на группы в равных соотношениях, предоставляя им при этом разные настройки баланса противников:

Медленные, но стойкие враги

slow_hard
{'enemy_speed':'20'}
{'enemy_life':'8'}

Быстрые, но слабые враги

fast_easy
{'enemy_speed':'80'}
{'enemy_life':'2'}

И для любителей измываться над своими нервами, быстрые и стойкие враги

fast_hard
{'enemy_speed':'80'}
{'enemy_life':'8'}

Соответственно, каждая группа пользователей будет по-разному реагировать на предоставленный им вариант балансировки игры. Проанализировав их поведение на примере достижения тех или иных целей (к примеру, проведенное время в игре или возвраты пользователей), мы можем определить лучший вариант, который будет больше остальных соответствовать нашим целям.

A/B тестирование в Android-приложениях

A/B тестирование в Android-приложениях

Далее нужно создать правило, по которому будет активироваться макрос — то есть проверка на версию, с которой у нас предусмотрена поддержка контейнера в приложении.

A/B тестирование в Android-приложениях

Дополнительно указываем параметры нашего эксперимента — процент от общего количества пользователей, которые будут участвовать в эксперименте, правило распределения трафика и остальные.

A/B тестирование в Android-приложениях

Ну вот, контейнер готов, теперь его нужно как то доставлять к конечным пользователям, и для этого нужно создать версию готового контейнера.

A/B тестирование в Android-приложениях

Создав версию контейнера, в нем уже ничего изменить нельзя, версия готова к публикации, потому загружаем дефолтный вариант для оффлайнового использования, нажав кнопку «Загрузить», и публикуем версию контейнера.

Таким образом, у нас все готово для интеграции контейнера в приложение.

Чтобы начать работу с контейнером непосредственно в приложении, нам понадобится библиотека Google Play Services lib и файл самого контейнера, скачанный после создании его версии. Сразу сделаем папку raw в директории ресурсов нашего проекта и зальем туда файл контейнера.

A/B тестирование в Android-приложениях

Как это работает? Пользователь устанавливает приложение на девайс, в нем уже присутствует контейнер с дефолтными настройками. Некий код пытается обратится к сервису, чтобы получить настройки контейнера. При выполнении запроса пользователь получает обновление контейнера с соответственными настройками, при отсутствии сети или при остальных обстоятельствах в приложении будет использоваться дефолтный контейнер, который мы положили в ресурс raw. Собственно говоря, вот этот некий условный код:

(CONTAINER_ID можно найти у себя в консоле в разделе версий контейнеров.)

TagManager tagManager = TagManager.getInstance(this);
 
        tagManager.setVerboseLoggingEnabled(true);
 
        PendingResult<ContainerHolder> pending =
                tagManager.loadContainerPreferNonDefault(GAExperimentsUtil.CONTAINER_ID,
                        R.raw.gtm_default_container);
 
        pending.setResultCallback(new ResultCallback<ContainerHolder>() {
            @Override
            public void onResult(ContainerHolder containerHolder) {
                if (!containerHolder.getStatus().isSuccess()) {
                    Log.e("Loger", "failure loading container");
                } else {
                    GAExperimentsUtil.setContainerHolder(containerHolder, 					MainActivity.this);
                    Log.i("Loger", "Container Available");
                }
                initFragments();
            }
        }, 2, TimeUnit.SECONDS);

Если вкратце, то здесь все очень просто, инициализируем TagManager, создаем объект PendingResult и загружаем в него контейнер tagManager.loadContainerPreferNonDefault, в калбек в итоге нам вернется ContainerHolder. Проверяем, получили ли мы адекватный ответ, если да — сохраняем холдер в DataLayer для удобства последующего использования в любой части кода. Инициируем приложение с полученным контейнером. В данном варианте "failure loading container" мы получим лишь в том случае, если не выполняется условие активации макроса, или же если мы забыли положить файл контейнера в директорию raw.

Следующий класс написан для удобства работы с холдером контейнера:

public class GAExperimentsUtil {
    public static final String CONTAINER_ID = "GTM-ХХХХХХ";
    public static final String TESTING_GROUP_TYPE_KEY = "balans_type";
 
    public static final String TESTING_GROUP_SLOW_HARD = "slow_hard";
    public static final String TESTING_FAST_EASY = "fast_easy";
    public static final String TESTING_FAST_HARD = "fast_hard";
 
 
    private static ContainerHolder containerHolder;
 
    /**
     * Utility class; don't instantiate.
     */
    private GAExperimentsUtil() {
    }
 
    public static ContainerHolder getContainerHolder() {
        return containerHolder;
    }
 
    public static void setContainerHolder(ContainerHolder c, Context context) {
        containerHolder = c;
        // Put the image_name into the data layer for future use.
        TagManager.getInstance(context).getDataLayer().push(TESTING_GROUP_TYPE_KEY, getType());
    }
 
    private static String getType() {
        return containerHolder.getContainer().getString(TESTING_GROUP_TYPE_KEY);
    }
}

Для получения значения обращаемся к ранее записанному DataLayer из любой части кода:

TagManager.getInstance(this).getDataLayer().get(GAExperimentsUtil.TESTING_GROUP_TYPE_KEY);

Вот и все. Таким образом, мы можем получить любое нужное нам значение и инициализировать на его основе приложение, а в Google Analytics наблюдать за течением эксперимента и достижением целей по каждому из тестовых вариантов.