Опыт внедрения автоматизации в процесс ручного тестирования на примере Android-приложения

в 3:27, , рубрики: android, iOS, windows, Тестирование мобильных приложений

Совместно с нашими партнёрами мы активно разрабатываем, тестируем и поддерживаем семейство приложений для разных платформ: Android, iOS, Windows. Приложения активно развиваются, вместе с чем увеличивается и объём тестирования, в первую очередь — регрессионного.

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

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

Поехали!

Отправная точка

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

Проблема №1: много регрессионных тестов

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

Проблема №2: нужно тестировать на всех версиях мобильной ОС

Важным требованием является работоспособность наших мобильных приложений на большом диапазоне версий операционной системы. Например, в случае Android на момент написания статьи — это уровни API от 17 до 28.
В идеале мы должны тестировать на каждой версии Android, что ещё больше «утяжеляет» нашу регрессию. Процесс непосредственного тестирования приложения обрастает дополнительной, помноженной на количество устройств, рутиной: установка и запуск приложения, приведение его в исходное состояние после каждого отдельного теста, удаление. При этом поддерживать собственную ферму устройств достаточно трудозатратно.

Решение: внедрить автоматизацию в процесс ручного тестирования

Опыт внедрения автоматизации в процесс ручного тестирования на примере Android-приложения - 1

Типичная задача автоматизации тестирования — автоматизировать регрессионные тесты. Так мы хотим улучшить эффективность процесса тестирования сегодня и предотвратить возможные последствия роста завтра.

В то же время мы прекрасно осознаём, что невозможно и ненужно пытаться полностью искоренить ручное тестирование автоматизацией. Критическое мышление и человеческий взгляд трудно чем-то заменить. На эту тему есть хорошая статья в блоге Майкла Болтона The End of Manual Testing (или перевод от Анны Родионовой).

Мы подумали, что будет полезно иметь набор автоматизированных тестов, которые покрывают стабильные части приложения, а в дальнейшем — писать тесты на найденные баги и новый функционал. При этом мы хотим связать автотесты с существующими тестовыми наборами в нашей системе управления тестами (мы используем TestRail), а также дать возможность тестировщикам легко запускать автотесты на облачных физических устройствах (в качестве облачной инфраструктуры мы выбрали Firebase Test Lab).

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

Что мы хотим получить в итоге:

  1. Автоматизацию регрессионного тестирования.
  2. Интеграцию с системой управления тестами.
  3. Возможность параметризованного ручного запуска автотестов на облачных устройствах.
  4. Возможность переиспользования решения в дальнейшем.

Далее я отдельно расскажу про реализацию каждого из этих пунктов с небольшим погружением в техническую составляющую.

Общая схема реализации решения

Но сначала — общая схема того, что у нас получилось:

image

Автотесты запускаются двумя способами:

  1. Из CI после merge или pull request в master.
  2. Тестировщиком вручную из веб-интерфейса 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.

Для каждого самостоятельного фрагмента приложения создаётся специальный класс-робот, в котором реализовывается бизнес-логика. Взаимодействие с каждым элементом фрагмента описывается в отдельном методе. Кроме того, здесь же описываются все ассерты, выполняемые в данном фрагменте.
Рассмотрим простой пример. Описываемый фрагмент — несколько полей для ввода данных и кнопка действия:

image

Код самого теста функциональности логина:

image

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

Такая структура в первую очередь даёт тестам отличную читаемость, когда весь сценарий разбит на основные шаги выполнения. Также нам очень понравилась идея выноса ассертов в отдельные методы соответствующего робота. Ассерт становится таким же шагом, не разрывая общую цепочку, а ваши тесты по-прежнему ничего не знают о приложении.
В вышеупомянутом докладе Джейк Уортон приводит вариант реализации на языке Kotlin, где конечны. Мы уже попробовали его на другом проекте и нам очень понравилось.

2. Интеграция с системой управления тестами

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

В ходе выполнения тестового запуска с помощью JUnit RunListener отлавливаются разные события, такие как testRunStarted, testFailure, testFinished, в которых мы и отправляем результаты в TestRail. Если вы используете AndroidJUnitRunner, то ему нужно сообщить о вашем RunListener определённым образом, описанным в официальной документации.

Вам также нужно установить связь с различными сущностями TestRail по их ID. Так, для связи теста с соответствующим тест-кейсом мы создали простую аннотацию @CaseId, использование которой показано в примере реализации теста выше.

Код реализации самой аннотации:

image

Остаётся только достать её значение в нужном месте из Description:

image

3. Ручной запуск автотестов на облачных устройствах

Параметризация запуска в Jenkins Job

Для организации ручного запуска автотестов мы используем Free-style Jenkins Job. Этот вариант был выбран, так как в компании уже имелся определённый опыт подобной работы с Jenkins Job в других областях, в частности у DevOps-инженеров, коим они с радостью поделились.

В Jenkins Job выполняется скрипт на основе переданных из веб-интерфейса данных. Таким образом реализуется параметризация тестовых запусков. В нашем случае Bash-скрипт инициирует запуск тестов на облачных устройствах Firebase.

Параметризация включает в себя:

  • Выбор нужных APK путём указания номера соответствующего билда или загрузки их вручную.
  • Ввод всевозможных тестовых данных.
  • Ввод дополнительных кастомных данных для TestRail.
  • Выбор облачных физических устройств, на которых будут выполняться тесты, из списка доступных в Firebase Test Lab.
  • Выбор тестовых наборов, которые будут выполнены.

Рассмотрим часть веб-страницы нашей Jenkins Job на примере интерфейса выбора устройств и тестовых наборов:

image

Каждый элемент, где можно ввести или выбрать какие-либо данные, реализуется специальными Jenkins-плагинами. Например, интерфейс выбора устройств сделан с использованием Active Choices Plugin. С помощью groovy-скрипта из Firebase получается список доступных устройств, который затем отображается в нужном виде на веб-странице.

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

image

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

Итоговая тестовая матрица в Firebase Test Lab

Матрица устройств в Firebase содержит распределение тестов по устройствам, на которых они выполнялись:

image

По каждому устройству можно просмотреть полный лог, видео тестового прогона, различные performance-показатели. Кроме того, можно получить доступ ко всем файлам, которые могли быть созданы во время выполнения тестов. Мы используем это для того, чтобы выгрузить с устройства показатели покрытия кода тестами.

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

4. Переиспользование

Как всё это можно использовать в дальнейшем? С точки зрения кодовой базы данное решение применимо только для Android-приложений. Например, в ходе реализации у нас появились вспомогательные классы EspressoExtensions и UiAutomatorExtensions, где мы инкапсулируем различные варианты взаимодействия с интерфейсом и ожидание готовности элементов. Сюда же относится и RunListener-класс, отвечающий за интеграцию с TestRail. Мы уже вынесли их в отдельные модули и используем для автоматизации других приложений.

Если говорить о других платформах, то полученный опыт может быть очень полезен для того, чтобы выстраивать и реализовывать аналогичные процессы. Этим мы сейчас активно занимаемся на iOS-направлении и подумываем о Windows.

Заключение

Существует множество вариантов реализации и использования автоматизации тестирования. Мы придерживаемся мнения, что автоматизация — это в первую очередь инструмент, который призван облегчить традиционный процесс «человеческого» тестирования, а не искоренить его.

Автор: Seva_Morotskiy

Источник

* - обязательные к заполнению поля