В случае же с UI-тестами (они же end-to-end) ситуация куда более неоднозначная. Подобные тесты запускают приложение и нажимают на конкретные элементы интерфейса, иными словами - они имитируют взаимодействие человека с интерфейсом. Подобное тестирование может занимать очень много времени и требует оптимизации. Было принято решение использовать технологию контейнерной виртуализации, а как инструмент взять Appium. При использовании подобной связки существует возможность распараллелить запуск тестов в контейнерах и значительно ускорить тем самым процесс тестирования программного кода. Помимо всего прочего Appium дает возможность не использовать исходный код для запуска UI тестов, что существенно быстрее, нежели родное тестирование посредствам среды разработки XCode.
Appium - платформа, позволяющая писать тесты на любом языке программирования. Кроме того, она дает возможность запускать одни и те же тесты на разных платформах, при условии, что разработчики расставили одинаковые идентификаторы на элементы интерфейса и сам интерфейс повторяется на необходимых экранах.
Рис. 3. Обобщенная схема работы с Appium
Для работы Appium-сервера необходимо установить все пакеты на машину, на которой происходит запуск тестов. Так как используется облачное решение - установка необходимых компонентов должна происходить перед каждым запуском пайплайна - в before_script, что и было реализовано:
- ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- brew upgrade node
- sudo npm install -g appium --unsafe-perm=true --allow-root
- sudo pip install Appium-Python-Client
- pip install -U pytest
- brew upgrade carthage
В первой строке производится установка homebrew, затем обновляется npm, затем устанавливается Appium, затем производится установка библиотек python, необходимых для запуска тестов, и в конце производится обновление менеджера зависимостей carthage, необходимого для корректного запуска тестов.
Таким образом конечный алгоритм тестирования состоит из iOS приложения, написанного в соответствии со всеми требованиями, предъявляемыми к современному программному обеспечению и, в том числе, в соответствии с требованием о тестируемости кода, что позволяет написать нативные unit-тесты, использовать технологию контейнерной виртуализации для запуска end-to-end тестов посредствам платформы Appium и запускать это на машинах, предоставляемых платформой Travis CI, которая также связана с Github репозиторием проекта и позволяет наладить запуск любых скриптов базируясь на пушах в указанные ветки.
7.3 Написание E2E тестов на базе Appium
Одно из преимуществ Appium'a над системой тестирования, предоставляемой интегрированной средой разработки XCode, заключается в том, что у пользователя данной платформы есть возможность выбрать язык программирования, на котором будут написаны UI-тесты.
Выбор стоял между многими языками программирования (Java, Objective-C, JavaScript (Node), PHP, Python, Ruby, C#, Clojure, Perl) и было решено остановиться на Python, ввиду его простого синтаксиса и распространенности. Помимо этого, python довольно просто изучить с нуля, что является довольно важным критерием для языка тестирования, ведь впоследствии написание UI-тестов можно частично переложить на специалистов по тестированию, и им будет намного проще выучить python, чем Java. Таким образом все end-to-end тесты в проекте были написаны на Python с использованием библиотеки PyTest.
В первую очередь было решено покрыть тестами простейшие сценарии работы iOS приложения. В приложении имеется авторизация, а, следовательно, и соответствующие экраны - три экрана для заполнения начальных данных (они присутствуют только при регистрации), и один экран с двумя состояниями для ввода номера телефона и смс-сообщения (присутствует и при регистрации и при входе). На экранах заполнения данных присутствует проверка введенных значений, что приводит к изменению экрана - на эти сценарии и были написаны первые тесты. Затем была покрыта логика введения телефона, а затем введения смс-сообщения. Этот функционал было решено проверять исключительно на тестовой среде с заранее известным смс-кодом, так как в данной логике используется сторонний сервис отправки сообщений, который может далеко не всегда работать стабильно, и, к тому же, для таких целей пришлось бы перехватывать сообщение, так как на симулятор, на котором запускается приложение для тестирования, сообщения приходить не могут. Это слишком сложная логика для проведения тестов, которая сама может содержать ошибки, иными словами - написание такого рода теста могло бы привести к еще большим проблемам и непониманиям, чем без него. Именно по этой причине и было решено не проверять функционал прихода смс-сообщений (так как за это отвечает сторонний сервис), а проверить только реакцию пользовательского интерфейса на правильно и неправильно введенное сообщение, что было можно сделать на тестовой среде с установленным бэкенд-разработчиком кодом.
Затем тестами покрывался экран настроек. Основное, что там нужно было проверить - правильность момента сохранения данных. Если пользователь нажимает на кнопку «назад» - данные сохраняться не должны и при последующем переходе к экрану настроек пользователь должен увидеть неизмененную информацию. Если пользователь нажимает кнопку «ок» - данные должны сохраниться и при последующем переходе на экран настроек пользователь должен увидеть измененные данные.
После вышеобозначенных тестов было решено написать проверку всей цепочки действий, которую может совершить пользователь, а именно - войти в приложение, зарегистрироваться, введя определенную информацию о себе, после чего попасть на экран информации, где он должен увидеть введенное ранее имя и возраст, соответствующий введенной дате рождения. С этого экрана пользователь может перейти на экран настроек, в котором также везде должны быть введены те же самые данные. После этого пользователь может поменять данные, нажать на кнопку «ок» и попасть на экран информации. Затем снова зайти на экран настроек и выйти из аккаунта, попав на экран входа. С экрана входа пользователь получает возможность заново войти в приложение, указав введенный при регистрации номер телефона. Попав после авторизации, в приложении пользователь должен увидеть те же самые данные в экране настроек, которые он вводил перед нажатием на кнопку «ок». Подобный тест позволяет полностью покрыть путь еще не зарегистрированного в приложении пользователя. Если сломается механизм регистрации, либо механизм сохранения данных в базу - данный тест не пройдет и тем самым не позволит выложить приложение с неработающим базовым функционалом. Очень важно тестировать подобный функционал в первую очередь, так как без него все остальное не имеет смысла - если пользователь не смог зарегистрироваться, он не сможет пользоваться приложением.
Пример теста выглядит следующим образом:
def testPhoneTextField(self):
phoneTextField = self.driver.find_element_by_accessibility_id('phoneTextField')
phoneTextField.send_keys("+79999999999")
sleep(1)
self.assertEqual(phoneTextField.get_attribute("value"), "+7 999 999-99-99")
Сначала находится элемент на экране с соответствующим id доступа, установленным заранее. Затем в поле записывается информация с помощью метода send_keys, после чего программа на всякий случай ждет непродолжительное время и затем берет значение из поля. Ожидаемый результат - данные отформатировались.
Одной из возможностей платформы Appium является написание одних и тех же сценариев на разные платформы (iOS и Android). Мы, совместно с Android разработчиком, изначально использовали подобную практику, но впоследствии отказались от этого. Дизайн под разные платформы может отличаться и, таким образом, наличие одинаковых тестов под разные платформы может в один момент все усложнить - подобная система не была бы расширяемой, что противоречит принципам проектирования.
7.4 Запуск системы
Перед запуском системы в работу было проведено ее повсеместное тестирование на отказоустойчивость и выдачу результатов, соответствующих действительности - сборка приложения должна выкладываться только при всех пройденных тестах. Так как в системе участвует значительное количество сторонних компонентов, таких как Travis CI, Github, Appium, их работоспособность по отдельности проверяется силами тех разработчиков, которые их предоставляют, и, в связи с тем, что обозначенные компоненты уже давно используются сообществом и имеют исключительно положительную репутацию, то было решено перейти к тестированию связки компонентов, считая, что по-отдельности они всегда работоспособны. Такое допущение было сделано исходя из вероятностных соображений, так как все обозначенные выше компоненты имеют свой набор всех возможных тестов и разрабатываются корпорациями, шанс того, что они откажут несущественен.
Для проверки работоспособности системы она была введена в использование и все операции по выкладыванию сборок проходили исключительно через нее. Это позволило устранить очевидные ошибки, сделанные в начале разработки системы и усовершенствовать ее, чтобы в дальнейшем систему можно было применять к любым проектам на платформе iOS. Так же было проведено тестирование негативно отрабатываемых сценариев - были написаны тесты, всегда выдающие ошибочную проверку. Система показала правильные результаты - сборка не была выложена.
После описанных выше действий код конфигурации системы был просмотрен и приведен в соответствие с современными требованиями к разработке программного обеспечения. Все повторяющиеся участки кода были вынесены в отдельные скрипты. Сами же скрипты были разбиты на логические составляющие. Так, все взаимодействия с системой сборки XCode и API iTunes Connect были вынесены в файл Fastfile, написанный на языке программирования Ruby (используется для написания скриптов Fastlane). Логика, обозначающая поведение скриптов, в зависимости от ветки, была вынесена из файла конфигурации Travis CI в отдельный файл build.sh (написан на языке bash) для большей читаемости. Таким образом система была приведена в готовый к распространению вид.
Все последние сборки, выложенные на платформу testflight с ветки master или же на платформу fabric с ветки develop, прошли UI- и Unit-тестирование с помощью Appium и встроенной системы для Unit-тестов среды разработки XCode.
8 Разработка Android части
8.1 Разработка приложения
Continuous Integration (далее CI) как технология должна оправдывать свою реализацию в проекте. Приложение может функционировать и без модуля CI, и, если речь идет о маленьком проекте, создание такого модуля будет излишним, потому что время и усилия, потраченные на конфигурацию всех его частей будут превышать дальнейшую пользу. Так, если приложение, в которое внедряется модуль CI, имеет низкую сложность и не представляет оснований к предположению его вероятной ненадежности, модуль CI является излишним.
Таким образом, продумывая приложения для дальнейшей интеграции, мы отталкивались не от идеи приложения и ее целесообразности, а от целесообразности внедрения в дальнейшем в это приложение модуля CI.
Стоит заметить, что CI - это процесс. Он состоит из нескольких этапов, и реализация многих этапов в свою очередь зависит от самого приложения.
Нами было выбрано мультиплатформенное веб-приложение для общественных коммуникаций. Поддержка такого приложения определенно несет в себе ряд сложностей, которые можно решить при помощи добавления к проекту модуля CI.
Важным аспектом этого приложения является необходимость совместного доступа. Это механизм, позволяющий пользоваться приложением с разных платформ одновременно.
Продумав все вышеупомянутые моменты, я приступил непосредственно к разработке приложения.
Подготовка к разработке - один из самых важных шагов, позволяющий значительно упростить последующие этапы разработки. Поддержка приложения, продуманного непосредственно во время разработки, может оказаться крайне проблематичной, или вообще невозможной. Переходя к новым модулям приложения, программист может забыть о возможных конфликтах с другими частями приложения в лучшем случае, или столкнуться с необходимостью переписывать уже существующий код в худшем.
Более того, в ходе очень прямолинейной разработки с большой вероятностью будут нарушены принципы программирования (ООП-принципы в нашем случае), что неминуемо приведет к необходимости переписывания кода. Изменения в UI, логике или данных, могут повлечь за собой изменения в других частях приложения. Подобный код становится практически невозможно протестировать.
Таким образом, для начала мне предстояло выбрать архитектурный паттерн, по которому я бы делал приложение. Такой паттерн должен упрощать разработку приложения в перспективе, делать его легко изменяемым и надежно поддерживаемым.
Мной было рассмотрено несколько вариантов: MV-* паттерны, такие как MVP, MVC, MVVM; Паттерн Clean Architecture; Паттерн RIB.
MV-* паттерны предлагают опцию, при которой логика и данные приложения могут быть написаны таким образом, чтобы каждый компонент мог быть протестирован отдельно. Однако по своей сути такие паттерны направлены в первую очередь на реализацию проблем работы с UI. По этой причине использования только MV-* паттернов недостаточно для построения хорошей архитектуры.
Далее, было решено рассмотреть паттерн Clean Architecture. Его цель состоит в том, чтобы разделить проблемы, не давая бизнес-правилам знать ничего о внешних слоях приложения, чтобы их можно было проверить без какой-либо зависимости от сторонних элементов, таких как библиотеки, различные фреймворки или специализированный подход к UI. Стоит отметить, что каждый уровень использует свою собственную модель данных, чтобы можно было достичь этой независимости. Однако минусом этого паттерна является слишком большое усложнение логики и необходимость в маппинге данных, которые в случае приложения для данного проекта является излишним.
RIB -- это не только архитектурный паттерн. Этот кроссплатформенный фреймворк позволяет обеспечивать приложения качественной архитектурой, по правилам паттерна RIB. Вот его отличительные принципы: