Ускоряем прохождение iOS UI-тестов. Часть 1. Запускаем тесты без сборки проекта

от автора

Хабр, привет!

Меня зовут Борис. Я 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:

  1. Derived data — это папка, которая находится в ~/Library/Developer/Xcode/DerivedData по дефолту. Это место, в котором xcode хранит все виды промежуточные результатов сборки, сгенерированные индексы и так далее. Расположение derived data можно изменить в настройках Xcode (вкладка Locations).

  2. 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" )  

Самое важное

  1. Время прохождения нужно ускорять, когда ваши тесты:

    1. Создают очереди на CI;

    2. Прогон вместе со сборкой проекта занимают более 30 минут.

  2. Если только начинаешь работать с раннерами — используй fastlane. В интернете куча примеров, как организовать прогон тестов на ci в связке с ним.

  3. Для прогона тестов без компиляции проекта тебе понадобится: derived data и .xctestrun.

  4. Ищешь способ сократить время прогона — воспользуйся нашим подходом в разделе: “Как сделали мы”.

Данный подход позволил нам сократить время сборки проекта при запуске тестов на регрессе с 20 минут до 3 минут. 3 минуты уходит на то, чтобы скачать нужный нам архив с derived data с билд агента и разархивировать его. Дальше мы её передаем в аргументы для прогона тестов, и тесты начинают гоняться.

Полезные статьи на эту тему:


Интересуешься автоматизацией на iOS? Подписывайся на мой телеграмм-канал, в котором я публикую материалы, которые будут полезны как начинающим, так и опытным iOS-автоматизаторам.


ссылка на оригинал статьи https://habr.com/ru/company/vivid_money/blog/649901/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *