Клиентское приложение на страже стабильности UI-тестов

На девятом митапе VK Tech Talks | QA старший инженер по автоматизации тестирования Тимур Хасанов выступил с докладом о стабилизации UI-тестов внутри приложения.

Клиентское приложение на страже стабильности UI-тестов, image #1

Что такое тесты VK?

Клиентское приложение на страже стабильности UI-тестов, image #2

Большая часть кода в веб-версии «ВКонтакте» написана на JavaScript. Соответственно, тесты для неё тоже написаны на Java. А ещё на Java написаны тесты на Android. В свою очередь мобильный клиент написан на Java для Android и на Kotlin. Для iOS — на Objective-C/Swift, тесты же для iOS написаны на Swift. Суммарно на все приложения у нас более 1000 тестов, и за всеми нужно следить.

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

Обычный день тестировщика

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

Клиентское приложение на страже стабильности UI-тестов, image #3

Выясняется, что один из модулей не отработан — музыка не отрисовалась. Тут у человека возникает миллиард мыслей о том, что могло пойти не так за выходные, и приходит к выводу, что сломался бэкенд. Тестер пытается с этим разобраться, и ему дают совет: «Добавь вейтов, и всё будет хорошо. Ты просто не дождался отрисовки». Он следует совету, и теперь всё работает. Радостный тестер погружается в контекст другой задачи. Что может быть хуже, чем падающий тест?

Клиентское приложение на страже стабильности UI-тестов, image #4

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

Клиентское приложение на страже стабильности UI-тестов, image #5

Вариантов проблем может быть масса, но они не должны касаться автоматизатора UI, потому что его работа заключается в валидации данных, отображаемых на экране пользователя. Наш тестер идёт за советом к разработчикам. Здесь есть два варианта ответа:

  1. «Попробуй почистить юзера, может проблема на его стороне».
  2. «Проверь логи с бэкенда».
Клиентское приложение на страже стабильности UI-тестов, image #6

Моки и стабы

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

1) с замоканым бэкендом UI-тесты будут проходить достаточно быстро, потому что можно в том же докере поднять сервер, который будет отдавать заготовленные ответы;
2) можно по соседству с основным бэкендом поднять моковые ответы, которые не будут входить в базы данных и общаться с другими сервисами.

Преимущества моков

Сравнивая моки и стабы, можно заметить, что при использовании моков не нужно менять логику подсервера — вы решаете это отдельным моковым сервером, который просто принимает и отдаёт HTTP-ответы. В очередной раз, выкатывая код в продакшн, вы не будете переживать о неправильной конфигурации: мы всегда генерируем статичные ответы, которые ни от чего не зависят.

Минусы моков

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

Преимущества стабов

Стабы достаточно гибко генерируются при выполнении приложения. Мы можем как полностью застабить эндпоинт, так и частично, определяя на ходу в самом приложении. В JavaScript это сделать очень просто: нужно просто переписать одно поле эндпоинт. В Android есть интерцептеры, которые переключают нашу среду.

В iOS всё несколько сложнее. Со стабами попроще, потому что вы не генерируете весь стейт объектов, а только ответ. Исходя из этого, вам легче его подменять и поддерживать ваши имплементации. Вариативность реализации у стабов больше, в то время как моковый ответ просто подкладывает нужные данные в используемый объект, на который вы будете ссылаться при сравнении ваших заготовленных данных и самого приложения.

Минусы стабов

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

Вывод

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

Как работают моки?

Разберём на примере. Реализация мока достаточно тривиальна: просто нужно в любой момент времени вместо реального ответа подложить ожидаемый. В данном контексте мы ожидаем JSON, который будет содержать access_token, определённый по вашему формату. Он должен будет вернуть ответ 200, который мы проверяем в тестах, и body, который будет содержать access_token. По нему мы залогинимся. Сделав это, вы навсегда отвяжетесь от бэкенда.

Клиентское приложение на страже стабильности UI-тестов, image #7

Какая тут есть проблема?

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

Ещё один плюс

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

Как работают стабы?

Стабы меняют поведение самого приложения. Для их применения нужно лезть в исходники. Они работают на Swift, но мы их быстренько разберём.

Окунёмся в работу UI-тестов на iOS. Здесь у нас нет такого доступа к процессу, как в Android. Мы можем повлиять на приложение только при запуске: в поле environment вставляем название fakeServer, после чего приложение понимает, что на уровне кода приложения работает фейковый сервер. Вместо определённого сервера используем localhost. С него мы запускаем наш подготовленный сервер, после чего определяем метод в конечную точку, куда будем отправлять запросы. Это похоже на то, как мы собираем запрос в реальном приложении, только с подменой сервера. Далее генерируем наш запрос на экране, и вместо обычного экрана появляется наш предопределённый.

Клиентское приложение на страже стабильности UI-тестов, image #8

Минус такого подхода в том, что у вас не получится прогнать всё на environment — всё будет бежать против сервера, который вы подготовили.

Ещё одно решение

Существует метод Swizzle. Внутри он имплементирован сложнее, но на выходе нам нужно будет лишь пробросить название классов через селектор.

Допустим, defaultEndpoints будет содержать эндпоинты серверов, которые развёрнуты в продакшн. Мы подменяем их на моковые, которые хотим хотим разместить у себя на машинке для тестов.

Клиентское приложение на страже стабильности UI-тестов, image #9

В iOS есть такая специфика, как делегаты. Они выступают способом передачи по внутреннему транспорту из класса в класс и из экрана в экран. Тут нам нужно переопределить наш defaultEndpoints или просто используемый метод NetworkCalls. Далее используем класс, который отвечает за работу с сессиями и нетворк-запросами. Также вы работаете с используемыми тасками в приложении. Метод Swizzle позволяет переопределить любую работу тасков в рантайме.

Клиентское приложение на страже стабильности UI-тестов, image #10

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

Клиентское приложение на страже стабильности UI-тестов, image #11

Моки или стабы?

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

Проблема iOS

В iOS тесты очень часто падают, потому что мы не можем поднять любой ViewController, как в Android. А добираться до определённого экрана через пять других времени у нас нет.

Клиентское приложение на страже стабильности UI-тестов, image #12

Но и эту проблему можно решить. Мы можем пробросить единственный флаг, который будет содержать environment. Допустим, мы пробрасываем его и в main.swift переопределяем UIApplicationMain, а main, соответственно, подключается при запуске приложения. Из него определяется, какой будет стартовый экран, как мы будем работать с Keychain и как будет происходить отрисовка первых элементов, которые увидит пользователи. Тут мы будем работать с классом UIApplication, который отвечает за отрисовку чего угодно на iOS. Нам нужно пробросить его и подменить стандартный файл AppDelegate на TestingAppDelegate. В последнем мы должны пробросить в rootViewController определённый класс, в нашем случае ProfileViewController. Как нам известно, VK стартует с ленты новостей. Допустим, мы хотим сразу запустить наш профиль и убедиться, что на нём всё работает корректно. Если с пробросом данных всё в порядке и профиль не требует дополнительной инициализации объектов, мы запустим VK сразу с ProfileViewController.

Клиентское приложение на страже стабильности UI-тестов, image #13

Это не сработает, если объект ViewController требует дополнительные параметры для запуска экрана. В таком случае можно сгенерировать TestingAppDelegate, посмотреть, на какие поля у нас завязывается этот экран, и пробросить его. Таких делегатов можно наделать сколько угодно. Больше мы ни на что повлиять не можем.

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

Вопросы

Известно, что UI-тесты нестабильны. Нужно ли перезапускать тесты? Если да, то сколько раз? Или нужно разбирать каждый случай, когда тест фейлится?

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

Есть ли у тебя какая-то альтернатива стабам и мокам?

Пинать бэкенд, чтобы всё работало стабильно. На одной из работ мы 9 месяцев долбили админов, чтобы нам догрузили машины. После этого мы бились с бэкендерами, чтобы нам стабилизировали ответы на запросы. Иногда они говорили, что «retry это не плохо, делайте retry и мы вам дадим какой-нибудь нормальный ответ». Естественно, пользователи от этого будут страдать, потому что у нас итоговый environment полностью соответствовал тестовому environment и все проблемы тестового сохранялись. Чаще всего проблемы именно в коммуникациях с бэкендом, поэтому надо указывать им на проблемы, и тогда вы действительно найдёте такие ситуации, когда можно улучшить всё с их точки зрения. В маленьких приложениях я предпочитаю использовать моки. Если у вас какое-то большое приложение, в котором необходимо динамически подготавливать ответы, то тут лучше стабы использовать.

Вы сейчас рассказывали про UI-тесты. О каких запросах может идти речь?

На UI-тесты опирается request и отрисовывает определённые экраны в приложении. Кидая request в response, вы можете обнаружить ошибку. В этом случае пора задуматься о написании моков.

Приведите примеры использования различных моков.

Существуют статичные и динамичные моки. Статичные применимы в тех ситуациях, когда экран приложения как отдельная сущность не берёт данные ниоткуда. Например, вы тестируете мессенджер. Вам ведь не обязательно знать, какие сообщения были в чате, чтобы проверить его название. Динамичные моки применяются, когда мы держим базу данных из ответов отдельно или каждому шагу пишем свой мок, заранее определяя его поведение. Допустим, в том же мессенджере при отправке сообщения у нас всегда будет динамически формироваться timestamp.

У вас есть end-to-end тесты? Как вы мешаете время прохождения этих тестов?

End-to-end тесты подразумевают изменения данных от экрана к экрану. Вам вряд ли удастся избежать всех этих изменений по ходу тестов. Как раз тут нужны статичные моки и подмена всех ответов со стороны приложения по ходу вашего теста. Например, если вы подняли ViewController, проверили состояние и вышли оттуда, то с этим проблем не будет. Но когда вы переходите от крана к экрана, нужно позаботиться о том, чтобы у вас были подготовлены все шаги к логическому изменению данных. Это можно решить прогоном теста и дампом всех запросов и ответов, которое можно сделать с точки зрения приложения.

Почему нельзя сконструировать архитектуру, когда у нас будут при обычных адресах использоваться подменённые сервера?

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

В ваших UI-тестах проверяется только наличие элементов на экранах?

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

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

Клиентское приложение на страже стабильности UI-тестов, image #14

The Brown Room — независимое интернет-издание про социальные сети и современные технологии.

Автор: Влад Воробьёв
Корректор: Арсений Метелев
Слайды и данные: VK Tech

322 views·3 shares