Совместно с нашими партнёрами мы активно разрабатываем, тестируем и поддерживаем семейство приложений для разных платформ: Android, iOS, Windows. Приложения активно развиваются, вместе с чем увеличивается и объём тестирования, в первую очередь — регрессионного.
Мы решили попробовать облегчить и ускорить тестирование путём автоматизации бо́льшей части тестовых сценариев. При этом мы не хотели полностью отказываться от процесса ручного тестирования, а скорее модифицировать его.
Реализация такого подхода началась с одного из Android-приложений, о чём я и расскажу. Статья будет интересна начинающим авторам UI-тестов, в первую очередь для мобильных приложений, а также тем, кто хочет в некоторой мере автоматизировать процесс ручного тестирования.
Поехали!
Отправная точка
Для каждой платформы у нас имеется несколько схожих, выполняющих один и тот же основной бизнес-процесс приложений. Однако они отличаются друг от друга набором небольших вспомогательных функциональностей, выполнены под разными брендами в зависимости от заказчика (из-за чего интерфейс меняется от приложения к приложению), а бизнес-процесс может быть кастомизирован путём добавления дополнительных шагов.
Мы столкнулись с определёнными проблемами, которые необходимо решать. Подобные трудности могут возникнуть и в ситуации, отличной от нашей. Например, если у вас одно громоздкое приложение с трудной бизнес-логикой, обросшее множеством тестов.
Проблема №1: много регрессионных тестов
Наборы тестовых сценариев для каждого приложения одновременно и похожи, и отличаются между собой, что способствует увеличению регрессии и делает её ещё более скучной. Тем не менее, тестировать все приложения нужно по отдельности.
Учитывая, что уже работающие приложения регулярно обновляются, и в будущем их станет только больше, общее количество тестов будет неумолимо расти.
Проблема №2: нужно тестировать на всех версиях мобильной ОС
Важным требованием является работоспособность наших мобильных приложений на большом диапазоне версий операционной системы. Например, в случае Android на момент написания статьи — это уровни API от 17 до 28.
В идеале мы должны тестировать на каждой версии Android, что ещё больше «утяжеляет» нашу регрессию. Процесс непосредственного тестирования приложения обрастает дополнительной, помноженной на количество устройств, рутиной: установка и запуск приложения, приведение его в исходное состояние после каждого отдельного теста, удаление. При этом поддерживать собственную ферму устройств достаточно трудозатратно.
Решение: внедрить автоматизацию в процесс ручного тестирования
Типичная задача автоматизации тестирования — автоматизировать регрессионные тесты. Так мы хотим улучшить эффективность процесса тестирования сегодня и предотвратить возможные последствия роста завтра.
В то же время мы прекрасно осознаём, что невозможно и ненужно пытаться полностью искоренить ручное тестирование автоматизацией. Критическое
Мы подумали, что будет полезно иметь набор автоматизированных тестов, которые покрывают стабильные части приложения, а в дальнейшем — писать тесты на найденные баги и новый функционал. При этом мы хотим связать автотесты с существующими тестовыми наборами в нашей системе управления тестами (мы используем TestRail), а также дать возможность тестировщикам легко запускать автотесты на облачных физических устройствах (в качестве облачной инфраструктуры мы выбрали Firebase Test Lab).
Для старта и пробы мы взяли одно из наших Android-приложений. Важно было учитывать, что в случае успешности решения его лучшие практики можно было применить и на остальные наши приложения, в том числе и на других платформах.
Что мы хотим получить в итоге:
- Автоматизацию регрессионного тестирования.
- Интеграцию с системой управления тестами.
- Возможность параметризованного ручного запуска автотестов на облачных устройствах.
- Возможность переиспользования решения в дальнейшем.
Далее я отдельно расскажу про реализацию каждого из этих пунктов с небольшим погружением в техническую составляющую.
Общая схема реализации решения
Но сначала — общая схема того, что у нас получилось:
Автотесты запускаются двумя способами:
- Из CI после merge или pull request в master.
- Тестировщиком вручную из веб-интерфейса Jenkins Job.
В случае ручного запуска тестировщику нужно либо указать номер соответствующего билда, либо загрузить со своего компьютера 2 APK: с приложением и с тестами. Этот способ нужен для того, чтобы можно было прогонять необходимые тесты в любой момент времени на любых доступных устройствах.
Во время выполнения тестов их результаты отправляются в TestRail. Это происходит таким же образом, как если бы тестировщик выполнял тестирование вручную и заносил результаты уже привычным ему способом.
Таким образом, мы оставили устоявшийся процесс ручного тестирования, но добавили в него автоматизацию, которая выполняет определённый набор тестов. Тестировщик «подхватывает» то, что выполнилось автоматически, и:
- видит результат выполнения тест-кейсов на каждом устройстве, которые были выбраны;
- может перепроверить любой тест-кейс вручную;
- выполняет тест-кейсы, которые ещё не автоматизированы, либо не могут быть оптимизированы по каким-либо причинам;
- принимает итоговое решение о текущем тестовом запуске.
Теперь перейдём к обещанному описанию реализации.
1. Автотесты
Инструменты
Мы использовали 3 инструмента для взаимодействия с пользовательским интерфейсом:
- Espresso.
- Barista.
- UI Automator.
Основным инструментом и тем, с которого мы начали, является Espresso. Главным аргументом в пользу его выбора послужило то, что Espresso позволяет тестировать методом белого ящика, предоставляя доступ к Android Instrumentation. Код тестов находится в одном проекте с кодом приложения.
Доступ к коду Android-приложения нужен для того, чтобы в тестах вызывать его методы. Мы можем заранее подготовить наше приложение к определённому тесту, запустив его в нужном состоянии. Иначе нам нужно достигать этого состояния через интерфейс, что лишает тестов атомарности, делая их зависимыми друг от друга, и просто съедает много времени.
В ходе реализации к Espresso добавился ещё один инструмент — UI Automator. Оба фреймворка являются частью Android Testing Support Library от Google. С помощью UI Automator мы можем взаимодействовать с различными системными диалогами или, например, с Notification Drawer.
И последним в нашем арсенале стал фреймворк Barista. Он является обёрткой вокруг Espresso, избавляя вас от boilerplate-кода при реализации распространённых пользовательских действий.
Помня о желании иметь возможность переиспользования решения в других приложениях, важно отметить, что перечисленные инструменты предназначены исключительно для Android-приложений. Если вам не нужен доступ к коду тестируемого приложения, то, вероятно, вы предпочтёте использовать другой фреймворк. Например, очень популярный сегодня Appium. Хотя и с ним можно попробовать достучаться до кода приложения с помощью бэкдоров, о чём есть хорошая статья в Badoo. Выбор за вами.
Реализация
В качестве паттерна проектирования мы выбрали Testing Robots, предложенный Джэйком Уортоном в одноимённом докладе. Идея этого подхода схожа с распространённым шаблоном проектирования Page Object, применяем при тестировании веб-систем. Язык программирования — Java.
Для каждого самостоятельного фрагмента приложения создаётся специальный класс-робот, в котором реализовывается бизнес-логика. Взаимодействие с каждым элементом фрагмента описывается в отдельном методе. Кроме того, здесь же описываются все ассерты, выполняемые в данном фрагменте.
Рассмотрим простой пример. Описываемый фрагмент — несколько полей для ввода данных и кнопка действия:
Код самого теста функциональности логина:
Здесь мы проверяем позитивный сценарий, когда введённые аутентификационные данные верны. Сами данные подаются тестам на вход, либо используются значения по умолчанию. Таким образом, у тестировщика есть возможность параметризации в части тестовых данных.
Такая структура в первую очередь даёт тестам отличную читаемость, когда весь сценарий разбит на основные шаги выполнения. Также нам очень понравилась идея выноса ассертов в отдельные методы соответствующего робота. Ассерт становится таким же шагом, не разрывая общую цепочку, а ваши тесты по-прежнему ничего не знают о приложении.
В вышеупомянутом докладе Джейк Уортон приводит вариант реализации на языке Kotlin, где конечны. Мы уже попробовали его на другом проекте и нам очень понравилось.
2. Интеграция с системой управления тестами
До внедрения автоматизации мы вели всё наше тестирование в системе управления тестами TestRail. Хорошей новостью стало то, что существует достаточно неплохой TestRail API, с помощью которого мы смогли связать уже заведённые в системе тест-кейсы с автотестами.
В ходе выполнения тестового запуска с помощью JUnit RunListener отлавливаются разные события, такие как testRunStarted
, testFailure
, testFinished
, в которых мы и отправляем результаты в TestRail. Если вы используете AndroidJUnitRunner, то ему нужно сообщить о вашем RunListener определённым образом, описанным в официальной документации.
Вам также нужно установить связь с различными сущностями TestRail по их ID. Так, для связи теста с соответствующим тест-кейсом мы создали простую аннотацию @CaseId
, использование которой показано в примере реализации теста выше.
Код реализации самой аннотации:
Остаётся только достать её значение в нужном месте из Description:
3. Ручной запуск автотестов на облачных устройствах
Параметризация запуска в Jenkins Job
Для организации ручного запуска автотестов мы используем Free-style Jenkins Job. Этот вариант был выбран, так как в компании уже имелся определённый опыт подобной работы с Jenkins Job в других областях, в частности у DevOps-инженеров, коим они с радостью поделились.
В Jenkins Job выполняется скрипт на основе переданных из веб-интерфейса данных. Таким образом реализуется параметризация тестовых запусков. В нашем случае Bash-скрипт инициирует запуск тестов на облачных устройствах Firebase.
Параметризация включает в себя:
- Выбор нужных APK путём указания номера соответствующего билда или загрузки их вручную.
- Ввод всевозможных тестовых данных.
- Ввод дополнительных кастомных данных для TestRail.
- Выбор облачных физических устройств, на которых будут выполняться тесты, из списка доступных в Firebase Test Lab.
- Выбор тестовых наборов, которые будут выполнены.
Рассмотрим часть веб-страницы нашей Jenkins Job на примере интерфейса выбора устройств и тестовых наборов:
Каждый элемент, где можно ввести или выбрать какие-либо данные, реализуется специальными Jenkins-плагинами. Например, интерфейс выбора устройств сделан с использованием Active Choices Plugin. С помощью groovy-скрипта из Firebase получается список доступных устройств, который затем отображается в нужном виде на веб-странице.
После того, как все нужные данные введены, запускается соответствующий скрипт, за ходом выполнения которого мы можем наблюдать в разделе Console Output:
Отсюда тестировщик, который инициировал тестовый прогон, по полученным URL-адресам может перейти в TestRail или в консоль Firebase, где содержится много полезной информации о выполнении тестов на каждом из выбранных устройств.
Итоговая тестовая матрица в Firebase Test Lab
Матрица устройств в Firebase содержит распределение тестов по устройствам, на которых они выполнялись:
По каждому устройству можно просмотреть полный лог, видео тестового прогона, различные performance-показатели. Кроме того, можно получить доступ ко всем файлам, которые могли быть созданы во время выполнения тестов. Мы используем это для того, чтобы выгрузить с устройства показатели покрытия кода тестами.
Мы выбрали Firebase, так как уже использовали этот сервис для решения других задач, и нас устраивает ценовая политика. Если уложиться в 30 минут чистого времени на тестирование в сутки, то платить не нужно вовсе. Это может быть дополнительной причиной, по которой важно иметь возможность запускать только определённые тесты.
Возможно, вы предпочтёте другую облачную инфраструктуру, которая также хорошо впишется в ваш процесс тестирования.
4. Переиспользование
Как всё это можно использовать в дальнейшем? С точки зрения кодовой базы данное решение применимо только для Android-приложений. Например, в ходе реализации у нас появились вспомогательные классы EspressoExtensions
и UiAutomatorExtensions
, где мы инкапсулируем различные варианты взаимодействия с интерфейсом и ожидание готовности элементов. Сюда же относится и RunListener-класс, отвечающий за интеграцию с TestRail. Мы уже вынесли их в отдельные модули и используем для автоматизации других приложений.
Если говорить о других платформах, то полученный опыт может быть очень полезен для того, чтобы выстраивать и реализовывать аналогичные процессы. Этим мы сейчас активно занимаемся на iOS-направлении и подумываем о Windows.
Заключение
Существует множество вариантов реализации и использования автоматизации тестирования. Мы придерживаемся мнения, что автоматизация — это в первую очередь инструмент, который призван облегчить традиционный процесс «человеческого» тестирования, а не искоренить его.
Автор: Seva_Morotskiy