Remote Config: как тестировать то, что меняется без релиза
Тестер пишет: «Зеркало — дичь. Рандомно прилетает 3к урона, контрплея ноль». Речь о враге в моей idle-игре, который отражает часть урона обратно в башню. На десятом уровне сложности он убивал с одного отражения.
Хорошая новость: все числа врагов лежат в Remote Config, их можно поменять без релиза. Плохая: за следующий час я наступил почти на все грабли, которые бывают с удалёнными конфигами. Этот разбор про них.
Почему Remote Config — отдельная зона риска
Remote Config (Firebase, LaunchDarkly, свой JSON с сервера — неважно) — это код, который едет в прод мимо всего, что обычно защищает прод: без ревью, без сборки, без регресса, без ревью стора. Нажал «Publish» — и через несколько минут поведение у живых игроков другое.
Поэтому вопрос «что тестировать» тут не про сам JSON, а про четыре вещи:
- дойдёт ли изменение до игрока и когда;
- какой версии клиента оно дойдёт и как та версия его поймёт;
- что будет, если конфиг кривой или неполный;
- можно ли этим конфигом реально починить проблему, или числа не спасут.
Грабли 1: старый конфиг перекрывает новый
Типичная схема: в сборку зашит конфиг по умолчанию, а при запуске приложение подтягивает удалённый и кладёт в кэш. Кэш главнее встроенного.
Что у меня сломалось: я выпустил сборку с новыми врагами и новым встроенным конфигом. А у игроков в кэше лежал удалённый конфиг от прошлой версии, без новых врагов. Он был «главнее» и молча перекрыл новый. Обновление вышло, а в игре ничего не поменялось.
Как ловить:
- в каждом конфиге поле
version; - правило в клиенте: удалённый конфиг применяется, только если его версия не ниже встроенной;
- тест: поставь старую сборку, дай ей закэшировать удалённый конфиг, обнови приложение поверх и проверь, какие значения реально в игре.
Последний тест почти никто не делает, потому что QA обычно ставит сборку «начисто».
Грабли 2: когда изменения реально доходят до игрока
Я опубликовал фикс, тестер перезашёл — зеркало всё так же убивает. Причины было две, и обе нормальные:
- Применение со следующего запуска. Свежий конфиг скачивается в фоне, когда игра уже работает, и применяется при следующем старте. Первый перезапуск только скачивает, второй применяет.
- Ограничение частоты запросов. Клиент не ходит за конфигом при каждом старте: в Firebase есть минимальный интервал между запросами, и в релизе его обычно ставят от минут до часов. По умолчанию у Firebase это 12 часов.
Для QA из этого следует простая вещь: в тикете на «починили конфигом» должно быть написано, через сколько и после каких действий игрок увидит изменение. Иначе тестер справедливо пишет «не починили».
Я в итоге переделал так: конфиг применяется сразу, как пришёл, если игрок не в бою. Если в бою — сразу после конца забега. Менять баланс посреди боя опасно: враги на поле ссылаются на типы из старого конфига.
Грабли 3: хотфикс надо проверять на той сборке, что у игроков
Это главный урок дня. Я поправил код (поставил врагу потолок урона), выкрутил числа, прогнал проверку на телефоне — всё отлично. Только проверял я на новой сборке, где уже был потолок. У тестера стояла старая из стора, где потолка нет, и там единственное, что может помочь, — числа из Remote Config.
Как правильно: собрать ровно тот коммит, который сейчас в сторе, поставить на устройство, подтянуть свежий удалённый конфиг и воспроизвести сценарий игрока. Я так и сделал, когда меня поймали. Со старыми числами — «убит зеркалом» на 12-й волне, один в один как у тестера. С хотфиксом — зеркало больше никого не убивает.
Отсюда же правило совместимости: новые поля в конфиге старая сборка просто проигнорирует. Значит, хотфикс для старых клиентов можно делать только теми полями, которые старый код уже читает.
Грабли 4: числа не всегда спасают
Сила отражения зеркала зависела от атаки самого врага, а атака растёт с уровнем сложности. Я мог крутить коэффициенты сколько угодно, но на высоких уровнях формула всё равно уходила в «убить с одного удара». Нужен был потолок от здоровья башни, а его в формуле не было, и через конфиг его не добавить.
Что тестировать заранее:
- у каждой формулы урона и наград есть потолок, который не растёт вместе с уровнем, или это осознанное решение;
- для каждого параметра в конфиге понятно, в каком диапазоне он безопасен;
- инварианты проверяются автотестом: «один удар обычного врага не снимает больше X% здоровья», «награда за волну не превышает Y».
Грабли 5: кривой и неполный конфиг
Кто-то опечатался в JSON, удалил поле или вписал строку вместо числа. Что сделает клиент?
Минимальный набор проверок:
- невалидный JSON — игнорируется, остаётся встроенный конфиг, в логах предупреждение;
- нет поля — берётся значение по умолчанию, а не ноль (ноль у «скорости» или «урона» легко ломает игру);
- неверный тип — не падает;
- пустой ключ в консоли — ведёт себя так же, как отсутствие ключа;
- очень большой или отрицательный параметр — клиент не уходит в бесконечный цикл и не делит на ноль.
Откат, таргетинг и постепенная раскатка
Самое ценное в Remote Config — его можно откатить. Но это работает, только если:
- история версий включена и кто-то знает, как откатиться (в Firebase это в истории шаблона);
- изменения раскатываются не на всех сразу: процент аудитории или отдельная группа, сначала тестеры;
- условия таргетинга проверены: версия приложения, страна, язык. Частая ошибка — условие «версия ≥ X» с опечаткой, и конфиг не доходит вообще ни до кого.
Хорошая общая рамка про виды флагов и их жизненный цикл — статья Мартина Фаулера про feature toggles.
Чек-лист
- В каждом конфиге есть версия, удалённый не перекрывает более новый встроенный
- Апгрейд со старой сборки с закэшированным конфигом проверен
- Известно, когда изменение доходит до игрока: фоновое скачивание, интервал, нужен ли перезапуск
- Конфиг не меняет критичную логику посреди сессии: боя, оплаты, сохранения
- Хотфикс проверен на сборке из стора, а не на ветке
- Хотфикс для старых клиентов использует только поля, которые читает старый код
- У формул урона и наград есть потолки, у параметров — безопасные диапазоны
- Невалидный JSON, пропущенное поле и неверный тип не ломают приложение
- Есть план отката и постепенная раскатка
- Условия таргетинга проверены на реальном устройстве
Remote Config экономит релизы, но не отменяет тестирование. Он просто переносит его туда, где о нём легко забыть: на стык между тем, что ты опубликовал в консоли, и тем, что реально крутится у игрока.