На чем писать Android UI-тесты |
16.10.2020 00:00 | ||||||||||||||||||||||||||||||||||||||||
Авторы: Евгений Мацюк и Егор Курников Всем привет. Мы в Avokado Project продолжаем рассказывать про автотестирование в Android. Эта статья — обзор и сравнение существующих инструментов для написания UI-тестов. Давайте начнем с того, что вспомним, как обычно выглядит процесс тестирования. Будем называть сущность, которая взаимодействует с приложением, клиентом. Для взаимодействия с приложением клиенту обычно доступно несколько интерфейсов: API, REST API, CLI, GUI и т.д. И если, например, API используются клиентами-программами, то GUI используется человеком. Ожидания от поведения приложения описываются в спецификации. Задача тестирования — проверить, что поведение приложения соответствует спецификации. Для решения этой задачи фиксируется набор сценариев использования или тест-кейсов. Они представляют собой последовательности шагов, которые приводят продукт в требуемое состояние. Таким образом, процесс тестирования приложения — это процесс прохождения всех тест-кейсов. В случае ручного тестирования тестом является процесс прохождения тест-кейса человеком. Если этот процесс автоматизирован, то вместо человека с приложением взаимодействуют программные клиенты. В этом случае тестом также называется сам реализованный на языке программирования тест-кейс. Для реализации тестов и их автоматизированного запуска нам понадобится фреймворк тестирования (JUnit, Cucumber). Он включает в себя:
В зону ответственности раннера в свою очередь входят:
Несмотря на то, что с фреймворком поставляется также и раннер, существует возможность использования более совершенных сторонних раннеров. Тема раннеров, их особенностей в контексте Android-тестирования, и выбора наиболее подходящего (Avito test runner, Marathon, Spoon, Fork) довольно обширна и заслуживает отдельной статьи, поэтому вернемся к самим тестам. Если API приложения доступен тесту напрямую, то для GUI интерфейса нужны программные адаптеры — драйверы. Тесты, обращающиеся к GUI-драйверам, называются UI-тестами. В отличие от unit-тестов, они выполняются на реальном девайсе или эмуляторе под управлением соответствующей мобильной операционной системы. GUI-драйверы являются наиболее сложным компонентом всего стека тестирования. Они решают базовые, низкоуровневые задачи. Когда вы даете GUI-драйверу (Espresso, UiAutomator) команду «кликнуть на кнопку», он обрабатывает ее, взаимодействует с приложением, и эта команда превращается в клик по элементу. Работа с API драйвера напрямую из теста имеет ряд недостатков:
Для устранений таких проблем используются обертки для драйверов, например, Kakao или Kaspresso. Эти обертки могут сильно различаться в зависимости от решаемых задач. Некоторые из них — просто синтаксический сахар, другие же — довольно толстая прослойка, выполняющая много сложной работы. В этой статье мы поговорим о драйверах и обертках, рассмотрим наиболее популярные из них. ДрайверыВ Android-мире четыре популярных драйвера:
Все они работают на Android Instrumentation Framework — базовом API Android для взаимодействия с системой. Самые популярные — Espresso и UiAutomator. Они оба разрабатываются и поддерживаются компанией Google. Их без труда можно использовать одновременно в пределах одного теста. Давайте рассмотрим их поближе. UiAutomatorUiAutomator поставляется вместе с Android SDK начиная с 16 версии API. Как GUI-драйвер он служит для поиска элементов интерфейса на экране девайса и эмуляции различных действий: кликов, тачей, свайпов, ввода текста, проверок на видимость. Рекордер для записи тестов он не предоставляет, зато предоставляет утилиту для просмотра древовидной структуры экрана — Ui Automator Viewer. UiAutomator позволяет писать тесты по модели черного ящика (black-box). Он живет в собственном процессе и работает без доступа к исходному коду приложения. Значит, тест с его использованием может взаимодействовать практически с любыми приложениями, в том числе системными. Это открывает доступ к множеству функций, например, становятся возможны:
Внутри UiAutomator реализован при помощи Каждая View реализует интерфейс Для более детального погружения в устройство работы UiAutomator рекомендуем ознакомиться с докладом “UI Automator deep diving”. Среди недостатков UiAutomator:
Таким образом, наиболее подходящим сценарием для использования UiAutomator является не работа с тестируемым продуктовым приложением, а взаимодействие со сторонними или системными приложениями. Более подходящим инструментом для работы с продуктовым приложением является Espresso. EspressoЭто также официальный фреймворк для UI-тестирования от Google, но тесты с его использованием работают уже по модели белого ящика (white-box). Espresso поддерживает Android API с 10 версии, хотя появился значительно позже. Предоставляет рекордер для записи тестов. Фактически Espresso является лидером и даже стандартом в индустрии. Такой успех может быть связан с тем, что он обеспечивает повышенную скорость и стабильность тестов в сравнении с конкурентами. Espresso решает низкоуровневую задачу — найти необходимый элемент на экране по заданным параметрам ( Синтаксис Espresso реализован на основе фреймворка Hamcrest. Он построен на использовании иерархий вложенных матчеров — сущностей, описывающих требования к объекту, в случае Espresso — к элементам интерфейса. Они используются при задании параметров поиска элемента на экране, а также в проверках как описание свойств, которыми элемент должен обладать. Вложенность матчеров часто приводит к тому, что код тестов становится трудно читать и поддерживать.
Стабильность и скорость тестов на Espresso обусловлены его внутренним устройством — все команды Espresso выполняются в том же процессе, в котором работает само тестируемое приложение. Действия и проверки преобразовываются и кладутся в очередь сообщений главного потока приложения. Выполняются они только когда приложение готово к этому, то есть его главный поток находится в состоянии ожидания пользовательского ввода (idle). Для того, чтобы убедиться в этом, достаточно обратиться к внутренностям, например, метода
Исходя из вышесказанного можно выделить следующие недостатки Espresso:
Если вы готовы самостоятельно искать решения этих проблем, или если они в вашем случае не играют роли, то Espresso — это отличный инструмент, тесты будут быстрыми и стабильными. Robotium и SelendroidRobotium и Selendroid были довольно популярны до появления Espresso и UiAutomator. Cейчас же их аудитория заметно сократилась, и развиваются они далеко не так активно. Оба инструмента могут работать только с одним тестируемым приложением, не требуют доступа к исходному коду и поддерживают работу на эмуляторах и реальных устройствах. Robotium может использоваться для тестирования приложений на Android API 8+, (поддержка работы с WebView доступна, однако, только с API 15), тесты для него пишутся на Java. Также он предоставляет плагин Robotium Recorder для IntelliJ IDEA и Android Studio. Selendroid поддерживает ограниченный список версий API — с 10 по 19. Он поддерживает протокол WebDriver, предоставляет утилиту Inspector для просмотра иерархии элементов и записи простых record-and-playback-тестов. Если вы начинаете внедрять автотесты с нуля, мы не видим веских причин использовать Robotium или Selendroid. RobolectricНесмотря на то, что Robolectric не вполне вписывается в описанную ранее структуру, его нельзя здесь не упомянуть. Он не является интеграционным тестовым фреймворком, с его помощью вы не сможете тестировать взаимодействие Android компонентов или писать UI-тесты. Однако Robolectric позволяет писать особые unit-тесты на основе JUnit с использованием Android API. Он мокирует часть Android SDK, предоставляя пользователю так называемые shadow-объекты. Robolectric берет на себя такие задачи, как inflate view, загрузка ресурсов, и множество других, которые имеют нативную С-реализацию на Android-девайсах. Поэтому Robolectric позволяет писать тесты, имеющие зависимости на Android, но запускать их не на эмуляторе или реальном девайсе, а на десктопной JVM. Это существенно ускоряет процесс сборки, запуска и выполнения тестов. ОберткиИтак, с драйверами и устройством наиболее популярных из них мы разобрались. Мы поняли, что все они решают низкоуровневые задачи: поиск элемента на экране и выполнение с ним какого-либо действия. Из-за этого они предоставляют невыразительный API, которым неудобно пользоваться для решения более высокоуровневых проблем. Например, ни один из них не предоставляет собственный механизм для реализации повторов неудачных действий или логирования. Более того, часто существует необходимость работать в тестах сразу с несколькими драйверами, например, с Espresso и UiAutomator. На помощь приходят обертки. Именно к API оберток мы будем обращаться в наших тестах, и именно обертки предоставляют конечный арсенал приемов для наших тестов. Прежде, чем мы рассмотрим и сравним наиболее популярные обертки, выделим основные требования к тестам. Стабильность. Общая беда всех инструментов для UI-тестирования заключается в том, что ни один из них не позволяет реализовать абсолютно стабильные тесты. Тесты с использованием каждого из них будут флекать (от англ. чудной, чокнутый). То есть если вы запустите рабочий тест множество раз в одинаковых условиях, то в ряде случаев он может упасть. Причем причины таких падений могут быть совершенно непредсказуемыми. Избавиться от падений полностью не удастся, ведь зачастую они вызваны нестабильностью окружения и среды запуска тестов. И все же долю неудачных запусков можно сократить. Часто это удается сделать, внеся правки в код теста или самого приложения, но иногда проблема может быть вызвана самим инструментом, с помощью которого реализованы тесты. Поэтому при выборе инструмента следует обратить особое внимание на сам принцип его работы, оценить количество потенциальных точек отказа, которые он вносит в общий алгоритм выполнения UI-тестов. Универсальность. Наш опыт привел нас к убежденности в том, что тесты должны быть реализованы по модели белого ящика, когда это возможно. Это позволяет добиться более высокой скорости и стабильности их исполнения по сравнению с black-box тестированием. Однако иногда это невозможно. Случается, что в тестах требуется наличие функциональности, которую предоставляют только black-box решения, например, работы со сторонними или системными приложениями. Поэтому обертка мечты должна уметь агрегировать под своим интерфейсом обращение к нескольким драйверам для предоставления как white-box, так и black-box фич. Гибкость. Приложений на рынке великое множество, и каждое из них обладает своими особенностями. Разработчикам обертки вряд ли удастся заранее все предусмотреть и предоставить реализации всевозможных фичей, которые могут пригодиться пользователям. Поэтому обертка должна обладать точками расширения, быть гибкой и конфигурируемой, чтобы разработчики тестов могли без проблем адаптировать ее для своих нужд. Логи. Как мы уже выяснили, тесты неизбежно будут падать. Причем не только флекать: они будут устаревать, терять актуальность, и просто делать то, ради чего писались — падать в тех местах, где поведение приложения отличается от ожидаемого, тем самым подсвечивая баги. А значит, нам придется много медитировать над упавшими тестами, расследовать все обстоятельства происшествий, выдвигать и проверять гипотезы, находить и исправлять проблемы в тестах и в самом приложении. А для этого желательно не только наличие stacktrace’а с ошибкой, но и полноценных логов, чтобы после прохождения теста можно было понять, что происходило, и что сломалось. Скриншоты. Одних логов бывает недостаточно. Очень часто возникает потребность увидеть, что реально отрендерилось на экране на момент ошибки. Поэтому было бы очень кстати, если бы к логам прилагались скриншоты. Доступ к adb. Часто из тестов бывает необходимо обратиться к adb: включить/выключить сеть, установить геолокацию, эмулировать работу с отпечатком пальца или работать с файловой системой. Выразительный API. Код тестов — это тоже код, причем зачастую очень нетривиальный. С одной стороны запутанный, загроможденный, многословный, императивный код тестов — это не только потенциальный источник багов в самих тестах, но и сложность в их поддержке. С другой стороны, творческие инженеры могут далеко зайти с выделением все новых и новых абстракций, усложнением манипуляций с ними и вообще с созданием велосипедов. На наш взгляд обертка была бы молодец, если обеспечивала бы единообразие структуры исходного кода тестов и предоставляла четкий набор абстракций и паттернов для использования. Теперь давайте сравним популярные обертки с этими хотелками. AppiumAppium — это довольно популярный кросс-платформенный open source инструмент для автоматизации тестирования десктоп и мобильных приложений под Android и iOS. Архитектура Appium схожа с Selenium WebDriver, широко распространенным и ставшим стандартом в web-тестировании. Кроссплатформенность достигается за счет использования разных драйверов для разных платформ. Именно драйверы транслируют клиентский Appium-код в команды, непосредственно исполняемые на устройствах. Для написания тестов Appium предоставляет клиентские библиотеки с фиксированным API. Стабильность. Как видно из рисунка, Appium является довольно громоздкой оберткой. По сути он представляет собой целый HTTP-сервер на Node.JS, который создает и обрабатывает WebDriver-сессии, где и происходит общение с конечным драйвером команд на устройстве. Такое сложное устройство хоть и позволяет абстрагироваться от платформы, все же негативно сказывается как на скорости, так и на стабильности тестов. К сложным и громоздким механизмам, скрытым в драйверах, Appium добавляет собственный оверхэд. Универсальность. Appium умеет абстрагироваться не только от платформы, но и от используемого драйвера. В случае Android он с помощью своих адаптеров может транслировать команды как в черный ящик UiAutomator, так с недавнего времени и в белый ящик Espresso. Адаптер к драйверу, который будет использоваться в тесте, указывается при конфигурации перед началом теста. Также есть возможность использовать сразу несколько адаптеров. Это позволит в пределах одного теста работать и с Espresso и с UiAutomator. Выразительный API. Для написания тестов Appium предоставляет клиентские библиотеки с фиксированным фасадным API, очень похожим на WebDriver. Appium не предлагает никакой архитектуры для тестов, все отдается на откуп конечному разработчику. Гибкость. Перед началом теста есть возможность задать конфигурацию, например, ОС на мобильном устройстве, ее версию, тип девайса (реальный или эмулятор), таймаут ожидания ответа от Appium-сервера, язык, ориентацию, указать экран приложения, с которого стартует тест. Также перед началом теста можно указать, какой драйвер будет использоваться. Однако гибкостью настроек и конфигурации все ограничивается — функциональность драйвера не может быть расширена. Логи. Не предоставляет инструмента для логирования шагов, действий и проверок теста. Скриншоты. Из коробки готовой функции нет, хотя ее несложно добавить самостоятельно. Доступ к adb. Благодаря своей клиент-серверной архитектуре у Appium’а нет проблем с тем, чтобы посылать на девайс adb-команды. Это, бесспорно, является его плюсом. Итог. Appium подкупает своей кроссплатформенностью. Он активно развивается — Espresso драйвер был добавлен не так давно. Однако сложная клиент-серверная архитектура как плата за кроссплатформенность не позволяет добиться высокой стабильности тестов, что вкупе со сложностью инициализации тестовых сессий сильно увеличивает длительность прохождения тест сьютов в сравнении с конкурентами. Также стоит отметить, что Appium — это отдельная и зачастую чуждая Android-разработчикам технология. К тому же, если в вашем проекте используется Kotlin, в тестах придется вернуться к Java. Из-за этой чуждости Appium часто встречает неприятие со стороны разработчиков. Это является негативным фактором, поскольку по нашему опыту как раз разработчики должны драйвить процесс автотестирования. Подробнее в этих докладах: «Автотесты в Авито. Зачем они, как помогают, сколько стоят», «Как начать писать автотесты и не сойти с ума». KakaoKakao — простой и удобный Kotlin DSL для Espresso. Он позволяет упростить код тестов на Espresso и повысить его читаемость. Выразительный API. По сути Kakao — это написанный за вас boilerplate-код для поддержки в тестах следующих двух паттернов:
Этот архитектурный подход позволяет писать декларативные тесты, которые гораздо проще поддерживать, чем тесты на голом Espresso. Вместо этого:
получаем это:
Стабильность. Kakao — это намного более тонкая обертка, нежели Appium, и поэтому вместе со своими бонусами она не вносит практически никакого оверхэда в ваши тесты. Команды по-прежнему исполняются внутри главного потока тестируемого приложения. Все особенности Espresso в том, что касается стабильности тестов, также унаследованы — возможны проблемы при работе со сложными асинхронными интерфейсами, списками и т.д. Универсальность. Kakao оборачивает только Espresso, это исключительно белый ящик с доступом к исходникам и слепотой за пределами процесса тестируемого приложения. Гибкость. Для обеспечения расширяемости в Kakao реализован механизм интерсепторов. Пользователь через специальное API может внедрять вызов собственного кода в момент, когда выполняется та или иная Espresso-команда. Благодаря этому пользователь может самостоятельно добавить в тесты, например, ожидания или повторы неудачных действий. Однако что-либо сделать вне процесса тестируемого приложения по-прежнему не удастся. Логи. Логов из коробки нет, как и в Espresso. Хотя можно добавить их самостоятельно при помощи интерсепторов. Скриншоты. Механизма снятия скриншотов из коробки также нет. Потребуется собственная реализация. Доступ к adb. Отсутствует, как и у Espresso. Итог. Таким образом, Kakao — это удобный DSL для упрощения написания и поддержки тестов на Espresso. Но помимо него для решения многих бытовых задач вам необходимо будет использовать UiAutomator, чей API уже не такой выразительный. Скорее всего со временем вам придется дописать множество собственных надстроек для расширения функциональности, например, для логирования и повторов. BaristaBarista — это объемная библиотека, содержащая большое количество полезных решений и приемов при работе с Espresso. Выразительный API. Как и Kakao, эта библиотека построена поверх Espresso. Однако Barista — скорее широкий набор приемов, которые можно выборочно использовать в своих Espresso-тестах. Этот набор приемов включает в себя:
Также Barista умеет работать с NestedScrollView, в отличие от Espresso, и еще включает в себя множество других приятных фишек. Но никакого специального DSL библиотека не предоставляет, поэтому формирование архитектуры в тестах остается за пользователем. Стабильность. Благодаря вышеупомянутому FlakyTestRule есть возможность перезапускать падающие тесты. Однако обычно эту задачу успешно решают раннеры. К тому же в библиотеке отсутствует механизм ретраев для отдельных действий и проверок, что позволило бы не перезапускать каждый раз весь тест целиком и экономить время. Универсальность. Исключительно белый ящик из-за работы только поверх Espresso. Гибкость. Расширять в этой библиотеке по сути нечего, можно только вдохновляться и выделять у себя в проекте подобные рабочие Espresso приемы. И ваша свобода ограничена только функциональностью Espresso. Логи. Механизм для логирования шагов, действий и проверок в тестах отсутствует. Скриншоты. Механизм снятия скриншотов также отсутствует. Доступ к adb. Отсутствует, как и у Espresso. Итог. Barista вряд ли подойдет для построения тестов целиком и полностью на ней. Она скорее будет полезна для того, чтобы время от времени обращаться к ней за советом, подсматривать и перенимать те или иные способы решения ваших проблем. Или же вы можете затащить ее к себе в проект и вручную адаптировать для работы вместе с другими инструментами. KaspressoФреймворк-обертка для UI-тестирования, который претендует на то, чтобы избавить мир от зла и стать практически единственной зависимостью в вашем проекте для работы с тестами. Выразительный API. Создатели вдохновлялись наработками инженеров из Авито и красотой Kakao. Kaspresso расширяет лаконичный Kakao DSL — он предоставляет собственный Kotlin DSL для описания тестовых секций и шагов, внутри которых продолжает жить Kakao со своими
За Kaspresso API скрывается много работы для решения основных задач, рано или поздно возникающих перед разработчиком автотестов. Правильное использование фреймворка подталкивает пользователя к использованию зарекомендовавших себя паттернов построения декларативных, поддерживаемых и стабильных тестов (см. How to write autotests). Универсальность. Kaspresso как фреймворк-обертка агрегирует под своим API обращение и к Espresso и к UiAutomator. Таким образом, тесты с его использованием могут работать как внутри процесса тестируемого приложения, так и вне его, когда это необходимо. Например, если во время теста вам необходимо зайти в стороннее приложение или кликнуть на нотификацию, с его помощью сделать это не составит проблемы. Kaspresso предоставляет Kautomator — собственную Kakao-like обертку для работы с UiAutomator, что делает для пользователя обращение к Espresso и к UiAutomator визуально неотличимым.
Для взаимодействия с девайсом и операционной системой можно воспользоваться специальным фасадом device. Вот лишь неполный список того, что возможно с его помощью:
Архитектура самого Kaspresso во многом строится на использовании паттерна интерсептор, что обеспечивает соответствие следующим четырем пунктам этого критерия. Гибкость. Kaspresso предоставляет пользователю возможность конфигурации и расширения, позволяя внедрять кастомные реализации интерсепторов разных типов. Вы можете внедрять вызов собственного кода в ответ на различные события, например, на каждый вызов Стабильность. Kaspresso проникает в самые недра Espresso и UiAutomator и умеет повторять завалившиеся действия или проверки, закрывать всплывающие системные диалоги, автоматически доскраливать до элемента внутри
Логи. Доступны и по умолчанию используются готовые реализации интерсепторов, которые логируют необходимую информацию о каждых действии или проверке, стадии жизненного цикла шагов или всего теста. Скриншоты. Также доступны реализации интерсепторов для снятия скриншотов после каждого шага и по окончанию всего теста. Этот механизм легко кастомизировать, и Kaspresso будет делать скриншоты именно тогда, когда это нужно пользователю. Также скриншот можно сделать просто из кода теста:
Также см. Localization autotests. Доступ к adb. Еще одной не менее важной составляющей Kaspresso является AdbServer — HTTP-сервер, который умеет выполнять запрашиваемые из теста adb-команды. Он запускается на хосте, к которому подключены девайсы или эмуляторы, на которых будут запускаться тесты. Во время запуска теста на девайсе происходит соединение с сервером, и далее сервер может выполнять запрашиваемые из теста adb-команды для нужного девайса. AdbServer используется для реализации большинства функций, доступных через фасад device. Также внутри тестов вам доступно простое и удобное API для вызова необходимых adb-команд:
Однако AdbServer опционален, его не обязательно запускать, если в ваших тестах не требуется обращение к adb. Какую обертку выбрать
Из этой таблицы ответ должен быть очевиден. Если вы только начинаете погружение в мир автотестирования, проще всего будет начинать вместе с Kaspresso. Он решит за вас множество проблем, с которыми вы непременно столкнетесь. Он предоставляет удобный API, включающий и расширяющий Kakao, под которым скрывается обращение и к Espresso, и к UiAutomator, и к AdbServer, что значительно расширяет ваши возможности. И, что не менее важно, он содержит специальные точки расширения, так что вы сможете добиться от него необходимого поведения. |