Всем привет. Мы в Avokado Project продолжаем рассказывать про автотестирование в Android. Эта статья — обзор и сравнение существующих инструментов для написания UI-тестов.


Давайте начнем с того, что вспомним, как обычно выглядит процесс тестирования. Будем называть сущность, которая взаимодействует с приложением, клиентом. Для взаимодействия с приложением клиенту обычно доступно несколько интерфейсов: API, REST API, CLI, GUI и т.д. И если, например, API используются клиентами-программами, то GUI используется человеком.


Ожидания от поведения приложения описываются в спецификации. Задача тестирования — проверить, что поведение приложения соответствует спецификации.



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


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


Для реализации тестов и их автоматизированного запуска нам понадобится фреймворк тестирования (JUnit, Cucumber). Он включает в себя:


  • Набор функций для проверок в тестах — ассерты (assertions).
  • Механизм создания фикстур для шаринга данных между тестами. В общепринятом смысле фикстура — фиксированное состояние окружения, на котором выполняются тесты.
  • Раннер для запуска тестов.

В зону ответственности раннера в свою очередь входят:


  • подготовка окружения;
  • формирование набора тестов для исполнения;
  • запуск тестов;
  • получение результатов выполнения тестов;
  • подготовка отчетов о прохождении тестов;
  • сбор статистики.

Несмотря на то, что с фреймворком поставляется также и раннер, существует возможность использования более совершенных сторонних раннеров. Тема раннеров, их особенностей в контексте Android-тестирования, и выбора наиболее подходящего (Avito test runner, Marathon, Spoon, Fork) довольно обширна и заслуживает отдельной статьи, поэтому вернемся к самим тестам.


Если API приложения доступен тесту напрямую, то для GUI интерфейса нужны программные адаптеры — драйверы.



Тесты, обращающиеся к GUI-драйверам, называются UI-тестами. В отличие от unit-тестов, они выполняются на реальном девайсе или эмуляторе под управлением соответствующей мобильной операционной системы.


GUI-драйверы являются наиболее сложным компонентом всего стека тестирования. Они решают базовые, низкоуровневые задачи. Когда вы даете GUI-драйверу (Espresso, UiAutomator) команду «кликнуть на кнопку», он обрабатывает ее, взаимодействует с приложением, и эта команда превращается в клик по элементу.


Работа с API драйвера напрямую из теста имеет ряд недостатков:


  • Функциональность драйвера ограничена: для тестов требуется дополнительное логирование, валидация данных, ожидания, повторы неудачных действий.
  • API драйвера не способствует написанию хорошо читаемого и легко поддерживаемого кода тестов.
  • Часто необходимо абстрагировать и унифицировать доступ сразу к нескольким драйверам.

Для устранений таких проблем используются обертки для драйверов, например, Kakao или Kaspresso. Эти обертки могут сильно различаться в зависимости от решаемых задач. Некоторые из них — просто синтаксический сахар, другие же — довольно толстая прослойка, выполняющая много сложной работы.



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


Драйверы


В Android-мире четыре популярных драйвера:


  • Espresso.
  • UiAutomator.
  • Robotium.
  • Selendroid.

Все они работают на Android Instrumentation Framework — базовом API Android для взаимодействия с системой. Самые популярные — Espresso и UiAutomator. Они оба разрабатываются и поддерживаются компанией Google. Их без труда можно использовать одновременно в пределах одного теста. Давайте рассмотрим их поближе.


UiAutomator



UiAutomator поставляется вместе с Android SDK начиная с 16 версии API. Как GUI-драйвер он служит для поиска элементов интерфейса на экране девайса и эмуляции различных действий: кликов, тачей, свайпов, ввода текста, проверок на видимость. Рекордер для записи тестов он не предоставляет, зато предоставляет утилиту для просмотра древовидной структуры экрана — Ui Automator Viewer.


UiAutomator позволяет писать тесты по модели черного ящика (black-box). Он живет в собственном процессе и работает без доступа к исходному коду приложения. Значит, тест с его использованием может взаимодействовать практически с любыми приложениями, в том числе системными. Это открывает доступ к множеству функций, например, становятся возможны:


  • набор номера и совершение звонка;
  • отправка и чтение смс;
  • взаимодействие с нотификациями;
  • управление настройками сети, геолокации;
  • снятие скриншотов.

Внутри UiAutomator реализован при помощи AccessibilityService, облегчающего работу с девайсом людям с ограниченными возможностями. Именно AccessibilityService знает, что в каждый конкретный момент времени отрисовано на экране и умеет с этими элементами взаимодействовать. Для этого он строит виртуальное дерево компонентов просматриваемого экрана, в узлах которого находятся объекты AccessibilityNodeInfo. Эти узлы содержат метаинформацию о соответствующих им View: имя класса, координаты или отображаемый текст.



Каждая View реализует интерфейс AccessibilityEventSource и через Binder IPC оповещает системный AccessibilityManagerService о каждом действии, произведенном с ней, например, о клике или смене фокуса. AccessibilityManagerService передает эти данные в каждый активный на данный момент AccessibilityService и, в случае теста, UiAutomator, которые обновляют свое виртуальное дерево компонентов. По такой же схеме в обратную сторону передаются команды от AccessibilityService’ов и UiAutomator’a к отображаемым элементам интерфейса любого приложения в системе. Если элемент интерфейса не является наследником View, то для участия в такой схеме для него потребуется самостоятельно реализовать ряд методов.


Для более детального погружения в устройство работы UiAutomator рекомендуем ознакомиться с докладом “UI Automator deep diving”.


Среди недостатков UiAutomator:


  • Сложный и универсальный механизм работы, затрагивающий Binder IPC, реализован неидеально. Для того, чтобы выполнить то или иное действие UiAutomator ждет, пока приложение придет в консистентное состояние. Он ожидает появления временного окна, в течение которого от системы не поступит никаких событий. Такое устройство приводит не только к нестабильности и низкой скорости исполнения команд, но часто даже к длительным задержкам или к полной остановке теста.
  • Элементы интерфейса, не являющиеся наследниками View или отрисованные при помощи OpenGL или Unity, невидимы для UiAutomator’a, если разработчики заранее об этом не побеспокоились. Поэтому, например, при взаимодействии с мобильными играми у UiAutomator’a могут возникать проблемы.

Таким образом, наиболее подходящим сценарием для использования UiAutomator является не работа с тестируемым продуктовым приложением, а взаимодействие со сторонними или системными приложениями. Более подходящим инструментом для работы с продуктовым приложением является Espresso.


Espresso



Это также официальный фреймворк для UI-тестирования от Google, но тесты с его использованием работают уже по модели белого ящика (white-box). Espresso поддерживает Android API с 10 версии, хотя появился значительно позже. Предоставляет рекордер для записи тестов.


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


Espresso решает низкоуровневую задачу — найти необходимый элемент на экране по заданным параметрам (ViewMatcher), произвести с ним действия (ViewAction) или выполнить проверки (ViewAssertion).



Синтаксис Espresso реализован на основе фреймворка Hamcrest. Он построен на использовании иерархий вложенных матчеров — сущностей, описывающих требования к объекту, в случае Espresso — к элементам интерфейса. Они используются при задании параметров поиска элемента на экране, а также в проверках как описание свойств, которыми элемент должен обладать. Вложенность матчеров часто приводит к тому, что код тестов становится трудно читать и поддерживать.


@Test
public void espressoTest() {
    onView(allOf(allOf(withId(R.id.label_bf_hotelname), 
        isDescendantOfA(withId(R.id.custom_view_trip_review))), 
        isDescendantOfA(withId(R.id.contentView))))
        .check(matches(
            withEffectiveVisibility(Visibility.VISIBLE)
        ));
}

Стабильность и скорость тестов на Espresso обусловлены его внутренним устройством — все команды Espresso выполняются в том же процессе, в котором работает само тестируемое приложение. Действия и проверки преобразовываются и кладутся в очередь сообщений главного потока приложения. Выполняются они только когда приложение готово к этому, то есть его главный поток находится в состоянии ожидания пользовательского ввода (idle).



Для того, чтобы убедиться в этом, достаточно обратиться к внутренностям, например, метода check класса ViewInteraction. Передаваемый аргументом объект ViewAssertion вызовется на главном потоке после ожидания idle’а.


public ViewInteraction check(ViewAssertion viewAssertion) {
    // (...)
    runSynchronouslyOnUiThread(new Callable<Void>() {
        @Override
        public Void call() {
            uiController.loopMainThreadUntilIdle();
            // (...)
            viewAssertion.check(...)
        }
    });
}

Исходя из вышесказанного можно выделить следующие недостатки Espresso:


  • Ему требуется доступ к исходному коду тестируемого приложения.
  • Он не видит ничего за пределами процесса тестируемого приложения и не может взаимодействовать с другими приложениями. Например, совершить звонок удастся, только если тестируемое приложение и есть звонилка. А вот кликнуть на нотификацию Espresso уже не сможет совсем. Скорее всего в тестах придется прибегать к помощи UiAutomator.
  • API Espresso построен таким образом, что не способствует написанию чистого и читаемого кода тестов. Об этом придется придется заботиться отдельно.
  • У Espresso проблемы со стабильностью при работе со сложными асинхронными интерфейсами и со списками.

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


Robotium и Selendroid


Robotium и 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. Код тестов — это тоже код, причем зачастую очень нетривиальный. С одной стороны запутанный, загроможденный, многословный, императивный код тестов — это не только потенциальный источник багов в самих тестах, но и сложность в их поддержке. С другой стороны, творческие инженеры могут далеко зайти с выделением все новых и новых абстракций, усложнением манипуляций с ними и вообще с созданием велосипедов. На наш взгляд обертка была бы молодец, если обеспечивала бы единообразие структуры исходного кода тестов и предоставляла четкий набор абстракций и паттернов для использования.


Теперь давайте сравним популярные обертки с этими хотелками.


Appium



Appium — это довольно популярный кросс-платформенный 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 часто встречает неприятие со стороны разработчиков. Это является негативным фактором, поскольку по нашему опыту как раз разработчики должны драйвить процесс автотестирования. Подробнее в этих докладах: «Автотесты в Авито. Зачем они, как помогают, сколько стоят», «Как начать писать автотесты и не сойти с ума».


Kakao



Kakao — простой и удобный Kotlin DSL для Espresso. Он позволяет упростить код тестов на Espresso и повысить его читаемость.


Выразительный API. По сути Kakao — это написанный за вас boilerplate-код для поддержки в тестах следующих двух паттернов:


  • KView — это Kakao-представление элемента интерфейса на экране, с которым происходит взаимодействие во время теста. Например, Kakao предоставляет такие готовые реализации, как KEditText, KButton и т.д. Пользователь может добавлять собственные.
  • Screen — это реализация пришедшего из мира веб-разработки паттерна PageObject. Screen — это базовый класс, на основе которого рекомендуется создавать собственные stateless-хранилища всех KView на соответствующих экранах приложения. Рекомендуется создавать отдельный Screen для каждой активити, фрагмента или, например, диалога вашего приложения. Screen’ы описываются в отдельных файлах, таким образом работа по обнаружению элементов интерфейса на экране отделяется от самих тестов.

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


@Test
public void espressoTest() {
    onView(allOf(allOf(withId(R.id.label_bf_hotelname),
        isDescendantOfA(withId(R.id.custom_view_trip_review))),
        isDescendantOfA(withId(R.id.contentView))))
        .check(matches(
            withEffectiveVisibility(Visibility.VISIBLE)
        ));
}

получаем это:


object MainScreen : Screen<MainScreen>() {
    val hotelName = KTextView { withId(R.id.label_bf_hotelname) }
}

@Test
fun kakaoTest() {
    MainScreen { 
        hotelName { 
            isVisible() 
        } 
    }
}

Стабильность. Kakao — это намного более тонкая обертка, нежели Appium, и поэтому вместе со своими бонусами она не вносит практически никакого оверхэда в ваши тесты. Команды по-прежнему исполняются внутри главного потока тестируемого приложения. Все особенности Espresso в том, что касается стабильности тестов, также унаследованы — возможны проблемы при работе со сложными асинхронными интерфейсами, списками и т.д.


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


Гибкость. Для обеспечения расширяемости в Kakao реализован механизм интерсепторов. Пользователь через специальное API может внедрять вызов собственного кода в момент, когда выполняется та или иная Espresso-команда. Благодаря этому пользователь может самостоятельно добавить в тесты, например, ожидания или повторы неудачных действий. Однако что-либо сделать вне процесса тестируемого приложения по-прежнему не удастся.


Логи. Логов из коробки нет, как и в Espresso. Хотя можно добавить их самостоятельно при помощи интерсепторов.


Скриншоты. Механизма снятия скриншотов из коробки также нет. Потребуется собственная реализация.


Доступ к adb. Отсутствует, как и у Espresso.


Итог. Таким образом, Kakao — это удобный DSL для упрощения написания и поддержки тестов на Espresso. Но помимо него для решения многих бытовых задач вам необходимо будет использовать UiAutomator, чей API уже не такой выразительный. Скорее всего со временем вам придется дописать множество собственных надстроек для расширения функциональности, например, для логирования и повторов.


Barista



Barista — это объемная библиотека, содержащая большое количество полезных решений и приемов при работе с Espresso.


Выразительный API. Как и Kakao, эта библиотека построена поверх Espresso. Однако Barista — скорее широкий набор приемов, которые можно выборочно использовать в своих Espresso-тестах. Этот набор приемов включает в себя:


  • Удобные и лаконичные helper-методы для взаимодействия с элементами интерфейса, которые скрывают от нас Espresso вызовы. Например, для обыкновенного клика по кнопке вместо onView(withId(R.id.button)).perform(click()) на голом Espresso получаем простой метод clickOn(R.id.button). Или вместо множества строчек кода для клика на элемент списка получаем просто clickListItem(R.id.list, 4).
  • Расширенный Android-специфичный Assertions API.
  • Инструмент для мокирования результатов интентов, правда пока только для работы с камерой.
  • Инструмент для работы с runtime permission’ами.
  • Огромное количество test rule’ов, например, для перезапуска flaky-тестов или для очистки после прогона теста shared preferences, базы данных, или удаления файлов с устройства.

Также Barista умеет работать с NestedScrollView, в отличие от Espresso, и еще включает в себя множество других приятных фишек. Но никакого специального DSL библиотека не предоставляет, поэтому формирование архитектуры в тестах остается за пользователем.


Стабильность. Благодаря вышеупомянутому FlakyTestRule есть возможность перезапускать падающие тесты. Однако обычно эту задачу успешно решают раннеры. К тому же в библиотеке отсутствует механизм ретраев для отдельных действий и проверок, что позволило бы не перезапускать каждый раз весь тест целиком и экономить время.


Универсальность. Исключительно белый ящик из-за работы только поверх Espresso.


Гибкость. Расширять в этой библиотеке по сути нечего, можно только вдохновляться и выделять у себя в проекте подобные рабочие Espresso приемы. И ваша свобода ограничена только функциональностью Espresso.


Логи. Механизм для логирования шагов, действий и проверок в тестах отсутствует.


Скриншоты. Механизм снятия скриншотов также отсутствует.


Доступ к adb. Отсутствует, как и у Espresso.


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


Kaspresso



Фреймворк-обертка для UI-тестирования, который претендует на то, чтобы избавить мир от зла и стать практически единственной зависимостью в вашем проекте для работы с тестами.


Выразительный API. Создатели вдохновлялись наработками инженеров из Авито и красотой Kakao. Kaspresso расширяет лаконичный Kakao DSL — он предоставляет собственный Kotlin DSL для описания тестовых секций и шагов, внутри которых продолжает жить Kakao со своими Screen’ами и KView.


@RunWith(AndroidJUnit4::class)
class OpenHomeScreenTest : TestCase() {

    @Test
    fun kaspressoTest() {
        before { ... }
        .after { ... }
        .run {
            step("1. Open Home screen") { 
                MainScreen {
                    openHomeScreenBtn.click()
                }
            }
            step("2. Check Home title") { 
                HomeScreen {
                    title.isVisible()
                }
            }
            step("3. Do some stuff") { ... }
        }
    }
}

За Kaspresso API скрывается много работы для решения основных задач, рано или поздно возникающих перед разработчиком автотестов. Правильное использование фреймворка подталкивает пользователя к использованию зарекомендовавших себя паттернов построения декларативных, поддерживаемых и стабильных тестов (см. How to write autotests).


Универсальность. Kaspresso как фреймворк-обертка агрегирует под своим API обращение и к Espresso и к UiAutomator. Таким образом, тесты с его использованием могут работать как внутри процесса тестируемого приложения, так и вне его, когда это необходимо. Например, если во время теста вам необходимо зайти в стороннее приложение или кликнуть на нотификацию, с его помощью сделать это не составит проблемы. Kaspresso предоставляет Kautomator — собственную Kakao-like обертку для работы с UiAutomator, что делает для пользователя обращение к Espresso и к UiAutomator визуально неотличимым.


object KakaoScreen : Screen<KakaoScreen>() {
    val title = KTextView { withText(titleText) }
    val btn = KButton { withId(R.id.button1) }
}

object KautomatorScreen : UiScreen<KautomatorScreen>() {
    val title = UiTextView { withText(titleText) }
    val btn = UiButton { withId(pkgName, R.id.button2) }
}

@Test
fun kaspressoTest() {
    KakaoScreen { 
        title.isVisible()
        btn.click()
    }
    KautomatorScreen {
        title.isVisible()
        btn.click()
    }
}

Для взаимодействия с девайсом и операционной системой можно воспользоваться специальным фасадом device. Вот лишь неполный список того, что возможно с его помощью:


  • набор номера и совершение звонка;
  • отправка и чтение смс;
  • работа с файловой системой;
  • управление настройками сети, языка, геолокации;
  • установка и удаление приложений во время теста;
  • снятие скриншотов;
  • смена ориентации девайса;
  • взаимодействие с хардварными кнопками: back, home, recents.

Архитектура самого Kaspresso во многом строится на использовании паттерна интерсептор, что обеспечивает соответствие следующим четырем пунктам этого критерия.


Гибкость. Kaspresso предоставляет пользователю возможность конфигурации и расширения, позволяя внедрять кастомные реализации интерсепторов разных типов. Вы можете внедрять вызов собственного кода в ответ на различные события, например, на каждый вызов ViewAction или ViewAssertion, на старт, успешное окончание или падение шага вашего теста или всего теста целиком. Стоит отдельно подчеркнуть, что все параметры могут быть настроены перед запуском теста, ненужная функциональность может быть отключена, а недостающая добавлена (см. Configurator).


Стабильность. Kaspresso проникает в самые недра Espresso и UiAutomator и умеет повторять завалившиеся действия или проверки, закрывать всплывающие системные диалоги, автоматически доскраливать до элемента внутри ScrollView, если это необходимо, что заметно повышает стабильность тестов. Вы можете воспользоваться готовыми реализациями интерсепторов или специальными helper-методами. Например, для повтора неудачного клика по кнопке заверните ваше действие в блок flakySafely:


MainScreen {
    flakySafely {
        btn.click()
    }
}

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


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


device.screenshots.take("MainScreen_step_1")

Также см. Localization autotests.


Доступ к adb. Еще одной не менее важной составляющей Kaspresso является AdbServer — HTTP-сервер, который умеет выполнять запрашиваемые из теста adb-команды. Он запускается на хосте, к которому подключены девайсы или эмуляторы, на которых будут запускаться тесты. Во время запуска теста на девайсе происходит соединение с сервером, и далее сервер может выполнять запрашиваемые из теста adb-команды для нужного девайса. AdbServer используется для реализации большинства функций, доступных через фасад device. Также внутри тестов вам доступно простое и удобное API для вызова необходимых adb-команд:


adbServer.performAdb("emu sms send +79111111111 $smsText")
adbServer.perfromShell("rm -f $filePath")

Однако AdbServer опционален, его не обязательно запускать, если в ваших тестах не требуется обращение к adb.


Какую обертку выбрать


Appium Kakao Barista Kaspresso
Выразительный API ? + ± +
Универсальность + ? ? +
Гибкость ? + ? +
Стабильность ? ± ± +
Логи ? ± ? +
Скриншоты ? ? ? +
Доступ к adb + ? ? +
Из этой таблицы ответ должен быть очевиден. Если вы только начинаете погружение в мир автотестирования, проще всего будет начинать вместе с Kaspresso. Он решит за вас множество проблем, с которыми вы непременно столкнетесь. Он предоставляет удобный API, включающий и расширяющий Kakao, под которым скрывается обращение и к Espresso, и к UiAutomator, и к AdbServer, что значительно расширяет ваши возможности. И, что не менее важно, он содержит специальные точки расширения, так что вы сможете добиться от него необходимого поведения.