на каком шаге произошла ошибка

Создание бизнес-процесса на языке BPEL с использованием платформы Serena Business Manager

Пройдясь поиском по Хабрахабру, удалось обнаружить не так уж и много информации, посвященной, надо сказать, не очень распространённому языку BPEL (Business Process Execution Language). Если говорить в общем, то BPEL – это язык, основанный на формате XML, который позволяет описывать логику бизнес-процессов через использование веб-служб.

на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка

Реализаций движков, позволяющих создавать процессы с использованием этого языка, мне известно не так уж и много. В частности, можно упомянуть Oracle BPEL Process Manager и продукт, о котором пойдет речь дальше – Serena Business Manager (SBM). SBM позволяет быстро создавать web-приложения, автоматизирующие какой-нибудь процесс. В модели процесса (workflow) предусмотрена возможность в момент изменения состояния вызвать внешнюю web службу. А если нужно реализовать какую-нибудь логику и одного вызова недостаточно? Вот тут и пригодится процедура, написанная на языке BPEL и исполняемая средствами той же платформы BPM.

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

Задача была поставлена следующим образом – разработать функционал копирования бизнес-сущностей (в моём процессе – TD Links), но не просто так, а с предварительным опросом стороннего веб-сервиса. Этот сервис (bridge) обладает методом, принимающим на вход некоторые атрибуты объекта TD Link. Затем bridge опрашивает стороннюю систему и в ответ сообщает, может ли объект с такими атрибутами существовать в нашей системе. Как он это делает меня не интересует, bridge для меня является «чёрным ящиком».

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

Список шагов, из которых может строиться процесс, выглядит следующим образом:

на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка

Наиболее важные из них это:

Итоговый worflow выглядит так:

на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка

Так как объекты TD Links существуют в системе не сами по себе, а в связи с другими объектами (типа Stagings), для начала мне потребовалось найти в системе нужные мне Stagings.

AppServices обладает методом, позволяющим делать выборку с использованием SQL-запроса. SQL-запрос собран прямо в виде строки:
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка

Да, важное отступление. Движок позволяет определять переменные, которые будут использоваться по ходу процесса, чем я в данном случае и воспользовался:

на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка

Сформировав SQL-запрос, можно перейти к вызову нужного метода, выполнив маппинг данных:

на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка

Для маппинга могут использоваться как переменные, так и результаты выполнения предыдущих шагов процесса (например, результат запуска веб-службы).

После аналогичным способом (через SQL-запрос) делается выборка объектов для копирования – TD Links и запуск цикла.
И, наконец, этап опроса внешнего веб-сервиса, настраивается также через элемент типа «Service».

Последним шагом выполняем создание копии объекта TD Link, с сохранением в ней результатов, полученных от bridge (в том числе сообщение об ошибке, если оно есть).

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

на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка

Что можно сказать в итоге. Из плюсов предложенной реализации можно отметить следующие моменты:

Спасибо всем, кто дочитал пост до конца. Будем рады ответить на ваши вопросы в комментариях.

Источник

Автоматизация тестирования Android приложений

Концепция автоматического тестирования

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

Далее рассматриваются средства, позволяющие автоматизировать перечисленные шаги.

Управление Android устройствами

Для начала нужно выделить компьютер на котором будет запускаться автоматическое тестирование и настроить на нем Android SDK. Примеры приводятся для компьютера с установленной ОС Linux.

На всех тестируемых устройствах нужно отключить экран блокировки и максимально увеличить время ожидания. Для некоторых методов тестирования нужно отключить смену ориентации экрана.

В Android SDK имеются две утилиты для управления устройствами: adb и MonkeyRunner.

Я постараюсь подробно описать автоматизацию действий, использующихся при тестировании. Тем, кто знаком с ADB и MonkeyRunner имеет смысл сразу переходить к разделу «Способы автоматизированного тестирования».

Управление с помощью утилиты ADB

ADB (Android Debug Bridge) – утилита для управления Android устройствами из командной строки. Официальная документация по ADB: developer.android.com/tools/help/adb.html

Проверка работы ADB

Устанавливаем и настраиваем Android SDK, подключаем к компьютеру Android устройства и выполняем команду:

Команда выдаст список всех подключенных устройств. Если список устройств не пуст, значит ADB настроен и работает.

Работа с несколькими устройствами

