Привет, я решил выступить с докладом, имеющим непосредственное отношение не только к JS, но и к мобильной разработке.
"Иногда вам может показаться, что ваша жизнь разработчика довольно скучна. Не давайте себе заскучать - займитесь тестированием, и ваша жизнь не будет больше скучной никогда."
Несколько недель назад я получил задачу по покрытию тестами нашего мобильного приложения под iOS.
У нас уже был опыт написания E2E-тестов для Android приложения, но в связи с появившимся свободным от разработки временем, поняли, что необходимо озадачиться тестами и под iOS. Тесты для приложения под Android были необходимостью, ибо версия для этой OS отличается "стабильной" нестабильностью. Было решено привлечь js-разработчиков для помощи Android-коллеге. Необходимость написания тестов под iOS осознали, ну впрочем как обычно, когда гром грянул.
E2E-тестирование - это автоматизация пользовательского поведения для вашего приложения (сайта) с целью получения конечного результата. E2E-тестирование помогает проверить такие кейсы, которые не покроет unit-тестирование. Во время проведения E2E-тестов автоматизатору доступно все приложение в целом, он не ограничен каким одним компонентом, для которого он проверяет работоспособоность изолированно. E2E-тестирование - это возможность облегчить работу ручным тестировщикам, а также сэкономить на этом в принципе. Никто не хочет проводить вручную регрессионное тестирование, даже если приходится. Как правило, делается это с неохотой и вызывает удивление (а также праведный гнев в сторону разработчиков), мол как так можно было, разрабатывая одно - поломать что-то в совершенно постороннем месте?! Впрочем для этого регрессионное тестирование и проводят.
В нашей команде скоро произойдет неминуемый переход на React Native для мобильных приложений, а там мы столкнемся с новой порцией не только проблем, но и упрощений все-таки.
Кто из вас уже столкнулся с этим тоже и, возможно, как-то решил эту проблему, возможно, пошел тем же путем, что и мы, а, может быть, вы ограничились просто unit-тестирование, либо тесты в вашей команде пишут все-таки iOS-разработчики.
В любом случае, этот доклад будет полезен и для вас.
Любой человек при решении совершенно новых для него задач начинает с выбора инструмента (-ов), которые помогут ему справиться с работой. С этого начали и мы. Если прямо сейчас выбирать среди инструментов для E2E-тестирования мобильных приложений под обе (прости MSFT) платформы, то гугл выдаст вам по сути два ответа - это Appium и Calabash. Отличия между инструментами просты:
- Appium работает со всеми языками, которые поддерживает Selenium Webdriver, а Calabash работает на Ruby.
- Если вы работали уже с Selenium Webdriver, то и с Appium вы работать умеете, а Calabash имеет свой API, но при этом сценарии можно писать на Cucumber.
- Appium имеет GUI для инспектирования элементов на странице (получения их id, xpath, etc), в Calabash для этого есть cli.
- Appium имеет больший круг поклонников ввиду низкого порога вхождения, а также аналогичного API как у Selenium Webdriver.
В общем, ввиду имеющегося опыта работы с Selenium Webdriver, а точнее с его js-реализацией webdriver.io, был выбран именно Appium.
В качестве фреймворка для тестирования и ассертов был выбран tape, а если быть точным, то его модификация tape-async. Tape написан тем же человеком, что и Browserify, ник на гитхабе substack. Tape мы уже использовали для unit и e2e-тестов в вебе, поэтому не видели необходимости что-то менять. А изначально выбор на него пал после прочтения статьи Эрика Эллиота на Medium.
Платформа для тестирования - iOS 9.3, iPhone Simulator (5s), Xcode 7.3.
Если с запуском tape еще все ясно (главное - не забудьте зареквайрить babel-register), то с остальными инструментами придется повозиться.
Немного утилитарных вещей на старте упростят вам жизнь в будущем. Что сделали мы:
- Утилита для проверки - запущен ли Appium
- Утилита для нахождения запущенного симулятора
- Свой раннер тестов, чтобы реюзать существующий симулятор и синглтон хэлпера
- Ну и раннер всего и всея, чтобы проверить, что запущен Аппиум, найти нужный симулятор, который запущен, либо выбрать дефолтный, а также запустить, наконец, уже тесты.
В настоящий момент у нас есть небольшой Helper, который мы в каждом файле с тестами, как минимум из-за одной-двух функции. Текущий список вспомогательных функций выглядит так:
- backToHome.js - позволяет из любого состояния вернуться на главный экран
- cache.js
- createMenuButtonId.js - возвращает xpath для указанной кнопки на главном экране
- fixLocationError.js
- getAttr.js - позволяет получить значение атрибута для указанного xpath элемента
- getSource.js
- init.js
- iosDoneButtonIdCreator.js
- login.js - произведет авторизацию для указанного (дефолтного) пользователя
- logout.js - разлогинится в приложении
- mapOverVisible.js
- skywalker.js
Выделенные функции, пожалуй, самые полезные и популярные.
В команде существуют договоренности при написании тестов:
- Каждый тестовый набор должен начинаться с главного экрана (для консистентности).
- Каждый тестовый набор должен проходить, если запущен исключительно он, а также если он запущен в последовательности с другими тестовыми наборами.
- Каждый тестовый набор внутри себя должен выполнить все пререквизиты (залогиниться или разлогиниться, etc).
- Не использовать метод .pause(%TIME_IN_MS%) внутри тестов, т.к. он исключительно замедляет работу тестов, и ни к чему хорошему не приводит.
- Разрешается использовать .pause(%TIME_IN_MS%) там где вообще никак не поможет .waitForVisible(ELEMENT_XPATH, %TIME_IN_MS%) - у нас это один случай, и то только в Android. Так что помните - 99.9% задач решается без пауз.
Одним из признаков шизофрении считается ожидание разного результата при многократном повторении одних и тех же действий. Причем тут Appium? Appium разрушает это определение — можно ожидать разного результата, если работаешь с ним. По совершенно независящим от вас причинам все может пойти не так, как вы ожидали.
- Благо есть ситуации, когда это можно легко исправить путем увеличения времени по таймауту для метода .waitForVisible(), например, когда понимаете, что вот тут в приложении долго шел ответ от сервера, либо он принес большой кусок данных настолько, что лоадер отображался несколько дольше, чем вы ожидали изначально.
- Но бывает так, что вы сходу не можете понять и решить проблему, например, у нас есть кнопка в всплывающем экране, которая видна как на iPhone 5s, так и на iPhone 6 человеческому глазу. Код приложения не меняется, код тестов тоже. Мы просто сменили айфон и ожидаем, что все будет как и прежде, но нет. Увы, я пока что так и не разобрался в чем дело.
В процессе написания E2E-тестов с использованием Appium вы будете часто использовать Appium Inspector. Это инструмент, который, как вы поняли из названия, позволяет проинспектировать вьюхи приложения, с которым вы работаете. Он позволяет узнать вам id или xpath элементов текущего экрана. Внутри себя Appium Inspector для получения данных о вьюхе выполняет команду .getSource().
Во время написания тестов для приложений я заметил одну интересную особенность, которая видна в тот момент, когда вы выполните команду .getSource(). Интерес в том, что в Android и iOS эта команда работает одинаково хорошо, но сами OS возвращают несколько отличающийся результат. Пальма первенства отходит в этом случае к гугл. Android всегда вернет по команде .getSource() исключительно исходники видимой части вьюхи, в то время как iOS возвращает кроме видимой части еще исходники, например, главного экрана, а также элементов, которые видит человеческий глаз, но не видит система автоматизации. ПОЭТОМУ! Выключите раз и навсегда чекбокс "Show invisible" в Appium Inspector, чтобы однажды не задаться вопросом, а почему это я кнопку вижу, а мой метод .waitForVisible() все равно падает.
-
Тесты идут очень медленно, налейте чай, подумайте над тем, что будете дальше покрывать тестами в вашем приложении.
-
Перед запуском теста пройдитесь полностью по нему с помощью симулятора и Appium Inspector.
-
Смело выставляйте большие таймауты для .waitForVisible() там, где это возможно понадобится. Тут дело вот в чем - .waitForVisible() принципиально лучше, чем использование .pause() ввиду того, что его параметр для времени - это таймаут, а не время на приостановку чего-либо. Метод .pause() не гарантирует вам ничего, в то время, как .waitForVisible() гарантирует вам либо ошибку по истечению таймаута, либо успешное выполнение кода.
-
Смело используйте try / catch в местах, где теоретически может что-то случится, но необязательно. Например, вам необходимо скрыть подсказку при переходе на абстрактный экран, чтобы работать с приложением дальше, при этом операция эта может выполняться в цикле, т.е. в следующий раз этой подсказки уже точно не будет. В этом случае ошибка свалится в пустой блок catch и выполнение кода продолжится, например:
try { await driver .waitForVisible(acceptTwitterAccountsAccess, 2000) .click(acceptTwitterAccountsAccess) } catch (e) { // Do nothing }
-
Весь механизм работы тестов построен на промисах. Каждое действие — это запрос к Selenium Server, который имеет свой апи. Так что по сути, вам ничего не мешает делать запросы хоть из терминала, главное — знать что отправлять.
-
iPhone Simulator никогда не запустится с первого раза. А если он перезапустился уже как минимум 3-4 раза, то смело останавливайте тест раннер, затем appium, подождите минутку и запускайте снова все - это помогает).
-
Опыт нативной разработки будет полезен и упростит жизнь, если такового нет, то смело дергайте коллег, которые написали приложение.
-
Со временем я стал записывать через QuickTime с экрана прохождение теста. Главное, чтобы у вас было достаточно места на жестком диске, потому что это очень полезно в той ситуации, когда вы вдруг куда-то отвернулись от экрана, не следите за выполнением, а запись вам потом поможет, если вдруг что упадет.
Мы с коллегой хотим все наши наработки выложить в open source в рамках Tipsi на github, обернем все в npm-модуль и выпустим.