Привет, Хабр! Я Александр Непомнящих, QA в СберМаркете. Мы с командой кодим программу лояльности, которая позволяет списывать в заказах бонусы «Спасибо», а также запускать различные акции с повышенным начислением бонусов. 

За последний год проект быстро обрастал новыми фичами. Архитектура: монолит на Ruby + микросервисы на Go. Для безопасного внедрения многие изменения активировались фича-флагами, разрасталась ролевая модель, фичей становилось ещё больше. Всё это увеличивало количество действий для подготовки тестового стенда, их количество обычно достигало 9. Да, нужно было совершить девять разных операций, чтобы подготовить стенд к работе с бонусами.

Далее я расскажу, как мы в итоге сократили этот процесс до 1 команды в Rails-консоли.

А в чем, собственно, проблема?

Пока проект был небольшой, вся настройка тестового окружения фактически сводилась к выдаче текущему пользователю уникального идентификатора, привязанного к определенному статусу:

  • зарегистрирован и имеет положительный баланс;

  • зарегистрирован, баланс 0;

  • не зарегистрирован (доступна регистрация);

  • заблокирован (недоступна регистрация);

Проект был запущен в середине 2022 года, до этого времени пользователи могли списывать и копить бонусы только с картами Сбербанка, далее — по любым картам и с разными способами оплаты (картой онлайн, курьеру и др.) За последний год проект оброс новыми фичами:

  1. Появилась возможность списывать в определенных ритейлерах до 99% бонусами (на старте проекта было только до 10%).

  2. Особые промоакции, например: 

    • начисления на алкоголь — ранее код работал таким образом, что если в заказе есть алкоголь, любые операции с бонусами были запрещены;

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

  3. Начисления по заказам были отложены на сутки (продуктовые заказы) либо 15 дней (электроника), чтобы уменьшить риск фрода.

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

Приведу пример: чтобы прогнать на стенде задачу «Отложенные начисления в ритейлере А», нужно было:

  1. Выдать пользователю идентификатор registered.

  2. Активировать фича-флаг delayed_accruals.

  3. Выдать пользователю нужные роли, например для проведения возвратов заказа.

  4. Создать на стенде нужные промоакции.

  5. Добавить ритейлера А в конфиг, где хранятся id ритейлеров по которым начисления будут работать отложенно.

  6. Настроить время в минутах, через которое должно сработать начисление (для прода это 1440, для стенда достаточно 2 минут).

  7. Настроить баннер на чекауте, который будет предупреждать пользователя, что в текущем заказе бонусы будут начислены через N времени.

  8. Настроить в ритейлере А списание 99%.

  9. Выдать пользователю подписку.

Итого, 9 действий на каждый деплой (собственно, настройка тестового окружения). Далее нужно было создать несколько тестовых заказов:

  1. Авторизоваться на сайте.

  2. Накидать товары в корзину.

  3. На чекауте выбрать время доставки, политику замен, способ оплаты, адрес доставки и т.д.

  4. Оформить заказ (на этом этапе происходит холдирование денег и бонусов)

    Далее нужно было этот тестовый заказ довести до статуса «Доставлено», чтобы джоба которая отвечает за начисление бонусов, запланировалась на выполнение через N времени:

    • сначала заказ берется в сборку;

    • затем берутся все товары в заказе, им проставляется статус «Собрано», сборка завершается;

    • далее происходит окончательная оплата заказа и списание бонусов;

    • после заказ «доставляется» и запускается джоба начисления.

Настройка легко могла занимать до получаса, а если в день проверяешь 3-4 задачи, то и до двух часов. Случалось, что я полчаса настраивал окружение ради десятиминутной проверки задачи.

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

Код, решающий проблему

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

return Failure(message: "No way you're doing it in production") if 
Rails.env.production?

Далее объявили список фича-флагов, которые нужно включить при каждом вызове команды:

FEATURES_LIST = %i[
  flag1 
  flag2 
  flagN
].freeze

Далее описывали методы отдельно для каждого нужного действия. Активируем фича-флаги из списка:

def enable_flags
  FEATURES_LIST.each { |feature| FeatureToggle.enable!(feature) }
end

Выдаем пользователю нужные роли (если смысл задачи не в проверке ролевой системы, можно выдать все существующие).