Основные команды ADB

Открыть консоль на устройстве:

Запустить команду на устройстве:

В Android присутствуют многие стандартные утилиты Linux: ls, cat, dmesg,…

Установить приложение из apk файла:

Название package можно получить из apk файла командой:

Загрузить файл с устройства на компьютер:

Загрузить файл с компьютера на устройство:

Запускает указанную activity. Название activity, которая запускается при выборе приложения в меню можно получить из apk файла командой:

Чтение логов

Чтение логов в Android производится утилитой logcat.
Домашняя страница утилиты logcat: developer.android.com/tools/help/logcat.html

Считать логи с устройства (блокируется до нажатия Ctrl-C):

Очистить буфер логов на устройстве:

Считать буфер логов на устройстве (выдает текущее содержимое буфера, не блокируется):

Снятие скриншотов с помощью утилиты screencap

Утилита screencap сохраняет текущее содержимое экрана в графический файл:

Утилита screencap имеется на телефонах с Android 4.x и выше. На предыдущих версиях Android снятие скриншотов можно производить с помощью MonkeyRunner.

Пример BASH скрипта для тестирования приложения c помощью ADB

Управление с помощью MonkeyRunner

Утилита MonkeyRunner предоставляет API для написания скриптов, которые управляют Android устройстами. С помощью MonkeyRunner можно написать скрипт на языке Python, который устанавливает Android приложение, запускает его, имитирует действия пользователя, снимает скриншоты и сохраняет их на компьютер. Утилита MonkeyRunner использует Jython для выполнения скриптов.

на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка

Чтение логов с помощью MonkeyRunner

Скрипт запишет логи в файл example.log в текущей директории.

Снятие скриншотов

Скрипт снимает скриншот и сохраняет его в файл screenshot.png в текущей директории.

Пример управления устройством с помощью MonkeyRunner

Средства автоматизированного тестирования

Тестирование с помощью monkey

Представьте, что устройство попало в цепкие лапы очень активной и творческой обезьяны – утилита monkey призвана имитировать подобную ситуацию.

Утилита monkey входит в состав Android SDK. Утилита отправляет на устройство поток псевдо-случайных действий пользователя. Параметры командной строки задают количество действий пользователя, соотношение их типов и имя тестируемого пакета, чтобы, например, обезьяна не вышла за пределы тестируемого приложения и не начала рассылать SMS по всем контактам из адресной книги.

Примеры использования и перечень параметров приведены на домашней странице: developer.android.com/tools/help/monkey.html

Главное достоинство monkey – отсутствие затрат на поддержку. Кроме того, стресс-тестирование приложения потоком произвольных событий может обнаружить нетривиальные ошибки.

Тестирование с помощью MonkeyRunner

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

Тестирование с помощью getevent/sendevent

На устройстве должны воспроизвестись записанные действия.

Тестирование с помощью Robotium

В отличии от рассмотренных ранее способов Robotium не входит в состав Android SDK, а распространяется под Open Source лицензией.

Главное отличие Robotium в том, что тестовые действия описываются на уровне интерфейса приложения. В рассмотренных ранее способах тестовые действия явно или неявно описывались на уровне устройств ввода.

Например, в приложении нужно нажать кнопку «OK». С помощью скрипта MonkeyRunner нажатие на кнопку реализуется как: «Коснуться точки экрана с координатами (x0, y0)». С помощью Robotium это реализуется как: «Нажать кнопку с текстом «OK»».

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

Кроме того, Robotium позволяет проверять реакцию приложения на действие.

Например, после нажатия на кнопку «OK» в приложении должен появиться список с элементом «Item 1». С помощью Robotium можно проверить, появился ли список с таким элементом.

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

Сравнение способов тестирования

Способ тестированияДостоинстваНедостатки
Monkey – поток случайных действий пользователя.Отсутствуют затраты на сопровождение.
Не зависит от устройства.
Стресс-тестирование позволяет обнаружить нетривиальные ошибки.
Качество тестирования варьируется от приложения к приложению.
Найденные ошибки сложно воспроизвести.
Нет проверки состояния приложения.
MonkeyRunner – скрипт управления устройством.Гибкость.Сложность написания и поддержки скриптов даже для простых приложений.
getevent/sendevent – запись/воспроизведение действий пользователя.Для записи последовательности действий не требуются навыки программирования.Записанная последовательность действий подходит только к одному устройству при фиксированной ориентации.
При изменении интерфейса приложения необходимо заново записать последовательность действий.
Нет проверки состояния приложения.
Robotium – сценарий тестирования интерфейса приложения с проверкой состояния.Действия описываются на уровне интерфейса приложения.
Сценарий может быть независимым от разрешения экрана и ориентации устройства.
После совершения действия можно проверять состояние приложения.
Сложность написания сценариев на языке Java. При изменении интерфейса приложения сценарий придется модифицировать.

Анализ результатов

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

Анализ логов

Анализ скриншотов

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

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

MonkeyRunner позволяет сравнить два скриншота с заданным допуском в процентах:

К сожалению, в API MonkeyImage не предусмотрена функция загрузки из файла. Поэтому для сравнения сохраненных скриншотов придется писать свою функцию, например с помощью Python Imaging Library.

Сброс состояния устройства после тестирования

После тестирования приложения устройство нужно вернуть в первоначальное состояние.

Многократное нажатие кнопки «Назад»

Нажимаем кнопку «Назад» используя MonkeyRunner:

На практике этот вариант оптимален, так как имитирует поведение реального пользователя.

Заключение

В заметке были рассмотрены некоторые способы автоматического тестирования Android приложений, их достоинства и недостатки. Кроме того, рассмотрены инструменты, входящие в Android SDK или распространяющиеся под Open Source лицензией.

Хочется отметить, что автоматическое тестирование не является панацеей и не заменяет другие виды тестирования. Качественный продукт получается при грамотно построенном процессе тестирования, сочетающем различные способы.

Источник

Автоматизация UI-тестирования в приложении Недвижимости на Android. Доклад Яндекса

Чем больше процессов тестирования автоматизированы, тем эффективнее релизный пайплайн и тем быстрее пользователи получают новые возможности в сервисе. Руководитель Android-разработки Яндекс.Недвижимости Александр Рогов вспомнил, как эволюционировало UI-тестирование в его команде, как разработчики пришли к идее автоматизации, почему использовали фреймворк Espresso, с какими проблемами столкнулись и что в итоге получили.

— Начать хотелось бы с небольшого исторического экскурса. Когда я пришел в команду, она была маленькая, из двух-трех человек. Был один менеджер, один тестировщик. Релизы катились редко, примерно раз в месяц. Релиз обычно был прикреплен к какой-то фиче. Пока фичу не сделаем, релиз не случался. Соответственно, каждый релиз у нас было регрессионное тестирование. Такой неспешный режим работы нас всех устраивал, все было хорошо.

Но продукт начал развиваться, команда начала увеличиваться, менеджеров становилось все больше. И у менеджеров появился конфликт интересов, чью фичу катить, к чьей фиче привязывать релиз и так далее. Мы решили, что надо принимать меры, а именно — ускорять наш процесс, исправить наше узкое место в виде тестирования.

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

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

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

И здесь как раз появляется идея автоматизации тестирования. На самом деле, в процессе автоматизации тестирования мы планомерно перебрали все эти варианты. Переход с регрессионного тестирования проходил примерно так, что количество покрытых тестов увеличилось, выборочное тестирование уменьшалось, и даже чуть-чуть увеличилась команда тестирования.

Автоматизация UI-тестирования

Мы решили заняться написанием инструментальных тестов. Это тесты, которые прогоняются на эмуляторе или устройстве. В их основе лежит класс Instrumentation, который предоставляют средства мониторинга взаимодействия приложения и системы.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
И в отличие от обычных UI-тестов, как я уже сказал, они прогоняются на реальном устройстве. То есть поднимается Application, запускаются тесты.

Для написания таких тестов мы решили использовать фреймворк Espresso, его разрабатывает Google.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Он позиционируется как очень простой и удобный инструмент для написания стабильных тестов. Рассмотрим некоторые основные компоненты Espresso. Здесь есть так называемые ViewMatchers — средства, которые позволяют нам искать элементы на экране.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
ViewActions позволяют нам взаимодействовать с найденными элементами, а ViewAssertions — это некоторые проверки, которые мы можем выполнять с найденными элементами.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
В качестве примера будем рассматривать простенькое приложение, в котором есть два экрана. Вводим пароль на одном, нажимаем кнопку «Проверить». Идем делать сетевой запрос. На втором экране отображаем результат нашей операции.

Пример такого теста будет выглядеть примерно так.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Рассмотрим основные части этого теста. Есть некоторые Rules, которые позволяют нам модифицировать процесс исполнения теста. Например, ActivityTestRule позволяет нам в начале каждого теста запускать Activity и выделять соответствующие ресурсы.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
А в теле теста выполняются такие простейшие манипуляции. Мы находим элемент, выполняем, например, ввод текста.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Находим следующий элемент с кнопкой, выполняем нажатие.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Находим следующий элемент с результатом, проверяем его. Примерно так может выглядеть базовый тест Espresso.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Также фреймворк Espresso предоставляет нам базовый отчет, из которого мы можем понять, сколько по времени проходили наши тесты, какой процент тестов упал, а какой не упал.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Можно проваливаться по пакетам, по тестам.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Можно посмотреть ошибки.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Для ошибок выводится самый простой crash logs, ничего особо интересного.

Проанализировав все эти средства Espresso, мы выделили для себя недостатки. Например, изолированность. По умолчанию все тесты Espresso прогоняются в рамках одного инстанса Instrumentation и, соответственно, одного инстанса Application. Также нет никакой изолированности от внешней среды, такой как интернет. И от других сторонних приложений. Мы подробнее поговорим об этом дальше.

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

Также нам не очень понравилось, как пишутся такие тесты. Нам показалось, что они достаточно многословны. И нам не понравился отчет. Обо всем этом подробнее мы и поговорим. Посмотрим, что мы делали, чтобы решить эти проблемы.

Изолированность между тестами

Как я уже сказал, по умолчанию все тесты запускаются в одном инстансе Instrumentation, но существует средство, которое позволяет эту проблему решить: Android Test Orchestrator.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Это инструмент, который позволяет запускать каждый тест изолированно от остальных, в своем инстансе Instrumentation. Для каждого теста будет запускаться свой Application. Не будет никакого разделяемого состояния между тестами.

Даже если нам необходимо почистить базу данных, мы можем использовать специальные флаги, как здесь на слайде, clearPackageData. Это позволит нам максимально изолировать тесты друг от друга.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Концептуально это выглядит так: вместе с APK тестов и приложения устанавливается еще Orchestrator, и он управляет процессом исполнения тестов: поодиночке запускает каждый тест в своем инстансе Instrumentation.

Из плюсов, как я уже сказал, — максимальная изоляция состояния. Также получаются изолированные крэши. Поскольку каждый тест исполняется в своем Instrumentation, то если он падает по какой-то причине, то остальные тесты продолжают исполняться. В каком-то смысле повышается стабильность наших тестов. Из минусов: значительно замедляется исполнение тестов, так как на каждый тест мы запускаем свой инстанс Application.

Изолированность от внешней среды

Все современные приложения ходят в сеть, поэтому мы подвержены такому явлению, как отсутствие интернета. Что делать, если интернет пропал и тесты начинают падать? Нестабильные тесты — не то, к чему мы стремились. Мы хотели добиться максимально стабильного исполнения тестов, чтобы не тратить время на ненужные нам разборки.

Здесь мы решили использовать MockWebServer. Мы в своем проекте используем библиотеку OkHttpClient для сетевого взаимодействия. В ней есть модуль MockWebServer. Идея была запускать некий локальный WebServer и направлять все сетевые запросы нашего API на localhost.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Для реализации нам потребовалось переопределить Application для тестов.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Мы унаследовались от нашего реального Application.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Определили некий Dispatcher, куда мы будем мокать наши запросы. Запустили WebServer.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Далее нам необходимо, чтобы в тестах запускался именно наш тестовый Application, а не реальный.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Для этого нужно переопределить AndroidJUnitRunner, сделав кастомный, у которого есть специальный метод newApplication, позволяющий подменить реализацию на нашу тестовую.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Следующей задачей для нас стало перенаправить все наши сетевые запросы на localhost. Для этого нам помог Dagger 2, который мы используем в проекте.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Здесь все по аналогии. Есть некоторый Application-компонент, для теста мы создаем TestApplication-компонент, который наследуется от Application-компонента, но использует другие модули сетевого взаимодействия.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Вот пример такого модуля.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Оба модуля используют базовые сетевые модули, но предоставляют разную реализацию endpoint и OkHttpClient. Вот наш сетевой модуль, который как раз использует эти компоненты.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Когда мы запускаем что-то в тестах, то ходим на localhost.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Когда мы запускаем prod в сборку, то мы идем на продовый endpoint.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Что получилось в тесте? Добавляется вот такой блок для конфигурации WebServer. Здесь мы используем простенький DSL, в котором мы обращаемся к Instrumentation, получаем Application, кастуем его к нашему тестовому, и оттуда уже можем обращаться к его полям.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Помните, мы определили там Dispatcher? Какие плюсы? Такой подход позволяет нам изолироваться от внешней среды и тем самым повышается стабильность. Наши тесты становятся независимыми от интернета и перестают неупорядоченно падать.

Из минусов: необходимо готовить Mock’и ответов. Порой они бывают очень громоздкими, это повышает трудоемкость.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Пару слов об изоляции от внешних приложений. Здесь у Espresso есть отдельный модуль под названием Espresso-Intents. Он позволяет нам записывать ожидаемые Intent, которые запускает наше приложение, и мокать их ответ. Но на этом мы не будем подробно останавливаться.

Улучшаем восприятие теста

Помните, я рассказал, что нам не понравилось, как пишутся Espresso-тесты и нам хотелось добиться чего-то более лаконичного? Изначально планировалось, что команда тестирования будет смотреть код наших тестов и таким образом валидировать их.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Пример. Если посмотреть на наш тест, то видно, что для совершения простых действий — введи текст, нажми на кнопку и так далее — нам приходится совершать много манипуляций.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Здесь нам на помощь пришли так называемые тестовые роботы. Идея этих роботов: мы пытаемся отделить то, что мы делаем, от того, как мы это делаем. Появляются высокоабстрактные методы взаимодействия, которые скрывают от нас детали реализации.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Пример такого тестового робота мы можем посмотреть здесь. Тестовый робот — это дополнительная абстракция, представляющая собой часть пользовательского интерфейса, для которой характерен набор неких базовых действий.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Например, в нашем случае на первом экране ввода пароля есть действие «ввести пароль» и действие «нажать на кнопку». Мы посмотрели и поняли: было бы удобно переиспользовать алгоритмы поиска элементов на экране, и тоже выделили их в отдельную абстракцию, которую мы назвали Lookup.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Таким образом, получается базовый тест, который был разбит на робота и Lookup и преобразился примерно так.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Что мы видим теперь? Появились некоторые контексты, мы видим два экрана. Для каждого экрана мы создали своего робота. В каждом роботе есть функциональность, характерная для этого экрана. В тесте мы теперь можем это четко наблюдать. Есть некоторое разделение по контексту, тесты стало проще воспринимать.

Как я уже сказал, появилась декомпозиция — по функциональности, по Matchers. Из минусов: появляются Boilerplates по написанию роботов. Поговорим о нем более подробно чуть позднее.

Стабильность Espresso

Поговорим про стабильность Espresso и поймем, за счет чего она достигается. Google позиционирует свой фреймворк как инструмент для написания стабильных тестов.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
В основе этгого лежит следующий подход. Каждый раз, когда мы вызываем onView и пытаемся выполнить какие-то действия или проверки, они не будут выполнены до тех пор, пока очередь сообщений основного потока не будет пуста, либо не будет выполняться никаких AsyncTask, либо все так называемые IdlingResource будут находиться в состоянии idle.

Первые два пункта, очередь сообщений и AsyncTask, — это компоненты, которые известны системе Android, и она может их сама контролировать. Но существует множество других ресурсов, о которых Android не знает и не понимает, как их контролировать. Поэтому здесь вводится дополнительная абстракция IdlingResource. Этот инструмент предоставляет разработчику возможность определять такие асинхронные операции и сообщает фреймворку Espresso, что пока исполнять нельзя тест.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
В качестве примера такого ресурса рассмотрим OkHttp3IdlingResource. Так как мы применяем библиотеку OkHttp, то можем без проблем использовать и его тоже. Подключить к нашему тесту можно с помощью Rules, о которых я уже говорил.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Здесь используется тип ExternalResource. Его особенность в том, что он выполняет действия до исполнения теста и после.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
А создать такой ресурс можно опять-таки на основе доступа к нашему тестовому контексту, из которого мы можем получить OkHttpClient. Это реализовывается средствами Dagger.

