remote-configfeature-flagsmobilereliabilityqa

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 экономит релизы, но не отменяет тестирование. Он просто переносит его туда, где о нём легко забыть: на стык между тем, что ты опубликовал в консоли, и тем, что реально крутится у игрока.