def add_roles(user)
  user.roles = Role.all
end

Устанавливаем пользователю идентификатор в программе лояльности (registered), предварительно удалив все старые, если они были.

def update_auth(user)
  user.user_authentications.destroy_all
  user.user_authentications.create!(
    provider: 'provider_name',
    uid: external_service_id,
    payload: { uid: external_service_id }
  )
end

Создаем базовую промоакцию (и другие при необходимости):

def create_basic_promotion!
  Loyalty::AccrualPromotion.find_or_create_by!(kind: 'basic') do |promo|
    promo.name = 'Базовое начисление'
    promo.calculator =
      'AccrualCalculator'
    promo.state = 'active'
    promo.percent = 1
    promo.display_name = 'Базовое'
  end
end

Устанавливаем процент списания для первого ритейлера:

def max_spend_percent_for_first_retailer
  ChargeSettings.create!(
    starts_at: 1.year.ago,
    percent: 99,
    retailer_id: Retailer.first.id
  )
end
В итоге код имеет следующий вид:
FEATURES_LIST = %i[
  flag1 
  flag2 
  flagN
].freeze


option :user_id
option :loyalty_id


def call
  return Failure(message: "No way you're doing it in production") if Rails.env.production?


  user = Spree::User.find(user_id)
  enable_flags
  add_roles(user)
  update_auth(user)
  create_basic_promotion!
  max_spend_percent_for_first_retailer
  Success(message: "Loyalty is enabled! user id = #{user_id}; service id = #{loyalty_id}")
end

Как подготовить тестовое окружение одной командой

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

Вызвать класс EnableForTesting на тестовом окружении можно 2 способами:

  • из rails console;

  • из bash.

Чтобы быстро подключиться к rails console или bash мы используем kubectl + скрипт kch, за который отдельное спасибо моему коллеге Алексею. Скрипт является набором алиасов для часто используемых у нас команд kubectl. 

Вызов команды производится в формате kch namespace pod cmd. Например, вместо длинной команды kubectl exec -n stand-0000 -i -t -c app api-pod-ffffff-0000 -- rails c можно написать короткую kch 3003 api rails, которая будет трансформирована в вышеописанную длинную, т.е. нам не нужно знать полные имена подов приложения —  достаточно знать номер стенда и куда хотим подключиться (rails, bash, mysql). 

Скрипт в общем списке kubectl найдет полное название неймспейса и пода в нем по соответствующим вхождениям, подставит их в шаблон команды для вызова, после чего выполнит получившуюся команду.

Вызов из bash происходит командой:

rake 'loyalty:enable_for_testing[user_id, loyalty_id]'

Вызов из rails console:

Loyalties::Tasks::EnableForTesting.call(user_id: 12345, loyalty_id: 'abcdef')

После вызова класса для пользователя с id = 12345 будут последовательно выполнены все команды по настройке тестового окружения. Второй аргумент (loyalty_id) является опциональным: если не передавать его, будет установлен loyalty_id по умолчанию (со статусом registered). Если нужен другой статус — передаём нужный loyalty_id и он привязывается к тестовому пользователю.

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

После выполнения видим результат:

=> Success({:message=>"Loyalty is enabled! user id = 12345; loyalty_id ="abcdef"})

Готово, мы только что сэкономили полчаса жизни :)

Заключение

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

Также обязательно пишем статью во внутренней базе знаний, как пользоваться нашими рейк-тасками, чтобы QA из других команд могли сами всё настроить, если нужно проверить какой-то функционал с бонусами. Тем самым сокращаем время на коммуникации. 

Теперь настройка занимает меньше минуты, а QA занимается непосредственно проверкой кода. При появлении новых фич (а значит фича-флагов и прочих артефактов) я делаю merge request в проект и добавляю нужный код, поддерживая инструмент в актуальном состоянии. Да, у нас QA тоже деплоят в продакшн :)

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

Поделитесь, как вы настраиваете тестовое окружение в своих командах?

Tech-команда СберМаркета ведет соцсети с новостями и анонсами. Если хочешь узнать, что под капотом высоконагруженного e-commerce, следи за нами в Telegram и на YouTube. А также слушай подкаст «Для tech и этих» от наших it-менеджеров.

Комментарии (0)