Хабрахабр

Покрываем A/B-тесты UI-тестами. Как не запутаться в собственном коде

Привет, Хабр!

Решением одного такого вопроса я и хочу поделиться в этой статье. Меня зовут Виталий Котов, я работаю в компании Badoo и бо́льшую часть времени занимаюсь вопросами автоматизации тестирования.

Я расскажу о том, с какими проблемами мы столкнулись и к какому флоу пришли в итоге. Речь пойдёт о том, как мы организовали процесс работы UI-тестов с A/B-тестами, коих у нас немало. Добро пожаловать под кат!

Пока не начали...

В этой статье очень часто встречается слово «тест». Всё потому, что мы говорим одновременно и про UI-тесты, и про A/B-тесты. Я старался всегда разделять эти два понятия и формулировать мысли так, чтобы текст читался легко. Если где-то я всё же упустил первую часть слова и написал просто «тест», я имел в виду UI-тест.

Приятного чтения!

Что такое A/B-тесты

Итак, давайте прежде всего определимся с понятием A/B-теста. Вот цитата из Википедии:

A/B testing, Split testing) — метод маркетингового исследования, суть которого заключается в том, что контрольная группа элементов сравнивается с набором тестовых групп, в которых один или несколько показателей были изменены, для того, чтобы выяснить, какие из изменений улучшают целевой показатель» Ссылка. «A/B-тестирование (англ.

Я бы выделил несколько вариантов: В терминах нашего проекта наличие A/B-теста подразумевает, что какой-то функционал различен для разных пользователей.

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

Чтобы вся эта логика работала, у нас в компании есть инструмент, который называется UserSplit Tool, и о нём подробно рассказывал наш разработчик Ринат Ахмадеев в этой статье.

Мы же сейчас поговорим о том, что значит наличие A/B-тестов для отдела тестирования и для автоматизаторов в частности.

Покрытие UI-тестами

Когда мы говорим о покрытии UI-тестами, мы не говорим о количестве строк кода, которые мы протестировали. Это и понятно, ведь даже просто открытие страницы может задействовать много компонентов, в то время как мы ещё ничего не протестировали.

Не буду перечислять их все, лишь скажу, что мы предпочитаем оценивать этот показатель по количеству фич, которые покрыты UI-тестами. За много лет работы в сфере автоматизации тестирования я видел немало способов измерять покрытие UI-тестов. Это не идеальный способ (а идеального лично я и не знаю), но в нашем случае он работает.

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

Как фичи покрывались UI-тестами изначально

Ещё до того как в компании появился инструмент UserSplit Tool и A/B-тестов стало по-настоящему много, мы придерживались следующей стратегии покрытия фич UI-тестами: покрытие только тех фич, которые уже какое-то время находятся на продакшене и устоялись.

А ещё она и вовсе могла не зарекомендовать себя и довольно быстро пропасть с глаз пользователей. А всё потому, что раньше, когда фича только попадала на продакшен, она какое-то время ещё «тюнилась» — могли измениться её поведение, внешний вид. Писать UI-тесты для нестабильных фич — дело затратное и у нас не практиковалось.

У каждого A/B-теста была так называемая «контрольная группа», то есть группа, которая видела какое-то дефолтное поведение фичи. С внедрением в процесс разработки A/B-тестов поначалу ничего не изменилось. Всё, что надо было сделать при написании UI-тестов для такой фичи, — не забыть включить пользователю именно дефолтное поведение. Именно на него и писались UI-тесты. force). Мы этот процесс называем форсом A/B-группы (от англ.

На описании форса остановлюсь подробнее, так как он ещё сыграет роль в моём повествовании.

Force для A/B-тестов и QaAPI

Мы уже не раз в своих статьях и в докладах рассказывали про QaAPI. Тем не менее, как ни странно, до сих пор мы не написали полноценную статью про этот инструмент. Вероятно, однажды этот пробел будет заполнен. А пока можно посмотреть видео выступления моего коллеги Дмитрия Марущенко: «4. Концепция QaAPI: взгляд на тестирование с другой стороны баррикад».

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

Вызов в тесте выглядит примерно так: При помощи того же QaAPI мы умеем форсить группу A/B-теста; достаточно только указать название теста и название желаемой группы.

QaApi::forceSpliTest(“Test name”, “Test group name”, );

Последним параметром мы указываем user_id или device_id, для которого этот форс должен начать работать. Параметр device_id мы указываем в случае неавторизованного пользователя, так как параметра user_id тогда ещё нет. Всё верно, для неавторизованных страниц у нас тоже бывают A/B-тесты.

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

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

Зачем покрывать A/B-тесты

Проблема первая — покрытие

Помимо контрольного, у каждой фичи есть ещё один, два или три других варианта. Как я написал выше, со временем почти все новые фичи стали выходить под A/B-тестами. Раньше, когда таких фич было мало, это не оказывало существенного влияния на суммарный показатель покрытия. Выходит, для такой фичи покрытие в лучшем случае не превысит 50%, а в худшем — будет примерно 25%. Теперь — стало оказывать.

Проблема вторая — долгие A/B-тесты

А мы продолжаем релизиться два раза в день (об этом можно почитать в статье нашего QA-инженера Ильи Кудинова «Как мы уже 4 года выживаем в условиях двух релизов в день»). Некоторые A/B-тесты сейчас занимают довольно длительное время.

А это обязательно скажется на user experience и сведёт на нет весь смысл A/B-тестирования фичи: ведь фича может показывать плохие результаты на какой-то из версий не потому, что она не нравится пользователям, а потому, что не работает как положено. Таким образом, вероятность сломать какую-то из версий A/B-теста за это время невероятно большая.

Если мы хотим быть уверены в результате A/B-тестирования, нельзя допускать, чтобы какая-то из версий фичи работала иначе, чем от неё ожидается.

Проблема третья — актуальность UI-тестов

Это означает, что A/B-тест собрал достаточное количество статистики и продакт-менеджер готов открыть победивший вариант для всех пользователей. Есть такое понятие, как релиз A/B-теста. Релиз A/B-теста происходит асинхронно с релизом кода, так как зависит от настройки конфига, а не от кода.

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

Значит, нужно быть готовым к закрытию любого A/B-теста заранее, чтобы оно не помешало работоспособности UI-тестов и, как следствие, очередному релизу билда.

Вывод

Логично? Вывод из вышесказанного напрашивается очевидный: надо покрывать A/B-тесты UI-тестами целиком, все варианты. Всем спасибо, расходимся! Да!

Не всё так просто. … Шутка!

Интерфейс для A/B-тестов

Первое, что показалось неудобным, — контроль над тем, какие A/B-тесты и варианты фич уже покрыты, а какие — ещё нет. Исторически сложилось так, что мы называем UI-тесты по следующему принципу:

  • название фичи или страницы;
  • описание кейса;
  • Test.

Например, ChatBlockedUserTest, RegistrationViaFacebookTest и так далее. Пихать сюда ещё и название сплит-теста показалось неудобным. Во-первых, названия стали бы невероятно длинными. Во-вторых, тесты пришлось бы переименовывать по завершении A/B-теста, а это плохо сказалось бы на сборе статистики, которая учитывает название UI-теста.

Грепать же всё время код на вызов QaAPI-метода — то ещё удовольствие.

Для неё мы сделали UI-представление на Selenium Manager (о нём я рассказывал тут). Так что мы решили убрать все вызовы QaApi::forceSplitTest() из кода UI-тестов и перенести данные о том, где какие форсы нужны, в MySQL-табличку.

Выглядит это примерно так:

Можно указать название самого UI-теста, класса тестов или All. В таблице можно указать, для какого UI-теста форс какого A/B-теста и в какой группе мы хотим применить.

Помимо этого, мы можем указать, распространяется этот форс на авторизованных или на неавторизованных пользователей.

Далее мы научили UI-тесты при запуске получать данные из этой таблицы и форсить те, которые имеют отношение непосредственно к запущенному тесту или ко всем (all) тестам.

Теперь список покрытых A/B-тестов удобен для просмотра. Таким образом нам удалось собрать все манипуляции A/B-тестами в одном месте.

Там же мы создали форму для добавления новых A/B-тестов:

д. Всё это позволяет легко и быстро добавлять и убирать необходимые форсы без создания коммита, ожидания, когда он разложится по всем клаудам, где запускаются UI-тесты, и т.

Архитектура UI-тестов

Второе, чему мы решили уделить внимание, — пересмотр подхода к написанию UI-тестов для A/B-тестов.

Архитектура довольно простая и привычная: В двух словах расскажу, как мы пишем обычные UI-тесты.

  • классы-тесты — там описана бизнес-логика покрываемой фичи (по сути, это сценарии наших тестов: сделал то-то, увидел то-то);
  • PageObject-классы — там описаны все взаимодействия с UI и локаторы;
  • TestCase-классы — там находятся общие методы, которые не касаются непосредственно UI, но могут пригодиться в нескольких классах-тестах (например, взаимодействия с QaAPI);
  • core-классы — там находятся логика поднятия сессии, логирование и другие вещи, которые при написании обычного теста трогать не надо.

В целом эта архитектура нас полностью устраивает. Мы знаем, что, если изменился UI, надо менять только PageObject-классы (при этом сами тесты не должны быть затронуты). Если же поменялась бизнес-логика фичи, меняем сценарий.

Чем этот процесс проще и понятнее, тем чаще тесты будут запускать люди, не имеющие к ним непосредственного отношения. Как я писал в одной из предыдущих статей, с UI-тестами у нас работают все: и ребята из отдела ручного тестирования, и разработчики.

Если мы будем писать их в том же формате, что и обычные UI-тесты, придётся постоянно удалять код из множества разных мест после завершения A/B-тестов. Но, как я писал выше, в отличие от устоявшихся фич, A/B-тесты то приходят, то уходят. Сами понимаете, на рефакторинг, особенно когда и без него всё работает, не всегда удаётся выделить время.

Как бы облегчить себе жизнь? Тем не менее позволить нашим классам зарастать неиспользуемыми методами и локаторами не хочется, это сделает те же PageObject’ы сложными для использования.

Тут нам на выручку пришёл PhpStorm (спасибо ребятам из JetBrains за удобную IDE), а именно вот эта фича.

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

В итоге код теста выглядел примерно так:

Наверняка и в других IDE есть нечто подобное. В каждом регионе находится код, который относится к тому или иному классу.

А после его завершения мы сначала удаляли из класса проигравшие варианты, а затем довольно легко разносили оставшийся код по нужным классам в соответствии с тем, что указано в регионе. Таким образом, мы одним классом-тестом покрывали все варианты A/B-теста, помещая туда же и PageObject-методы, и локаторы.

Как мы теперь закрываем A/B-тесты

Нельзя просто так взять и разом покрыть все A/B-тесты UI-тестами. С другой стороны, и задачи такой нет. Задача с точки зрения автоматизации состоит в том, чтобы быстро покрывать только важные и долгоиграющие тесты.

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

Дело в том, что, если добавить туда форс, он сразу начнёт включаться для всех UI-тестов. Упомянутое выше решение с MySQL-таблицей для этой цели не подходит. С результатами тех запусков будут работать коллеги из отдела ручного тестирования. Помимо стейджинга (нашего предпродакшен-окружения, где мы запускаем полный набор тестов), это повлияет ещё и на UI-тесты, запущенные против веток отдельных задач. Из-за этого на тестирование и разбирательство может уйти много времени (никто не будет доволен). И если зафоршенный A/B-тест имеет баг, тесты для их задач тоже упадут и ребята могут решить, что проблема в их задаче, а не в A/B-тесте.

Мы пока обошлись минимальными изменениями, добавив в таблицу возможность указывать целевое окружение:

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

Подводим итоги

Итак, до начала всей этой истории наши UI-тесты покрывали только основные (контрольные) группы A/B-тестов. Но мы поняли, что хотим большего, и пришли к выводу, что покрывать другие варианты A/B-тестов тоже необходимо.

В итоге:

  • мы создали интерфейс для удобного контроля над покрытием A/B-тестов; в результате теперь у нас есть вся информация о работе UI-тестов с A/B-тестами;
  • мы выработали для себя способ написания временных UI-тестов с простым и эффективным флоу их дальнейшего удаления или перевода в ряды постоянных;
  • мы научились легко и безболезненно тестировать релизы A/B-тестов, не мешая другим запущенным UI-тестам, и без излишних коммитов в Git.

Всё это позволило адаптировать автоматизацию тестирования под постоянно меняющиеся фичи, легко контролировать и увеличивать уровень покрытия и не зарастать легаси-кодом.

Делитесь им в комментариях. А у вас есть опыт приведения на первый взгляд хаотичной ситуации к какому-то контролируемому порядку и упрощения жизни себе и коллегам? 🙂

И с наступающим Новым годом! Спасибо за внимание!

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть