Хабр, привет!
Меня зовут Борис. Я Mobile QA lead в Vivid Money.
Это вступительная статья в цикле статей по iOS-автоматизации, в которых я расскажу о том, как ускорить прохождение UI-тестов.
Данная статья будет полезна iOS-автоматизаторам с опытом, либо разработчикам.
В рамках этой статьи мы разберем такие этапы:
зачем ускорять время прохождения UI-тестов;
что такое Test runner, и какие они бывают;
что нужно для прогона тестов без компиляции проекта;
делимся опытом, как это помогает нам.
Зачем ускорять время прохождения UI-тестов?
Быстрое прохождение автотестов позволяет использовать UI-тесты как можно чаще: на merge request, во время регрессионого тестирования и.т.д. Что позволяет отлавливать больше багов и меньше тратить время на поиск дефектов руками. Но задумываться об ускорении не всегда целесообразно на первых порах.
Вот ситуации, когда ускорение можно отложить:
небольшое кол-во тестов(меньше 100);
тесты прогоняются ночью или рано утром и никак не затрудняют работу другим;
время прохождения тестов со сборкой проекта занимает меньше 30 минут.
Вот ситуации, когда стоит задуматься об ускорении прохождения тестов:
Запущенные тесты образуют очереди на ci и затрудняют работу разработчикам, автоматизаторам;
UI-тесты запускается одновременно с началом регрессионого тестирования, а не заранее;
Вы хотите запускать автотесты чаще чем на ночных прогонах и во время регресса.
Что такое Test runner и какие они бывают
Test runner - это библиотека или инструмент, который выбирает сборку (или каталог с исходным кодом), который содержит тесты и набор настроек, а затем выполняет их и записывает результаты тестов.
Для запуска тестов на ci есть несколько вариантов:
Xcodebuild - нативная утилита от Apple;
Fastlane - самый популярный runner для iOS;
Marathon - раннер под iOS и Android;
Emcee - раннер от Avito.
Что нужно для прогона тестов без компиляции проекта
Для этого вам понадобится Derived data и xctestrun:
Derived data - это папка, которая находится в
~/Library/Developer/Xcode/DerivedData
по дефолту. Это место, в котором xcode хранит все виды промежуточные результатов сборки, сгенерированные индексы и так далее. Расположениеderived data
можно изменить в настройках Xcode (вкладка Locations).xctestrun - это файл, формирующийся после сборки таргета с тестами. Он содержит необходимую информацию для выполнения тестов. Для каждого тестового таргета он содержит запись с путем к тестовой машине, переменные среды и аргументы командной строки.
В качестве примера рассмотрим две реализации, используя fastlane
и xcodebuild
. Поскольку под капотом всех ранеров для iOS используется xcodebuild
.
Алгоритм действий будет следующий:
Создание сборки для тестирования
Fastlane
run_tests(
derived_data_path: "~/MyProject/derivedData",
scheme: "YourProjectUITests",
build_for_testing: true
)
Xcodebuild
$ xcodebuild -workspace <your_xcworkspace> -scheme <your_scheme> -sdk iphonesimulator -destination ‘platform=iOS Simulator,name=<your_simulator>,OS=14.0’ build-for-testing
workspace – путь к
.xcworkspace
файлу. Нужно указывать, если в проекте используются workspace;scheme – название схемы с тестами, которая будет запущена;
sdk – по умолчанию используется
iphoneos
, для использованияMacOS
илиIPadOS
нужно изменить значение;destination – параметр состоит из наборов пар ключ-значения, которые описывают, на чем запускать тесты/билд.
derviedDataPath – путь, куда сохранять
derived data
после сборки проекта. По умолчанию это значение равно тому, что установлено у вас в настройках в Xcode, но для CI лучше указать относительный путь;build-for-testing – параметр для того, чтобы собрать билд для тестирования.
Запуск тестов
Мы можем запустить тесты с помощью derived data
или .xctestrun
:
Fastlane
# Запуск тестов по Derived data
run_tests(
derived_data_path: "/Users/blysikov/MyProject/Swift-Radio-Pro-master/my_folder4",
test_without_building: true,
scheme: "SwiftRadioUITests",
device: "iPhone 8",
testplan: 'Regression'
)
# Запуск тестов по Xctestrun
run_tests(
scheme: "SwiftRadioUITests",
xctestrun: "/Users/blysikov/MyProject/Swift-Radio-Pro-master/my_folder4/Build/Products/SwiftRadioUITests_Smoke_iphonesimulator15.0-arm64.xctestrun",
)
Xcodebuild
# Запуск тестов по Xctestrun
xcodebuild -workspace "UITestExample.xcworkspace" -scheme "UITestExample" -xctestrun "build/Build/Products/UITestExample_iphoneos12.2-arm64e.xctestrun" -destination "id=9b63456a33e367d45c9aja8bj9b93223ehcf79b1" -resultBundlePath "result" test-without-building
xctestrun – путь к
.xctestrun
файлу;destination – параметр состоит из наборов пар ключ-значения, которые описывают, на чем запускать тесты/билд;
resultBundlePath – путь, куда сохранять результаты прогона;
test-without-building – параметр для того, чтобы запустить прогон тестов без сборки проекта, но используя
.xctestrun
.
Как сделали мы
Описание процесса
Каждый день у нас собираются сборки для тестирования, которые отправляются в testFlight. В этот пайплайн мы добавили последним действием шаг, который собирает нам derived data тестового таргета, архивирует её, и отправляет на билд агент.
При запуске тестов мы скачиваем с билд агента derived data
, разархивируем и на ней запускаем тесты.
Установка
Для начала нам понадобится импортировать 3 библиотеки:
net-scp - Нужен для передачи файлов на удаленный хост через SCP;
rubyzip - Нужен для архивирования файлов;
fileutils - Нужен для создания директории.
Также нам нужно будет дописать 2 вспомогательных файла:
Первый будет взаимодействовать с билд агентом.
require 'net/scp'
module ScpService
def upload_derived_data(path_to_derived_data_zip)
Net::SCP.start('your_host', 'your_login') do |scp|
scp.upload(path_to_derived_data_zip, 'path_where_to_store_on_build_agent')
end
end
def download_derived_data(destination_download_path)
Net::SCP.start('your_host', 'your_login') do |scp|
scp.download('path_where_to_store_on_build_agent', destination_download_path)
end
end
end
Второй будет архивировать нашу derived data
. Архивация нужна для того, чтобы уменьшить место, которое занимает derived data
, в крупных проектах она может весить больше 10 ГБ.
require 'zip'
class ZipFileGenerator
# Initialize with the directory to zip and the location of the output archive.
def initialize(input_dir, output_file)
@input_dir = input_dir
@output_file = output_file
end
# Zip the input directory.
def write
entries = Dir.entries(@input_dir) - %w[. ..]
::Zip::File.open(@output_file, create: true) do |zipfile|
write_entries entries, '', zipfile
end
end
private
# A helper method to make the recursion work.
def write_entries(entries, path, zipfile)
entries.each do |e|
zipfile_path = path == '' ? e : File.join(path, e)
disk_file_path = File.join(@input_dir, zipfile_path)
if File.directory? disk_file_path
recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
else
put_into_archive(disk_file_path, zipfile, zipfile_path)
end
end
end
def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
zipfile.mkdir zipfile_path
subdir = Dir.entries(disk_file_path) - %w[. ..]
write_entries subdir, zipfile_path, zipfile
end
def put_into_archive(disk_file_path, zipfile, zipfile_path)
zipfile.add(zipfile_path, disk_file_path)
end
end
Реализация
Реализация будет состоять из 2 job:
Первая джоба формирует нужные файлы и отправляет их на билд агент. Вот как это выглядит:
# Собираем таргет с тестами для формирования derivedData и xcresults
run_tests(
derived_data_path: "~/yourProject/derivedData",
scheme: "YourProjectUITests",
build_for_testing: true
)
# Архивируем deirived data
zf = ZipFileGenerator.new('directory_to_zip', 'path_to_derived_data_zip')
zf.write
# Отправляем на билд агент
upload_derived_data('path_to_derived_data_zip')
Вторая джоба запускается во время ежедневных прогонов или во время прохождение регресса. Вот как это выглядит:
# Скачиваем derived data
download_derived_data('destination_download_path')
# Разархивируем derived data
Zip::File.open('destination_download_path') do |zip_file|
zip_file.each do |f|
f_path = File.join('destination_unzip_path', f.name)
FileUtils.mkdir_p(File.dirname(f_path))
zip_file.extract(f, f_path) unless File.exist?(f_path)
end
end
# Запускаем тесты с xctestrun
run_tests(
scheme: "yourUITests",
xctestrun: "your_deived_data_folder/yourUITests_test_plan_name_iphonesimulator14.5-arm64.xctestrun"
)
Самое важное
-
Время прохождения нужно ускорять, когда ваши тесты:
Создают очереди на CI;
Прогон вместе со сборкой проекта занимают более 30 минут.
Если только начинаешь работать с раннерами - используй
fastlane
. В интернете куча примеров, как организовать прогон тестов на ci в связке с ним.Для прогона тестов без компиляции проекта тебе понадобится:
derived data
и .xctestrun
.Ищешь способ сократить время прогона - воспользуйся нашим подходом в разделе: “Как сделали мы”.
Данный подход позволил нам сократить время сборки проекта при запуске тестов на регрессе с 20 минут до 3 минут. 3 минуты уходит на то, чтобы скачать нужный нам архив с derived data с билд агента и разархивировать его. Дальше мы её передаем в аргументы для прогона тестов, и тесты начинают гоняться.
Полезные статьи на эту тему:
Интересуешься автоматизацией на iOS? Подписывайся на мой телеграмм-канал, в котором я публикую материалы, которые будут полезны как начинающим, так и опытным iOS-автоматизаторам.