Пример использования. Нам необходимо добавить такой Rule в тест. Для этого используется RuleChain, то есть цепочка Rules.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
В цепочку можно добавлять сколько угодно правил.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Из плюсов IdlingResource: у пользователя появляется возможность размечать асинхронные ресурсы и тем самым повышать стабильность своего приложения. Из минусов: не всегда просто эти ресурсы реализовать. Природа наших приложений может быть в значительной степени асинхронной, не все ресурсы могут быть легко доступны из тестового кода, и здесь могут возникать проблемы.

Что же делать, если использование IdlingResource затруднительно? Мы хотим добиться написания стабильных тестов, но сейчас, например, не можем себе позволить реализовать IdlingResource во всех местах приложения.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Мы, например, использовали вот такой известный костыль. Грубо говоря, это цикл, в котором мы проверяем, не выполнилось ли наше условие, не оказались ли мы в ожидаемом состоянии. Если по истечении заданного интервала мы не оказались в ожидаемом состоянии, то тест падает. За счет этого простого инструмента, который не рекомендуем к использованию, а рекомендуем все-таки использовать IdlingResource, нам удалось добиться значительной стабильности исполнения наших тестов.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Как использовать это в тесте? Предположим, мы нажали на кнопку «проверить пароль», переходим на вторую Activity, и на ней мы, прежде чем выполнять проверку, можем дождаться — действительно ли элемент появился на экране? Если да — выполнить проверку. Интеграция такой конструкции в тесте выглядит достаточно лаконично. Чуть-чуть избыточно, но что делать.

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

Улучшаем отчет

Еще одним пунктом, о котором я говорил, был отчет. Нам не понравился базовый отчет, нам хотелось большего: проще узнавать, в чем проблема, быстрее реагировать на возникающие сложности.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Здесь мы решили попробовать использовать фреймворк построения отчетов Allure. Легковесный, предоставляет дополнительные средства построения отчета. Интеграция Allure происходит очень просто. Достаточно унаследоваться от их AllureAndroidJUnitRunner, и это уже позволит вам строить отчет. Также в Allure предоставляется базовый набор Rules. Например, есть ScreenshotRule, WindowHierarchyRule, LogcatRule. То есть, если у вас происходит тест, к нему автоматически будут добавлены скриншоты, логи из Logcat, иерархия представлений. Уже можно будет проанализировать результаты, информации чуть больше, чем в случае с Espresso.

Генерация отчета происходит либо из командной строки, либо с помощью Allure Gradle plugin.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Отчет выглядит примерно так. Опять-таки, мы видим, сколько тестов выполнилось, сколько нет и сколько они выполнялись по времени.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Можно проваливаться в тесты.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Увидеть, как раз на этом скриншоте, что к сфейленному тесту добавилась дополнительная информация. Еще одна интересная возможность, которую предоставляет Allure: накопление информации и ведение статистики. То есть когда вы агрегируете Allure-отчеты в одном месте, можно вести настоящий трекинг того, как проходило ваше тестирование, как вы фиксили проблемы, как они появлялись, исчезали и так далее.

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

Мы были очень заинтересованы в том, чтобы получился более документированный отчет, чтобы команда тестирования смотрела не в код наших тестов, а в отчет и понимала, что там происходило.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Для этого мы решили создать прослойку между основными компонентами Espresso, которые мы назвали именованные компоненты.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Это просто обертка, которая добавляет имя. Таким образом появились обертки для Matchers.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
И для Assertions, и для Actions.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
И для Interaction. Входной точкой для наших тестов стал уже не Espresso#onView, а NamedViewInteraction#onView.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Идея была в том, что для всех действий добавляется человекопонятное имя, которое будет отражено в отчете с помощью команды step, которую здесь можно видеть.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
То есть каждый раз, когда мы выполняем проверку, в отчете будет зафиксировано, что именно мы проверяем.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Также мы модифицировали всех наших роботов, добавив в их входную точку команду step, которая говорит, что начинается активность. И модифицировали все наши Lookup — добавили к ним человекопонятные имена.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Это привело к тому, что сгенерировался вот такой отчет, из которого уже можно понять, что и в каком контексте происходит, что и куда мы вводим.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Тестирование может прочитать этот отчет и понять, какой пользовательский сценарий мы этим покрыли.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Также здесь можно видеть, на каком шаге произошла ошибка. Нам это очень понравилось.

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

