Привет, Хабр!
Меня зовут Николай Абалов. Я работаю в лондонском офисе Badoo в команде Mobile QA Automation. Мой коллега Раждип Варма рассказал о том, как сделать Appium-тесты быстрее и надёжнее. Ниже перевод его статьи.
В последние годы Appium стал одним из самых популярных инструментов автоматизации в мобильной разработке. Однако наряду с преимуществами у него есть некоторые ограничения, в частности связанные с тем, что тестовый код полностью отделяется от кода приложения. В этой статье Раждип рассказывает, как вы можете наделить свой код суперсилой и решить самые неприятные проблемы автоматизации end-to-end-тестов для Android.
Проблема №1: капризное приложение
Представьте, что при прогоне теста у вас в приложении возникает какое-то всплывающее сообщение (как на картинке слева). Что, если логика, управляющая отображением сообщения, неподвластна тестировщику? Как в этом случае обеспечить надёжность тестов? Или, скажем, вы хотите кликнуть по двигающемуся баннеру в вашем приложении, но тест проваливается, потому что позиция баннера изменилась к тому моменту, как Appium смог кликнуть!
Есть множество примеров непредсказуемости поведения приложения в ходе тестирования, приводящей к сбоям тестов.
А представьте, что ваши тесты могут сказать приложению: «Я сейчас тебя тестирую, так что отключи, пожалуйста, всплывающее сообщение с просьбой поставить приложению оценку».
Проблема №2: Appium поддерживает не всё
В нашем приложении есть фича, позволяющая, встряхнув устройство, отменить свой отказ от оценки.
Как автоматизировать с помощью Appium тряску на Android-устройстве? Есть конечная точка Shake ((POST /session/:session_id/appium/device/shake), но она не реализована для драйвера UIAutomator2.
А представьте, что ваши тесты могут сказать приложению: «Притворись, что устройство встряхнули».
Решение: позвольте приложению помочь вашим тестам, реализовав бэкдоры
Бэкдоры — способ вызова методов, определённых в коде Android-приложения, из кодовой базы автоматизации, т. е. из ваших тестов.
Давайте рассмотрим это на простом примере. Допустим, у вас есть метод
foo()
в Activity, скажем, в классе
LoginActivity
:В методе
foo()
мы можем написать код, отключающий случайные всплывающие сообщения, ИЛИ добавить фальшивую функциональность встряхивания ИЛИ что-то, задающее состояние вашего приложения. Представьте, что мы можем вызывать этот метод из кодовой базы автоматизации с помощью чего-то подобного:Это решит нашу задачу «А представьте, что ваши тесты могут сказать приложению...»
Плохая новость: бэкдоры не поддерживаются в Appium «из коробки».
Хорошая новость: бэкдоры всё-таки можно реализовать в Appium!
Прежде чем переходить к решению, приведу несколько реальных примеров использования бэкдоров в автоматизации:
- изменение URL бекенда;
- изменение географического региона приложения;
- выбор конкретных вариантов клиентских A/B-тестов;
- симуляция наличия SIM-карты для приложения;
- получение ID текущей сессии;
- получение от приложения данных аналитики.
P. S. Делать всё это самостоятельно может быть сложно. Если вас не интересует, как я это сделал, то можете сразу переходить к главе «Быстрое начало работы с бэкдорами в вашем проекте».
Реализация бэкдоров в Appium
Архитектура «из коробки» сервера UIAutomator2
- APK-1 => appium-uiautomator2-server-debug-androidTest.apk
- APK-2 => appium-uiautomator2-server.apk
APK-1 — это пакет инструментации (instrumentation package) для APK-2. В нём есть тест — instrumentation test. Его запускает команда для драйвера «adb shell am instrument…» Единственная цель такого теста — запустить HTTP-сервер, определённый в APK-2.
Оба APK-файла выполняются в одном процессе, выделенном на иллюстрации зелёным цветом.
Когда сервер поднят и работает, клиент может отправлять JSON-HTTP-запросы, а сервер будет их исполнять.
Но мы можем выжать максимум из Android-инструментации, если изменим архитектуру (как показано ниже).
Модифицированная архитектура сервера UIAutomator2:
Оснащение тестируемого приложения APK Appium
Мы влили код APK-2 в APK-1. Следовательно, инструментальный тест и HTTP-сервер теперь находятся в одном APK-файле. Назовём его Merged Server APK.
Теперь можно изменить Manifest.xml нашего нового APK-файла таким образом, чтобы его целевым инструментируемым пакетом стал пакет с названием нашего тестируемого приложения:
Это легко реализовать с помощью Ruby Gem под названием appium_instrumenter, в основе которого лежит код из calabash-android gem.
Затем подписываем Merged Server APK с помощью того хранилища ключей (keystore), что и тестируемое приложение. Теперь у нас есть кастомный инструментальный сервер, исполняющийся в том же процессе, что и тестируемое приложение (выделен зелёным цветом).
Мы инструментировали наше приложение с помощью Merged Server APK, а значит, последний может получить доступ к контексту приложения. Это означает, что мы можем попросить файл Merged Server APK вызвать методы, определённые в нашем приложении.
Создадим в Merged Server APK конечную точку, чтобы указывать, какой метод приложения нужно вызвать:
/wd/hub/session/:sessionId/backdoor.
В эту конечную точку мы можем отправить имя метода, который хотим вызвать. Merged Server APK получает контекст приложения и вызывает метод с помощью API Java Reflection.
Весь код этого объединённого с конечной точкой для бэкдоров Appium UIAutomator2 Server вы можете скачать из репозитория, из ветки ‘single_apk’.
Далее заменяем APK-файл, поставляемый с пакетом appium_uiautomator2_driver, на наш кастомный APK-файл. Всё самое сложное позади, эти манипуляции нужно проделать только один раз.
Теперь в нашем коде автоматизации тестирования есть вспомогательный метод для вызова бэкдор-методов. Вот пример на Ruby:
Вуаля! Мы получили суперсилу! Теперь можно очень просто вызвать любой публичный метод, определённый в классе Application или текущем классе Activity:
При этом поддерживается получение тестовым кодом возвращаемых значений!
Быстрое начало работы с бэкдорами в вашем проекте
1. Генерируем APK-файл Appium UIAutomator Server для вашего
приложения:
установите
appium_instrumenter gem
. Даже если ваши Appium-тесты написаны на Java, вам придётся один раз сделать это, поскольку я написал утилиту только на Ruby.gem install appium_instrumenter
appium_instrumenter instrument app-debug.apk
В вашей текущей директории создастся папка
./test_servers
:test_servers
├── appium-uiautomator2-server-debug-androidTest.apk
└── appium-uiautomator2-server-v0.3.0.apk
Установите на устройство оба вышеупомянутых APK-файла:
adb install test_servers/appium-uiautomator2-server-debug-androidTest.apk
adb install test_servers/appium-uiautomator2-server-v0.3.0.apk
2. Теперь установите NPM-пакет appium_uiautomator2_driver
из моего форка:
npm install “rajdeepv/appium-uiautomator2-driver#adb_host”
3. Определите метод backdoor()
:
определите метод, отправляющий данные в конечную точку для бэкдора:
http://localhost:#{APPIUM_FORWARDED_PORT}/wd/hub/session/:sessionId/backdoor
Рассмотрите приведённый выше пример реализации этого метода на Ruby. Вы можете реализовать такой же метод, если пишете на Java или любом другом языке.
4. Да прибудет с вами суперсила!
Держитесь от проблем подальше
«С большой силой приходит и большая ответственность».
Бэкдор — очень мощный инструмент. И при неправильном использовании он может привести к неприятным последствиям. Так, если мы воспользуемся бэкдором для полного изменения логики приложения, то риски тестирования будут выше, чем его ценность.
Бэкдоры позволяют реализовать ранее невозможные сценарии, они расширяют возможности тестирования. И в некоторых случаях без них просто не обойтись!
Вот такая история. К слову, 1 апреля Раждип выступил на CodeFest в Новосибирске. Видеозапись его доклада должна появиться летом на YouTube-канале CodeFest и на Badoo Tech.