Компания, в которой я работаю, разрабатывает ПО на заказ, в том числе мобильные приложения на базе Android и iOS. В связи с тем, что конкуренция в этом сегменте рынка довольно высока, тестировщики не только отвечают за соответствие конечного продукта спецификациям и ожиданиям клиента, но еще и поставлены в жесткие рамки по бюджету и срокам тестирования. Это побуждает нас исследовать новые инструменты и методы, которые позволили бы нам уменьшать затраты на тестирование и повышать качество продуктов.
Imagrium — это результат одного из таких исследований. Технически это Jython фреймворк для кросс-платформенного тестирования мобильных Android/iOS приложений с помощью распознавания изображений, написанный нашей компанией. Он представлен в виде рабочего PyDev проекта, который вы можете изменить под свои нужды. Код распространяется под MIT лицензией и доступен на Github. В этой статье я расскажу о принципах работы фреймворка и его устройстве.
Фреймворку уже около 2-х лет, в течение этого времени он рос и развивался, вбирая в себя опыт применения на боевых проектах. При этом основные принципы, пожалуй, не сильно изменились. Они таковы:
Использовать одни и те же тесты на разных платформах
Обычно наши клиенты заказывают приложение сразу под несколько платформ, чаще всего Android и iOS. Получается, что спецификация одна, функциональные тесты одинаковые, так что наиболее эффективным с нашей точки зрения было бы иметь одну тестовую базу для разных платформ. Другими словами, один и тот же тест должен проходить под разными платформами.
Отделять ресурсы от логики
Инструменты, которые позволяют кросс-платформенное тестирование, можно разделить на два типа:
Мы большие фанаты первого подхода, но на тот момент некоторых инструментов еще не было, остальные работали не очень стабильно, это и вынудило нас попробовать свои силы в написании альтернативы. При этом мы не хотели очередной record-and-replay инструмент, потому что тесты, созданные подобными инструментами, очень быстро становится очень сложно содержать. Почему? Потому что в них ресурсы связаны с логикой работы. Напр��мер, при изменении какой-нибудь примитивной операции придется не только переправлять картинки во всех текстах, но еще и менять в нужных тестах нужные шаги. Хотелось избежать подобной бессмысленной и затратной работы, используя популярный паттерн PageObject.
Поддерживать непрерывную интеграцию и удобную отладку
Все наши проекты автоматически собираются Jenkins, так что с первой же строчки кода этого фреймворка нам хотелось бы, чтобы и тесты под приложение тоже можно было запускать автоматически. Изначально это было не так просто, потому что для работы с изображениями решили использовать Sikuli (как наиболее популярное, документированное и бесплатное решение), у которой на тот момент не было простой возможности отделить библиотеку от IDE и была поддержка только Jython 2.5 (у которого, между прочим, не было еще поддержки json, да-да), в unittest не было кучи вкусных возможностей (например, авто-нахождения тестов). Однако со временем эти трудности были побеждены, и теперь результаты тестов доступны в jUnit формате, а Ant делает из них красивую страничку со статистикой.
По второму пункту, если вы видели Sikuli IDE, то вы понимаете, что делать в ней что-то чуть более серьезное, чем 10-ти строчное приложение — это боль. Хотя бы потому, что там нет отладчика. Для нас этого было достаточно, чтобы перейти на использование PyDev Eclipse, который привычен программистам и содержит массу вспомогательных возможностей для ускорения разрабоки.
Гарантировать одинаковое начальное состояние системы для всех тестов
Мы позаимствовали идею независимости те��тов друг от друга из jUnit т.к. это дает возможность запускать тесты параллельно или совершать выборочные проверки. Еще одна задача, которая нас к этому подтолкнула — это падение тестов в середине выполнения. Нам нужна была система, в которой падение одного теста никак не отражалось бы на выполнении других тестов. В результате решили использовать snapshots (снимки) эмулятора на Android и функцию reset симулятора на iOS.
Загрузка и ответы эмулятора не должны критически задерживать выполнение тестов
Нам очень нравилась скорость iOS симулятора, чего нельзя было сказать про коробочный Android эмулятор. У нас были надежды на HAXM и x86 образы, поставляемые Intel, но беда в том, что эти образы до версии 4.4 не содержали Google API, который используется в большинстве наших приложений (т.е. приложения просто не ставились на эти образы). В свою очередь образ 4.4, который содержит это API, работал у нас нестабильно (например, мог упасть при повторной установке приложения). Поэтому мы выбрали Genymotion и VirtualBox для создания снимков и управления ими.
Если вы разделяете эти принципы и думаете о каком-то подобном фреймворке для тестирования, предлагаем вам рассмотреть наш как одну из альтернатив.
Imagrium успешно работает на Java 7 x64 и Windows 7 x64 (ветка win репозитория) или MacOS 10.9 (ветка ios репозитория).
Исторически так сложилось, что под Windows тестировался только Android, а под MacOS, соответственно, только iOS.
Перед тем, как мы кратко пройдемся по правилам написания тестов на Imagrium, хотим показать видео с возможностями фреймворка (выноски на английском). На этом видео показано прохождение теста для приложения HopHop на iOS и Android.
Код, написанный на Imagrium, можно разделить на два блока: страницы и тесты. В этой группировке тест — это последовательность операций, проводимых на разных страницах, а также переходы между страницами. Например:
Как можно догадаться из кода, сначала тест загружает страницу AuthPage, потом переходит с нее на fbAuthPage, заполняет необходимые данные и отправляет форму, подтверждает пользователя и передит на lobbyPage. Другими словами, тест ходит по страницам и выполняет понятные с т.з. тестировщика операции, оставляя реализацию операций внутри страниц. Т.е. тесты — это достаточно простая группа, и чтобы научиться их писать, мы должы всего лишь научиться писать страницы. Это тоже достаточно просто.
Страница — это Jython представление экрана/activity/страницы приложения. Технически это класс с полями и методами, которые управляют этими полями. В самом сложном случае он выглядит так:
В этом фрагменте используется большинство вкусностей Imagrium, так что давайте детально обсудим этот фрагмент с т.з. возможностей фреймворка.
Начнем с этой строки:
Этот фрагмент связывает поле страницы email с графическим ресурсом (картинкой или строкой). В данном случае мы связываем поле сразу с двумя ресурсами (для английской и русской локали). Когда страница спрашивает поле email, система пытается найти одно из этих изображений и возвращает область, связанную с первым успешно найденным изображением.
По нашим договоренностям мы храним графические ресурсы в директории res. Чтобы задать ресурс, мы должны передать в ResourceLoader путь до ресурса или текст, например:
но для гибкости и удобства содержания кода мы храним пути до ресурсов в переменных объекта src.core.r.Resource.
Итого: Чтобы управлять графическим элементом или строкой, нужно добавить в соответствующую страницу объявление ResourceLoader.
Объявление поля только задает необходимую нам связь, чтобы использовать ее, нам нужно обратиться к полю. Такие обращения происходят либо при использовании какой-нибудь операции на поле (например, перетаскивание или щелчок), либо при инициализации страницы. Последняя операция очень важна, потому что во-первых она позволяет задать область поиска для полей (обычно мы хотим сузить ее до границ эмулятора) и проверить, что мы именно на той странице, на которой хотим. Поэтому давайте рассмотрим подробнее, что происходит в коде инициализации страницы на примере FbAuthPage.
Во-первых, мы всегда должны наследовать страницу от класса src.core.page.Page
так как это дает нам доступ к общим методам управления страницами, например, метод ожидания полной загрузки страницы. Также мы должны запустить родительский конструктор при инициализации страницы.
и последняя особенность инициализации — возможность задать для поля область поиска, обычно это область, занятая эмулятором, и чтобы искать только в ней, мы пишем
Параметр box изначально считается после создания снимка эмулятора и перед запуском приложения а первый раз, а дальше передается от страницы к странице. Более детально, берутся две линии (вертикальная и горизонтальная на эмуляторе) из, например, res/pages/android/hdpi/core, система обнаруживает их на странице и строит по ним область эмулятора. Если мы ничего не присваиваем полям, система ищет эх по всему экрану (что обычно сказывается на качестве поиска), хотя это бывает нужно для какой-нибудь экзотики, например, чтобы нажать какую-нибудь hardware кнопку на эмуляторе.
Обычно при инициализации страницы мы проверяем, что на ней находятся нужные нам поля, и делаем это таким методом:
Некоторые поля могут быть невидимы при инициализации, указывайте только видимые. Если страница не может найти поля, она посылает исключение AssertionError которое приводит к провалу теста а также сообщает детали в stdout.
Итого: Если вы хотите, чтобы система искала поля в рамках эмулятора, присваивайте им self.box. Используйте проверку полей страницы с помощью self.checkIfLoaded().
В нашем примере кода FbAuthPage мы использовали метод click() на поле email:
Каждое поле на самом деле представлено объектом Match из Sikuli, так что с ним можно делать все, что написано в соответствующей спеке (таскать, щелкать, зажимать, отпускать, вводить текст, и прочее).
Конфигурация является очень важной частью запуска тестов. В частности она определяет, под какую платформу мы хотим запустить тесты, какие тесты, для какого приложения. Также в конфигурацию можно прописывать свои переменные и использовать их в тестах или в коде страниц.
В нашем примере класса FbAuthPage вы можете увидеть строку:
Здесь аттрибут settings это образец ConfigParser связанный с текущим конфигурационным файлом, так что вы можете использовать все методы из официальной спеки при работе с ним. Imagrium добавляет settings к каждому тесту, так что вы можете использовать конфигурацию и непосредственно в тестах.
Пример работы с конфигурацией:
Например, временами требуется нажать кнопку back (специфично для Android) или ввести текст (у Sikuli это не получается сделать просто, потому что она вводит текст асинхронно, что приводит обычно к тому, что часть символов не успевает дописаться). Для предоставления этих возможностей и были введены классы, которые дают вашей странице ОС-специфичный функционал.
На практике это выглядит так (��ножественное наследование!):
или так:
Итого: Если хотим ОС-специфичные функции, наследуемся от нужного класса.
В предыдущих разделах мы говорили сначала о FbAuthPage, а потом перескачили на FbAuthPageiOS и FbAuthPageAndroidHdpi. В этой секции мы обсудим, что это за классы, зачем они нужны, и как связаны с FbAuthPage.
Изначально мы говорили, что хотим запускать один и тот же тест на разных платформах, но даже в рамках одной платформы у нас могут вознинуть серьезные отличия в представлении графических ресурсов. Например, ресурсы под hdpi могут отличаться от xhdpi, ресурсы под iOS могут отличаться от тех же под Android. При этом отличаются только ресурсы, а методы работы с ними остаются теми же (или почти теми же, с поправкой на guidelines). Нужно было придумать какое-то решение для переопределения ресурсов под разные платформы, и мы использовали стандартное наследование. Другими словами, наши страницы можно разделить на два уровня — общие и платформозависимые.
При таком разнообразии страниц, не хотелось бы, чтобы тест или страница сама решала, какие страницы им загрузить. В Imagrium это обязанность системы — прочитать конфигурацию и загружать нужные страницы, когда страница вызывает метод load(). Чтобы система загружала правильные классы, эти классы должны быть по-определенному названы. Более подробно:
Если все еще класс не найден, то система отдает AssertionError с соответствующим описанием, что проваливает тест.
На практике это выглядит так:
В тесте пишем
В общей странице AuthPage есть метод:
Данный метод вызывает метод load(), при выполнении которого система решает, какую бы из страниц вызвать(например, FbAuthPageiOS или FbAuthPageAndroidHdpi).
Итого: Мы реализуем общую логику работы страницы, а потом, если того требует другая плотность/платформа, добавляем измененные ресурсы в соотв��тствующие классы страниц. Система по конфигурации решает, какую страницу вызвать в подходящем случае.
Предполагается, что вы клонируете проект с Github, откроете в Eclipse PyDev, удалите все лишнее, добавите все нужное, а потом захотите это протестировать. Для тестирования вам необходимо установить все, что нужно для работы фреймворка (смотрите инструкции по установке), а потом из корня проекта запустите:
Чуть более подробно, этот вызов запускает run.py с определенным конфигурационным файлом в качестве единственного входного параметра (например, conf/android_settings.conf) а также с прописыванием необходимых путей в переменные PATH и CLASSPATH, а потом создает страницу с результататми тестирования.
В общем случае при тестировании запускается эмулятор, на нем переустанавливается приложение, после чего создается снимок эмулятора. Далее на каждый тест запускается один и тот же снимок, на котором и проходит очередной тест.
Мы рекомендуем использовать Imagrium в тех случаях, когда вам нужно тестировать сразу несколько платформ или когда у вас нет простого доступа к коду приложения, чтобы добавить в нем локаторы для GUI элементов. Наиболее быстро освоить фреймворк получится у тех, кто программировал на Python, хотя простота синтаксиса языка должна способствовать быстрому обучению работы с инструментом. В этой статье были приведены только основы работы с Imagrium, подробно о конфигурации фреймворка и его возможностях (например, многопользовательское выполнение сценариев) я расскажу в последующих статьях, если эта статья будет интересна сообществу. Также можете прочитать официальную документацию на Github странице проекта и посмотреть этот Hello, World пример.
Imagrium — это результат одного из таких исследований. Технически это Jython фреймворк для кросс-платформенного тестирования мобильных Android/iOS приложений с помощью распознавания изображений, написанный нашей компанией. Он представлен в виде рабочего PyDev проекта, который вы можете изменить под свои нужды. Код распространяется под MIT лицензией и доступен на Github. В этой статье я расскажу о принципах работы фреймворка и его устройстве.
Принципы работы
Фреймворку уже около 2-х лет, в течение этого времени он рос и развивался, вбирая в себя опыт применения на боевых проектах. При этом основные принципы, пожалуй, не сильно изменились. Они таковы:
Использовать одни и те же тесты на разных платформах
Обычно наши клиенты заказывают приложение сразу под несколько платформ, чаще всего Android и iOS. Получается, что спецификация одна, функциональные тесты одинаковые, так что наиболее эффективным с нашей точки зрения было бы иметь одну тестовую базу для разных платформ. Другими словами, один и тот же тест должен проходить под разными платформами.
Отделять ресурсы от логики
Инструменты, которые позволяют кросс-платформенное тестирование, можно разделить на два типа:
- Предоставлющие общее API и сервис-посредник, который транслирует общие вызовы в специфичные для оси (MonkeyTalk, Appium, Robotium).
- Использующие метод распознавания образов (Borland Silk Mobile, eggPlant).
Мы большие фанаты первого подхода, но на тот момент некоторых инструментов еще не было, остальные работали не очень стабильно, это и вынудило нас попробовать свои силы в написании альтернативы. При этом мы не хотели очередной record-and-replay инструмент, потому что тесты, созданные подобными инструментами, очень быстро становится очень сложно содержать. Почему? Потому что в них ресурсы связаны с логикой работы. Напр��мер, при изменении какой-нибудь примитивной операции придется не только переправлять картинки во всех текстах, но еще и менять в нужных тестах нужные шаги. Хотелось избежать подобной бессмысленной и затратной работы, используя популярный паттерн PageObject.
Поддерживать непрерывную интеграцию и удобную отладку
Все наши проекты автоматически собираются Jenkins, так что с первой же строчки кода этого фреймворка нам хотелось бы, чтобы и тесты под приложение тоже можно было запускать автоматически. Изначально это было не так просто, потому что для работы с изображениями решили использовать Sikuli (как наиболее популярное, документированное и бесплатное решение), у которой на тот момент не было простой возможности отделить библиотеку от IDE и была поддержка только Jython 2.5 (у которого, между прочим, не было еще поддержки json, да-да), в unittest не было кучи вкусных возможностей (например, авто-нахождения тестов). Однако со временем эти трудности были побеждены, и теперь результаты тестов доступны в jUnit формате, а Ant делает из них красивую страничку со статистикой.
По второму пункту, если вы видели Sikuli IDE, то вы понимаете, что делать в ней что-то чуть более серьезное, чем 10-ти строчное приложение — это боль. Хотя бы потому, что там нет отладчика. Для нас этого было достаточно, чтобы перейти на использование PyDev Eclipse, который привычен программистам и содержит массу вспомогательных возможностей для ускорения разрабоки.
Гарантировать одинаковое начальное состояние системы для всех тестов
Мы позаимствовали идею независимости те��тов друг от друга из jUnit т.к. это дает возможность запускать тесты параллельно или совершать выборочные проверки. Еще одна задача, которая нас к этому подтолкнула — это падение тестов в середине выполнения. Нам нужна была система, в которой падение одного теста никак не отражалось бы на выполнении других тестов. В результате решили использовать snapshots (снимки) эмулятора на Android и функцию reset симулятора на iOS.
Загрузка и ответы эмулятора не должны критически задерживать выполнение тестов
Нам очень нравилась скорость iOS симулятора, чего нельзя было сказать про коробочный Android эмулятор. У нас были надежды на HAXM и x86 образы, поставляемые Intel, но беда в том, что эти образы до версии 4.4 не содержали Google API, который используется в большинстве наших приложений (т.е. приложения просто не ставились на эти образы). В свою очередь образ 4.4, который содержит это API, работал у нас нестабильно (например, мог упасть при повторной установке приложения). Поэтому мы выбрали Genymotion и VirtualBox для создания снимков и управления ими.
Если вы разделяете эти принципы и думаете о каком-то подобном фреймворке для тестирования, предлагаем вам рассмотреть наш как одну из альтернатив.
Требования к окружению
Imagrium успешно работает на Java 7 x64 и Windows 7 x64 (ветка win репозитория) или MacOS 10.9 (ветка ios репозитория).
Исторически так сложилось, что под Windows тестировался только Android, а под MacOS, соответственно, только iOS.
Демонстрация работы
Перед тем, как мы кратко пройдемся по правилам написания тестов на Imagrium, хотим показать видео с возможностями фреймворка (выноски на английском). На этом видео показано прохождение теста для приложения HopHop на iOS и Android.
Как писать тесты
Код, написанный на Imagrium, можно разделить на два блока: страницы и тесты. В этой группировке тест — это последовательность операций, проводимых на разных страницах, а также переходы между страницами. Например:
authPage = AuthPage.load(AppLauncher.box, self.settings)
fbAuthPage = authPage.signUpFb()
fbAuthPage.fillEmail(self.settings.get("Facebook", "email"))
fbAuthPage.fillPassword(self.settings.get("Facebook", "password"))
fbConfirmPage = fbAuthPage.login()
lobbyPage = fbConfirmPage.confirm()
Как можно догадаться из кода, сначала тест загружает страницу AuthPage, потом переходит с нее на fbAuthPage, заполняет необходимые данные и отправляет форму, подтверждает пользователя и передит на lobbyPage. Другими словами, тест ходит по страницам и выполняет понятные с т.з. тестировщика операции, оставляя реализацию операций внутри страниц. Т.е. тесты — это достаточно простая группа, и чтобы научиться их писать, мы должы всего лишь научиться писать страницы. Это тоже достаточно просто.
Пишем страницы
Страница — это Jython представление экрана/activity/страницы приложения. Технически это класс с полями и методами, которые управляют этими полями. В самом сложном случае он выглядит так:
class FbAuthPage(Page):
email = ResourceLoader([Resource.fbEmailFieldiOS, Resource.fbEmailFieldiOS_ru])
password = ResourceLoader([Resource.fbPasswordFieldiOS, Resource.fbPasswordFieldiOS_ru])
actionLogin = ResourceLoader([Resource.fbLoginBtniOS, Resource.fbLoginBtniOS_ru])
def __init__(self, box, settings):
super(FbAuthPage, self).__init__(box, settings)
self.email = self.box
self.password = self.box
self.actionLogin = self.box
self.settings = settings
self.waitPageLoad()
self.checkIfLoaded(['email', 'password'])
def fillEmail(self, text):
self.email.click()
self.waitPageLoad()
self.inputText(text)
В этом фрагменте используется большинство вкусностей Imagrium, так что давайте детально обсудим этот фрагмент с т.з. возможностей фреймворка.
Определение полей и локализация
Начнем с этой строки:
email = ResourceLoader([Resource.fbEmailFieldiOS, Resource.fbEmailFieldiOS_ru])
Этот фрагмент связывает поле страницы email с графическим ресурсом (картинкой или строкой). В данном случае мы связываем поле сразу с двумя ресурсами (для английской и русской локали). Когда страница спрашивает поле email, система пытается найти одно из этих изображений и возвращает область, связанную с первым успешно найденным изображением.
По нашим договоренностям мы храним графические ресурсы в директории res. Чтобы задать ресурс, мы должны передать в ResourceLoader путь до ресурса или текст, например:
email = ResourceLoader("res/pages/ios/fb_auth/fbEmailFieldiOS.png", "Password")
но для гибкости и удобства содержания кода мы храним пути до ресурсов в переменных объекта src.core.r.Resource.
Итого: Чтобы управлять графическим элементом или строкой, нужно добавить в соответствующую страницу объявление ResourceLoader.
Инициализация страницы и проверка полей
Объявление поля только задает необходимую нам связь, чтобы использовать ее, нам нужно обратиться к полю. Такие обращения происходят либо при использовании какой-нибудь операции на поле (например, перетаскивание или щелчок), либо при инициализации страницы. Последняя операция очень важна, потому что во-первых она позволяет задать область поиска для полей (обычно мы хотим сузить ее до границ эмулятора) и проверить, что мы именно на той странице, на которой хотим. Поэтому давайте рассмотрим подробнее, что происходит в коде инициализации страницы на примере FbAuthPage.
Во-первых, мы всегда должны наследовать страницу от класса src.core.page.Page
class FbAuthPage(Page):
так как это дает нам доступ к общим методам управления страницами, например, метод ожидания полной загрузки страницы. Также мы должны запустить родительский конструктор при инициализации страницы.
def __init__(self, box, settings):
super(FbAuthPage, self).__init__(box, settings)
и последняя особенность инициализации — возможность задать для поля область поиска, обычно это область, занятая эмулятором, и чтобы искать только в ней, мы пишем
self.email = self.box
self.password = self.box
Параметр box изначально считается после создания снимка эмулятора и перед запуском приложения а первый раз, а дальше передается от страницы к странице. Более детально, берутся две линии (вертикальная и горизонтальная на эмуляторе) из, например, res/pages/android/hdpi/core, система обнаруживает их на странице и строит по ним область эмулятора. Если мы ничего не присваиваем полям, система ищет эх по всему экрану (что обычно сказывается на качестве поиска), хотя это бывает нужно для какой-нибудь экзотики, например, чтобы нажать какую-нибудь hardware кнопку на эмуляторе.
Обычно при инициализации страницы мы проверяем, что на ней находятся нужные нам поля, и делаем это таким методом:
self.checkIfLoaded(['email', 'password'])
Некоторые поля могут быть невидимы при инициализации, указывайте только видимые. Если страница не может найти поля, она посылает исключение AssertionError которое приводит к провалу теста а также сообщает детали в stdout.
Итого: Если вы хотите, чтобы система искала поля в рамках эмулятора, присваивайте им self.box. Используйте проверку полей страницы с помощью self.checkIfLoaded().
Что можно делать с полями
В нашем примере кода FbAuthPage мы использовали метод click() на поле email:
self.email.click()
Каждое поле на самом деле представлено объектом Match из Sikuli, так что с ним можно делать все, что написано в соответствующей спеке (таскать, щелкать, зажимать, отпускать, вводить текст, и прочее).
Доступ к конфигурации
Конфигурация является очень важной частью запуска тестов. В частности она определяет, под какую платформу мы хотим запустить тесты, какие тесты, для какого приложения. Также в конфигурацию можно прописывать свои переменные и использовать их в тестах или в коде страниц.
В нашем примере класса FbAuthPage вы можете увидеть строку:
self.settings = settings
Здесь аттрибут settings это образец ConfigParser связанный с текущим конфигурационным файлом, так что вы можете использовать все методы из официальной спеки при работе с ним. Imagrium добавляет settings к каждому тесту, так что вы можете использовать конфигурацию и непосредственно в тестах.
Пример работы с конфигурацией:
self.settings.get("Facebook", "email")
ОС-зависимые методы
Например, временами требуется нажать кнопку back (специфично для Android) или ввести текст (у Sikuli это не получается сделать просто, потому что она вводит текст асинхронно, что приводит обычно к тому, что часть символов не успевает дописаться). Для предоставления этих возможностей и были введены классы, которые дают вашей странице ОС-специфичный функционал.
На практике это выглядит так (��ножественное наследование!):
class FbAuthPageiOS(FbAuthPage, iOSPage):
или так:
class FbAuthPageAndroidHdpi(FbAuthPage, AndroidPage):
Итого: Если хотим ОС-специфичные функции, наследуемся от нужного класса.
Организация страниц
В предыдущих разделах мы говорили сначала о FbAuthPage, а потом перескачили на FbAuthPageiOS и FbAuthPageAndroidHdpi. В этой секции мы обсудим, что это за классы, зачем они нужны, и как связаны с FbAuthPage.
Изначально мы говорили, что хотим запускать один и тот же тест на разных платформах, но даже в рамках одной платформы у нас могут вознинуть серьезные отличия в представлении графических ресурсов. Например, ресурсы под hdpi могут отличаться от xhdpi, ресурсы под iOS могут отличаться от тех же под Android. При этом отличаются только ресурсы, а методы работы с ними остаются теми же (или почти теми же, с поправкой на guidelines). Нужно было придумать какое-то решение для переопределения ресурсов под разные платформы, и мы использовали стандартное наследование. Другими словами, наши страницы можно разделить на два уровня — общие и платформозависимые.
- Общая страница содержит общую логику работы с ресурсами на странице. В нашем примере это FbAuthPage. Эти методы делают те же операции и под iOS, и под Android.
- Платформозависимые страницы обычно содержат только ресурсы, которыми нужно переопределить ресурсы общес страницы. Они выглядят как-то так:
class FbAuthPageAndroidHdpi(FbAuthPage, AndroidPage): email = ResourceLoader([Resource.fbEmailFieldAndroidHdpi, Resource.fbEmailFieldAndroidHdpi_ru])
При таком разнообразии страниц, не хотелось бы, чтобы тест или страница сама решала, какие страницы им загрузить. В Imagrium это обязанность системы — прочитать конфигурацию и загружать нужные страницы, когда страница вызывает метод load(). Чтобы система загружала правильные классы, эти классы должны быть по-определенному названы. Более подробно:
- iOS страницы должны называться [общая страница] + «iOS», например: FbAuthPageiOS. Дополнительно эта страница должна быть наследником FbAuthPage.
- У Android страниц больше параметров — это версия ОС и плотность. Сначала система пытается загрузить класс [общая страница] + "_" +"[мажорная версия]" + "_"+"[минорная версия]" + "_" + «Android» + "[плотность]". Например: FbAuthPage_4_2_AndroidHdpi. Если она не нашла такой класс, то пробует загрузить: [общая страница] + «Android» + "[плотность]". Например: FbAuthPageAndroidHdpi.
Если все еще класс не найден, то система отдает AssertionError с соответствующим описанием, что проваливает тест.
На практике это выглядит так:
В тесте пишем
fbAuthPage = authPage.signUpFb()
В общей странице AuthPage есть метод:
def signUpFb(self):
self.actionAgreeTermsBtniOS.click()
self.actionSignUpFb.click()
return FbAuthPage.load(self.box, self.settings)
Данный метод вызывает метод load(), при выполнении которого система решает, какую бы из страниц вызвать(например, FbAuthPageiOS или FbAuthPageAndroidHdpi).
Итого: Мы реализуем общую логику работы страницы, а потом, если того требует другая плотность/платформа, добавляем измененные ресурсы в соотв��тствующие классы страниц. Система по конфигурации решает, какую страницу вызвать в подходящем случае.
Как запускать тесты
Предполагается, что вы клонируете проект с Github, откроете в Eclipse PyDev, удалите все лишнее, добавите все нужное, а потом захотите это протестировать. Для тестирования вам необходимо установить все, что нужно для работы фреймворка (смотрите инструкции по установке), а потом из корня проекта запустите:
ant
Чуть более подробно, этот вызов запускает run.py с определенным конфигурационным файлом в качестве единственного входного параметра (например, conf/android_settings.conf) а также с прописыванием необходимых путей в переменные PATH и CLASSPATH, а потом создает страницу с результататми тестирования.
В общем случае при тестировании запускается эмулятор, на нем переустанавливается приложение, после чего создается снимок эмулятора. Далее на каждый тест запускается один и тот же снимок, на котором и проходит очередной тест.
Заключение
Мы рекомендуем использовать Imagrium в тех случаях, когда вам нужно тестировать сразу несколько платформ или когда у вас нет простого доступа к коду приложения, чтобы добавить в нем локаторы для GUI элементов. Наиболее быстро освоить фреймворк получится у тех, кто программировал на Python, хотя простота синтаксиса языка должна способствовать быстрому обучению работы с инструментом. В этой статье были приведены только основы работы с Imagrium, подробно о конфигурации фреймворка и его возможностях (например, многопользовательское выполнение сценариев) я расскажу в последующих статьях, если эта статья будет интересна сообществу. Также можете прочитать официальную документацию на Github странице проекта и посмотреть этот Hello, World пример.