Качество тестов

На этом этапе у нас уже был какой-никакой подход к написанию тестов. Они были достаточно стабильными. У нас уже был отчет, и мы стали задумываться, насколько качественно мы вообще проверяем UI?

Ведь если бы проверял тестировщик, он смотрел бы не только на то, что написано в поле, что это хороший пароль. Он проверял бы, как элемент расположен на экране, смотрел бы на цвет текста, на шрифт и так далее. Мы подумали над этим, и вот пример такой проблемы. Кто-то поменял верстку. Или мы обновили какую-то библиотеку, и верстка поехала, constraint сломался, или еще что-то.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Текущий тест на эту проблему никак не реагирует, но тестирование в ручном режиме смогло бы ее обнаружить. Мы пришли к тому, что решили сравнивать скриншоты.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Для этого мы написали простой Interaction и абстракции в стиле Espresso.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Выглядит это примерно так. В роботе появляется функциональность по сравнению скриншотов. Что произошло в отчете?
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
В отчет в случае ошибки мы добавляем то, что ожидали увидеть, то, что получили, и diff.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Теперь мы проверяем не то, что конкретная строка совпадает, а общее расположение элементов, их композицию на экране.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Из интересного можно отметить, как в принципе стоит сравнивать скриншоты, ведь мы делаем скриншоты-эталоны на одном эмуляторе, а сравниваем неизвестно где, на CI. Следует уделить влияние конфигурации ваших эмуляторов, именно тех, на которых вы делаете скриншоты, чтобы они совпадали с теми, на которых вы эти тесты прогоняете.

На что следует обратить внимание? Конечно, на форм-фактор, то есть на размер вашего экрана и режим отрисовки. В нашем случае нам необходимо было выключить GPU и скрыть кнопки. Если этого не сделать, у вас могут появляться различные артефакты из-за разницы реализации anti-aliasing в той или иной среде.

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

Улучшаем тестовых роботов

Я говорил, что у нас были претензии к роботам, некоторый Boilerplate, и мы пришли к осознанию того, что это тоже хотелось бы поправить.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Посмотрим на проблему. Предположим, появляется новая функциональность. Например, мы захотели проверять, что элементы — кнопка или поле ввода — видны. Тогда в соответствии с подходом робота нам надо эту функциональность вывести в робот.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Потом на экране неожиданно появилась еще одна кнопка, и для нее мы тоже должны продублировать функциональность по нажатию и проверке видимости.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Или вдруг появился еще один робот, и возникают еще какие-то кнопки, поля ввода. Здесь мы тоже дублируем функциональность, которая по сути имеет одну и ту же реализацию. Изменяется лишь Matcher, который мы используем.

Здесь, вдохновившись подходом из библиотеки Kakao, мы пришли к так называемому компонентному подходу.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Идея компонентного подхода: мы выделяем функциональность не в робота, а в компонент. Роботы заменяются так называемыми Screens. Каждый Screen представляет какую-то часть UI по аналогии с роботом, но хранит набор компонент, которые есть в этой части UI.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Базовый Screen предоставляет базовую функциональность.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Базовые компоненты тоже представляют какую-то функциональность, а конкретный компонент, например EditText, предоставляет функциональность по вводу текста.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
А в тесте получилось следующее.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Контекст остался: мы по-прежнему можем понимать, где производим действия. Единственное, чуть понизился уровень абстракции. Теперь мы вынуждены взаимодействовать с компонентами и вызывать на них действия.

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

Время исполнения тестов

Наверное, вы заметили, что многое из того, что мы делали, негативно влияло на скорость выполнения. Что же делать?
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Количество тестов растет, мы используем Orchestrator, waitUntil, Allure и так далее, замедление усиливается. Рассмотрим несколько вариантов. Можно делать частичное исполнение тестов. Например, мы не всегда прогоняем все тесты на каждый пул-реквест, на каждый коммит, а начинаем прогонять их порциями.

Либо мы реализовываем параллельное исполнение тестов с достижением необходимого, желаемого времени исполнения. Мы решили покопать в сторону этого варианта, посмотреть, что мы можем сделать с точки зрения параллельного исполнения.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Существует средство шардирования исполнения тестов через AndroidJUnitRunner. Однако оно доступно только из командной строки. Нам этот вариант тогда показался не очень удобным. Мы еще немного покопали в сторону сторонних фреймворков — например, Spoon. Но втягивание сторонних фреймворков нам казалось избыточным.

Есть Gradle-команда — выполняешь и получаешь список подключенных устройств. Разделяешь тесты по этим устройствам, исполняешь их на этих устройствах. Собираешь результат. Хотелось добиться чего-то такого, казалось бы, простого. Зачем здесь отдельный фреймворк?
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Решили попробовать сделать нечто подобное. Для этого мы написали свой Gradle-таск, в основе которого так называемый Worker API Gradle. Его идея в том, что существуют специальные абстракции: WorkerExecutor, который позволяет параллельно исполнять некоторые подтаски в рамках одной корневой таски, и действие WorkAction. Конфигурировать их можно с помощью WorkParameters.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Также мы проанализировали базовую команду для запуска Android-тестов — connectedAndroidTest, и выясняли, какие в принципе существуют абстракции, чтобы сделать нечто похожее. Обнаружили, что существует некий AndroidTestOrchestratorRemoteAndroidTestRunner, позволяющий из кода запускать тесты. У него есть дополнительный ITestRunListener и набор средств конфигурации этого Runner. Однако часть API, которые мы тогда нашли, начиная с Gradle 4.0 стали Deprecated, поэтому нам пришлось скопировать некоторые компоненты в проект.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Текущая конфигурация исполнения наших тестов выглядит примерно так. Мы запускаем два агента. На каждом запускаем по восемь эмуляторов — здесь их для краткости три.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Как можно заметить, на 16 эмуляторах тесты исполняются примерно за 30 минут, а последовательное их исполнение занимает порядка 5 часов. Но это не совсем честное время. Столько мы потратим только на исполненея тестов, без накладных расходов с точки зрения инфраструктуры.

Чего мы добились, проделав все это?
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Если посмотреть на этот график, то за год с момента интеграции UI-тестов количество наших релизов значительно выросло. Количество HotFix снижается. То есть тесты помогают нам писать более стабильный код, вносить масштабные исправления в проект без особой оглядки.

Сейчас у нас, по утверждению команды QA, автоматизирован 71% регрессионного тестирования, и сформирован план по дальнейшей автоматизации. На релизе мы проводим выборочное тестирование, которое занимает порядка двух-трех часов. Релизы случаются примерно раз в одну-две недели, как можно заметить по этому графику за последние три месяца.
на каком шаге произошла ошибка. Смотреть фото на каком шаге произошла ошибка. Смотреть картинку на каком шаге произошла ошибка. Картинка про на каком шаге произошла ошибка. Фото на каком шаге произошла ошибка
Что с проблемами, которые были обозначены в самом начале?

Для изолированности наших тестов мы используем такие средства, как Android Test Orchestrator, MockWebServer, Espresso Intens. Dagger 2 помогает подменять зависимости в тестовой среде. Для стабильности у нас есть IdlingResource, есть костыль waitUntil. Так как мы используем кастомный Gradle-таск, есть возможность перезапускать тесты, которые по непредсказуемым причинам падают. Что касается времени исполнения тестов, нам удалось реализовать параллельное исполнение средствами кастомной Gradle-таски.

У нас сейчас написано примерно полторы тысячи тестов. Они исполняются примерно за один час с учетом всех накладных расходов. Гоняем мы их на каждый коммит, на каждый пул-реквест.

В написании тестов используются подходы с тестовым роботом. Выделили Matcher и Lookup, используем компонентный подход, сравнение скриншотов для упрощения написания. Отчеты строятся в Allure, который предоставляет нам некоторую автодокументируемость. Это позволяет команде тестирования анализировать тесты, которые мы реализовали, и фиксировать, какую часть регресса мы покрыли.

Что же дальше? У нас есть планы по расширению использования IdlingResource. Сейчас мы их, к сожалению, используем только для OkHttpClient. Также есть планы по улучшению инфраструктуры параллельного исполнения тестов. Сейчас возникают очень большие накладные расходы: как я сказал, тесты исполняются за 30 минут, а все остальное время занимают процессы передачи управления между подзадачами. И, разумеется, можно предпринимать еще какие-то шаги по улучшению нашего отчета.

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *