Меня зовут Дмитрий, я работаю тестировщиком в компании MEL Science. Совсем недавно я закончил разбираться со сравнительно свежей фичей от Firebase Test Lab — а именно, с инструментальным тестированием iOS приложений с использованием нативного фреймворка тестирования XCUITest.
До этого я уже распробовал Firebase Test Lab для Android и мне все очень понравилось, так что я решил попробовать поставить тестовую инфраструктуру iOS проекта на те же рельсы. Приходилось очень много гуглить и не все получалось с первого раза, поэтому я решил написать статью-туториал для тех, кому все еще это предстоит.
Итак, если у вас есть UI тесты на iOS проекте, вы сможете уже сегодня попробовать запустить их на реальных девайсах, любезно предоставленных Корпорацией Добра. Заинтересованным — добро пожаловать под кат.
В повествовании я решил отталкиваться от некоторых исходных данных — приватный репозиторий на GitHub и система сборки CircleCI. Название приложения — AmazingApp, bundleID — com.company.amazingapp. Эти данные я привожу сразу, для уменьшения последующей путаницы.
Если те или иные решения в своем проекте вы реализовали по-другому — делитесь опытом в комментариях.
1. Сами тесты
Создаем новую ветку проекта для UI тестов:
$ git checkout develop
$ git pull
$ git checkout -b “feature/add-ui-tests”
Откроем проект в XCode и создадим новую Цель (Target) с UI тестами [XCode -> File -> New -> Target -> iOS Testing Bundle], даем ей говорящее название AmazingAppUITests.
Переходим в секцию Build Phases созданного Target и проверяем наличие Target Dependencies — AmazingApp, в Compile Sources — AmazingAppUITests.swift.
Хорошей практикой является выделение различных вариантов сборки в отдельные Схемы (Schemes). Создаем схему для наших UI тестов [XCode -> Product -> Scheme -> New Scheme] и даем ей такое же название: AmazingAppUITests.
Build созданной схемы должен включать в себя Target основного приложения — AmazingApp и Target UI тестов — AmazingAppUITests — см.скриншот
Далее, создаем новую конфигурацию сборки для UI тестов. В XCode кликаем по файлу проекта, переходим в секцию Info. Кликаем на “+” и создаем новую конфигурацию, например XCtest. Это потребуется нам в дальнейшем, дабы избежать плясок с бубном когда дело дойдет до code signing.
В вашем проекте есть по крайней мере три Target: основное приложение, юнит тесты (ведь они есть, не так ли?) и созданный нами Target UI тестов.
Заходим в Target AmazingApp, вкладка Build Settings, раздел Code Signing Identity. Для конфигурации XCtest выбираем iOS Developer. В разделе Code Signing Style выбираем Manual. Provisioning profile мы еще не сгенерировали, но чуть позже обязательно к нему вернемся.
Для Target AmazingAppUITests делаем то же самое, но в графу Product Bundle Identifier вписываем com.company.amazingappuitests.
2. Настройка проекта в Apple Developer Program
Заходим на страницу Apple Developer Program, переходим в раздел Certificates, Identifiers & Profiles и затем в графу App IDs пункта Identifiers. Создаем новый App ID с именем AmazingAppUITests и bundleID com.company.amazingappuitests.
Теперь у нас есть возможность подписывать наши тесты отдельным сертификатом, но… Процедура сборки билда для тестирования подразумевает сборку самого приложения и сборки раннера тестов. Соответственно, мы сталкиваемся с проблемой подписи двух bundle ID одним provisioning profile. Благо, существует простое и элегантное решение — Wildcard App ID. Повторяем процедуру создания нового App ID, но вместо Explicit App ID выбираем Wildcard App ID как на скриншоте.
На этом этапе работа с developer.apple.com окончена, но сворачивать окно браузера мы не будем. Идем на сайт с документацией по Fastlane и читаем про утилиту Match от корки до корки.
Внимательный читатель заметил, что для использования этой утилиты нам понадобится приватный репозиторий и аккаунт, имеющий доступ как к Apple Developer Program, так и к Github. Создаем (если вдруг такого нет) аккаунт вида InfrastructureAccount@your.company.domain, придумываем мощный пароль, регистрируем его в developer.apple.com, назначаем администратором проекта. Далее, даем аккаунту доступ к github репозиторию вашей компании и создаем новый приватный репозиторий с именем вроде AmazingAppMatch.
3. Настройка Fastlane и утилиты match
Открываем терминал, переходим в папку с проектом и инициализируем fastlane как указано в официальном мануале. После ввода команды
$ fastlane init
будет предложено выбрать доступные конфигурации использования. Выбираем четвертый пункт — ручная настройка проекта.
В проекте появилась новая директория fastlane, в которой лежат два файла — Appfile и Fastfile. В двух словах — в Appfile мы храним служебные данные, а в Fastfile прописываем jobs, в терминологии Fastlane именуемые lanes. Рекомендую к прочтению официальную документацию: раз, два.
Открываем Appfile в любимом текстовом редакторе и приводим его к следующему виду:
app_identifier "com.company.amazingapp" # Bundle ID
apple_dev_portal_id "infrastructureaccount@your.company.domain" # Созданный инфраструктурный аккаунт, имеющий право на редактирование iOS проекта в Apple Developer Program.
team_id "LSDY3IFJAY9" # Your Developer Portal Team ID
Возвращаемся в терминал и по официальному мануалу начинаем настраивать match.
$ fastlane match init
$ fastlane match development
Далее вводим запрашиваемые данные — репозиторий, аккаунт, пароль и т.д.
Важно: при первом запуске утилита match попросит ввести пароль для дешифровки репозитория. Очень важно сохранить этот пароль, на этапе настройки CI сервера он нам пригодится!
В папке fastlane появился новый файл — Matchfile. Открываем в любимом текстовом редакторе и приводим к виду:
git_url("https://github.com/YourCompany/AmazingAppMatch") #Созданный приватный репозиторий для хранения сертификатов и профайлов.
type("development") # The default type, can be: appstore, adhoc, enterprise or development
app_identifier("com.company.amazingapp")
username("infrastructureaccount@your.company.domain") # Your Infrastructure account Apple Developer Portal username
Заполняем именно таким образом, если хотим в дальнейшем использовать match для подписи билдов для выкладки в Crashlytics и/или AppStore, т.е для подписи bundle ID вашего приложения.
Но, как мы помним, для подписи тестового билда мы создали специальный Wildcard ID. Поэтому, открываем Fastfile и вписываем новый lane:
lane :testing_build_for_firebase do
match(
type: "development",
readonly: true,
app_identifier: "com.company.*",
git_branch: "uitests" # создаем отдельный бранч для development сертификата для подписи тестовой сборки.
)
end
Сохраняем, вводим в терминал
fastlane testing_build_for_firebase
и видим как fastlane создал новый сертификат и положил его в репозиторий. Отлично!
Открываем XCode. Теперь у нас есть нужный provisioning profile вида Match Development com.company.*, который нужно указать в секции Provisioning profile для таргетов AmazingApp и AmazingAppUITests.
Осталось дописать lane для сборки тестов. Идем в репозиторий проекта плагина для fastlane, облегчающего настройку экспорта в Firebase Test Lab и следуем инструкциям.
Копипастим из исходного примера, чтобы наш lane testing_build_for_firebase в итоге выглядел так:
lane :testing_build_for_firebase do
match(
type: "development",
readonly: true,
app_identifier: "com.company.*",
git_branch: "uitests"
)
scan(
scheme: 'AmazingAppUITests', # UI Test scheme
clean: true, # Recommended: This would ensure the build would not include unnecessary files
skip_detect_devices: true, # Required
build_for_testing: true, # Required
sdk: 'iphoneos', # Required
should_zip_build_products: true, # Must be true to set the correct format for Firebase Test Lab
)
firebase_test_lab_ios_xctest(
gcp_project: 'AmazingAppUITests', # Your Google Cloud project name (к этой строчке вернемся позже)
devices: [ # Device(s) to run tests on
{
ios_model_id: 'iphonex', # Device model ID, see gcloud command above
ios_version_id: '12.0', # iOS version ID, see gcloud command above
locale: 'en_US', # Optional: default to en_US if not set
orientation: 'portrait' # Optional: default to portrait if not set
}
]
)
end
Для получения полной информации о настройке fastlane в CircleCI рекомендую к прочтению официальную документацию раз, два.
Не забываем дополнить наш config.yml новой задачей:
build-for-firebase-test-lab:
macos:
xcode: "10.1.0"
working_directory: ~/project
shell: /bin/bash --login -o pipefail
steps:
- checkout
- attach_workspace:
at: ~/project
- run: sudo bundle install # обновляем зависимости
- run:
name: install gcloud-sdk # на mac машину необходимо установить gcloud
command: |
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null ; brew install caskroom/cask/brew-cask 2> /dev/null
brew cask install google-cloud-sdk
- run:
name: build app for testing
command: fastlane testing_build_for_firebase # запускаем lane сборки и отправки в firebase
4. А как же наш тестовый стенд? Настраиваем Firebase.
Приступим, собственно, к тому, для чего статья и писалась.
Возможно, ваше приложение использует Firebase на бесплатном тарифном плане, возможно — не использует вовсе. Принципиальной разницы абсолютно нет, потому как для нужд тестирования мы можем создать отдельный проект с годом бесплатного использования (круто, да?)
Логинимся в наш инфраструктурный аккаунт (или любой другой, без разницы), и идем на страницу консоли Firebase. Создаем новый проект с именем AmazingAppUITests.
Важно: В предыдущем шаге в Fastfile в lane firebase_test_lab_ios_xctest параметр gcp_project должен соответствовать названию проекта.
Дефолтные настройки нас вполне устраивают.
Не закрываем вкладку, под тем же аккаунтом регистрируемся в Gcloud — это вынужденная мера, так как общение с Firebase происходит с помощью интерфейса консоли gcloud.
Google дарит 300$ на год, что в контексте выполнения автотестов эквивалентно году бесплатного использования сервиса. Вводим платежные данные, дожидаемся тестового списания 1$ и получаем 300$ на счет. По прошествии года проект будет автоматически переведен на бесплатный тарифный план, так что волноваться о возможной потере денег не стоит.
Вернемся на вкладку с проектом Firebase и переведем его на тарифный план Blaze — теперь нам есть чем платить в случае превышения лимита.
В интерфейсе gcloud выбираем наш Firebase проект, выбираем пункт главного меню «Каталог» и добавляем Cloud Testing API и Cloud Tools Result API.
Затем переходим в пункт меню «IAM и администрирование» -> Сервисные аккаунты -> Создать сервисный аккаунт. Выдаем права на редактирование проекта.
Создаем API ключ в формате JSON
Скачанный JSON понадобится нам чуть позже, а пока настройку Test Lab будем считать оконченной.
5. Настройка CircleCI
Назревает резонный вопрос — а что делать с паролями? Надежно сохранить наши пароли и прочие чувствительные данные нам поможет механизм переменных окружения нашей билд-машины. В настройках проекта CircleCI выбираем Environment Variables
И заводим следующие переменные:
- key: GOOGLE_APPLICATION_CREDENTIALS
value: содержимое json файла ключа сервисного аккаунта gcloud - key: MATCH_PASSWORD
value: пароль для дешифровки github репозитория с сертификатами - key: FASTLANE_PASSWORD
value: пароль инфраструктурного аккаунта Apple Developer Portal
Сохраняем изменения, создаем PR и отправляем на review своему тимлиду.
Итоги
В результате выполнение этих нехитрых манипуляций мы получили хороший, стабильно работающий стенд с возможностью записи видео на экране устройства в момент прохождения тестирования. В тестовом примере я указал модель устройства iPhone X, но ферма предоставляет богатый выбор из комбинации различных моделей и версий iOS.
Вторая часть будет посвящена пошаговой настройке Firebase Test Lab для Android проекта.