Хабрахабр

Когда нужны скорость и масштабирование: сервер распределенных iOS-устройств

В Badoo прогоняется более 1400 end-to-end тестов для iOS-приложений на каждый запуск регрессии. Многим разработчикам UI-тестов под iOS наверняка знакома проблема времени тестового прогона. Это более 40 машинных часов тестов, которые проходят за 30 реальных минут.

Николай Абалов из Badoo поделился тем, как удалось ускорить выполнение тестов с 1,5 часов до 30 минут; как распутали тесно связанные тесты и инфраструктуру iOS, перейдя к серверу устройств; как это упростило параллельный запуск тестов и сделало тесты и инфраструктуру проще для поддержки и масштабирования.

Далее — повествование от первого лица: Вы узнаете, как легко запускать тесты параллельно с помощью таких инструментов, как fbsimctl, и как разделение тестов и инфраструктуры может упростить принятие, поддержку и масштабирование ваших тестов.
Изначально Николай представил доклад на конференции Heisenbug (можно посмотреть видеозапись), а теперь для Хабра мы сделали текстовую версию доклада.

Меня зовут Николай, я в Badoo занимаюсь в основном инфраструктурой iOS. Всем привет, сегодня я расскажу про масштабирование тестирования под iOS. Mobile — реализацию WebDriver для Windows Phone. До этого три года работал в компании 2ГИС, занимался разработкой и автоматизацией, в частности, написал Winium. И мне предложили интересные задачи по автоматизации iOS, про это я сегодня и расскажу. Меня взяли в Badoo работать над автоматизацией Windows Phone, но через некоторое время бизнес принял решение приостановить развитие этой платформы.

План таков: О чем мы поговорим?

  • Неформальная постановка проблемы, введение в используемые инструменты: как и почему.
  • Параллельное тестирование на iOS и как оно развивалось (в частности, по истории нашей компании, так как мы начали заниматься им еще в 2015-м).
  • Device server — это основная часть доклада. Наша новая модель распараллеливания тестов.
  • Результаты, которых мы достигли с помощью сервера.
  • Если у вас нет 1500 тестов, то device server вам может быть особо не нужен, но из него все равно можно вытащить интересные штуки, и речь пойдет о них. Их можно применить, если у вас 10-25 тестов, и это все равно даст либо ускорение, либо дополнительную стабильность.
  • И, наконец, подведение итогов.

Инструменты

Первое — это немного о том, кто чем пользуется. Наш стек немного нестандартный, потому что мы одновременно используем и Calabash, и WebDriverAgent (что дает нам скорость и бэкдоры Calabash при автоматизации нашего приложения и в тоже время полный доступ к системе и другим приложениям через WebDriverAgent). WebDriverAgent — это реализация WebDriver для iOS от Facebook, которая используется внутри Appium. А Calabash — встраиваемый сервер для автоматизации. Сами тесты мы пишем в человекочитаемом виде с помощью Cucumber. То есть у нас в компании псевдо-BDD. И из-за того, что мы использовали Cucumber и Calabash, мы унаследовали Ruby, весь код написан на нем. Кода очень много, и приходится продолжать писать на Ruby. Чтобы запускать тесты параллельно, мы используем parallel_cucumber, инструмент, написанный одним из моих коллег в Badoo.

Когда я начал готовить доклад, имелось 1200 тестов. Начнём с того, что у нас было. Пока я сюда доехал, тестов стало уже 1400. К моменту завершения их стало 1300. Они составляют 35-40 часов машинного времени на одном симуляторе. Это end-to-end тесты, а не unit-тесты и не интеграционные тесты. Расскажу, как они стали проходить за 30 минут. Проходили они раньше за полтора часа.

Разработчики создают примерно 10 запросов в основной репозиторий нашего приложения. У нас в компании есть рабочий процесс с ветками, ревью и запуском тестов на эти ветки. В итоге 30 тестовых запусков в день, как минимум. Но в нем есть также компоненты, которые шарятся с другими приложениями, поэтому иногда бывает и больше десяти. На той же инфраструктуре мы запускаем дополнительные проекты, такие как Liveshot, который снимает скриншоты приложения в основных пользовательских сценариях на всех языках, чтобы переводчики могли верифицировать корректность перевода, помещается ли он в экран и так далее. Поскольку разработчики пушат, затем понимают, что запушили с багами, перезаливают, и на все это запускается полная регрессия, просто потому что мы можем ее запускать. В результате выходит около полутора тысяч часов машинного времени на данный момент.

Чтобы это произошло, необходимо добиться от автоматизации быстрой и, главное, стабильной и надежной работы. В первую очередь мы хотим, чтобы разработчики и тестировщики доверяли автоматизации и полагались на неё для уменьшения ручной регрессии. А когда какие-то тесты упадут, он будет очень сильно не рад, что приходится возвращаться обратно, переключать внимание и что-то чинить. Если тесты проходят за полтора часа, разработчик устанет ждать результатов, он начнет делать другую задачку, его фокус переключится. Они постоянно падают, хотя багов в коде нет. Если же тесты ненадежные, то со временем люди их начинают воспринимать только как преграду. Соответственно, эти два пункта были раскрыты в настоящие требования: Это Flaky-тесты, какая-то помеха.

  • Тесты должны проходить за 30 минут или быстрее.
  • Они должны быть стабильными.
  • Они должны быть масштабируемыми, чтобы мы могли, добавив еще сотню тестов, укладываться в полчаса.
  • Инфраструктура должна легко поддерживаться и разрабатываться.
  • На симуляторах и физических девайсах все должно запускаться одинаково.

В основном мы гоняем тесты на симуляторах, а не на физических девайсах, потому что это быстрее, стабильнее и проще. Физические девайсы используются только для тестов, которые реально этого требуют. Например, камера, push-нотификации и подобное.

Ответ очень прост: мы удаляем две трети тестов! Как же удовлетворить эти требования и сделать всё хорошо? На этом у меня все. Это решение укладывается в 30 минут (потому что тестов осталась только треть), легко масштабируется (можно удалить больше тестов), и повышает надежность (потому что первым делом мы удаляем самые ненадежные тесты). Вопросы?

Если у вас очень много тестов, то надо пересмотреть их и понять, какие приносят реальную пользу. Но если серьезно, в каждой шутке есть доля правды. У нас же была другая задача, поэтому мы решили посмотреть, что можно сделать.

То есть подбирать соответствующие тесты на основе изменений файлов в приложении. Первый подход — фильтрация тестов на основе coverage или компонентов. Про это я рассказывать не буду, но это одна из задач, которую мы решаем в данный момент.

Вы берете конкретный тест, смотрите, какие шаги занимают в нем больше всего времени и можно ли их как-то оптимизировать. Другой вариант — это ускорение и стабилизация самих тестов. Если какие-то из них очень часто нестабильны, вы их правите, потому что это сокращает перезапуски тестов, и все проходит быстрее.

И, наконец, совершенно другая задача — параллелизация тестов, распределение их на большое количество симуляторов и обеспечение масштабируемой и стабильной инфраструктуры, чтобы было куда параллелизовать.

В этой статье поговорим в основном про последние два пункта и в конце, в tips & tricks, затронем пункт про скорость и стабилизацию.

Параллельное тестирование под iOS

Начнем с истории параллельного тестирования под iOS в целом и в Badoo в частности. Для начала простая арифметика, тут, правда, в формуле ошибка есть, если размерности сопоставить:

Тут приходит Сатиш, мой руководитель, и говорит, что ему нужно полчаса. Было 1300 тестов на один симулятор, получается 40 часов. В формуле появляется X: сколько симуляторов запустить, чтобы за полчаса все прошло. Надо что-то придумывать. И сразу возникает вопрос, куда эти 80 симуляторов засунуть, ведь они никуда не влезают. Ответ – 80 симуляторов.

А можно все придумать у себя и сделать хорошо. Есть несколько опций: можно пойти в облака типа SauceLabs, Xamarin или AWS Device Farm. Мы решили так, потому что облако с таким масштабом обойдется достаточно дорого, а также была ситуация, когда вышел iOS 10 и Appium почти месяц выпускал для него поддержку. Учитывая, что эта статья существует, мы сделали все у себя хорошо. Кроме того, все облака закрытые, и вы не сможете на них повлиять. Это значит, что в SauceLabs мы месяц не смогли бы автоматически тестировать iOS 10, что нас никак не устраивало.

Начали где-то в 2015 году, тогда Xcode не умел запускать более одного симулятора. Итак, мы решили все делать in-house. Если у вас много пользователей, то симуляторов можно запускать сколько угодно. Как выяснилось, он не может запускать более одного симулятора под одним пользователем на одной машине. Мой коллега Тим Баверсток придумал модель, на которой мы жили достаточно долго.

На картинке изображены две машины под два пользователя. Есть агент (TeamCity, Jenkins Node и подобное), на нем запускается parallel_cucumber, который просто идет на удаленные машины по ssh. А тесты уже запускают симулятор локально на текущем рабочем столе. Все нужные файлы типа тестов копируются и запускаются на удаленных машинах по ssh. И настроить ssh-демон, чтобы у него был доступ к процессам на рабочем столе. Чтобы это все работало, на каждую машину нужно было предварительно сходить, создать, например, 5 пользователей, если вы хотите 5 симуляторов, сделать одному пользователю автоматический логин, открытие screensharing для остальных, чтобы у них всегда был рабочий стол. Но на этой картинке есть несколько проблем. Таким нехитрым образом мы начали запускать тесты параллельно. То есть их надо запускать всегда на маках, они съедают ресурсы, которые могли быть потрачены на запуск симуляторов. Во-первых, тесты управляют симулятором, они находятся там же, где и сам симулятор. Другой момент заключается в том, что надо сходить на каждую машину, настроить пользователей. В итоге у вас меньше симуляторов на машине и они дороже стоят. Если есть пять пользователей, они поднимают много процессов, и в какой-то момент в системе закончатся дескрипторы. А затем вы просто уткнетесь в глобальный ulimit. Вы достигнете лимита, тест попытается открыть файл, упадет, и с этого момента все начнет проваливаться.

Посмотрели доклад Лоренса Ломакса из Facebook — они представили fbsimctl, и частично рассказали, как работает инфраструктура в Facebook. В 2016-2017 годах мы решили перейти к немного другой модели. Картинка не сильно отличается от предыдущей — мы просто избавились от пользователей, но это большой шаг вперед, поскольку теперь есть только один рабочий стол, меньше запускается процессов, симуляторы стали дешевле. Был еще доклад Виктора Короневича про эту модель. С этой моделью мы жили очень долго, до середины октября 2017 года, когда мы начали переходить на наш сервер удаленных устройств. На этой картинке три симулятора, а не два, так как освободились ресурсы для запуска дополнительного.

Слева ящик с макбуками. Так выглядело железо. Запускать тесты на макбуках, которые мы вставили в железный ящик, было не самой хорошей идеей, потому что где-то к обеду они начинали перегреваться, так как из них плохо уходит тепло, когда они так лежат на поверхности. Почему мы все тесты запускали на них — это отдельная большая история. Тесты становились нестабильными, особенно часто стали падать симуляторы при загрузке.

Решили мы это просто: поставили ноутбуки «палаточками», площадь обдува увеличилась и неожиданно повысилась стабильность инфраструктуры.

Так что иногда надо не софтом заниматься, а ходить поворачивать ноутбуки.

Это железная часть, и она еще хорошая была. Но если вы посмотрите на эту картинку, тут какое-то месиво из проводов, переходников и вообще жесть. В софте же творилось полнейшее переплетение тестов с инфраструктурой, и дальше так жить было невозможно.

Мы определили следующие проблемы:

  • То, что тесты были тесно связаны с инфраструктурой, запускали симуляторы и управляли всем их жизненным циклом.
  • Это приводило к сложности масштабирования, поскольку добавление новой ноды подразумевало ее настройку и для тестов, и для запуска симуляторов. Например, если вы хотели обновить Xcode, приходилось добавлять workaround прямо в тесты, потому что они гоняются на разных версиях Xcode. Появляются какие-то кучи if для запуска симулятора.
  • Тесты привязаны к машине, где находится симулятор, и это обходится в копеечку, так как их приходится запускать на маках вместо *nix, которые дешевле.
  • И всегда было очень просто покопаться внутри симулятора. В некоторых тестах мы ходили в файловую систему симулятора, удаляли там какие-то файлы или изменяли их, и все было хорошо, пока это не делалось тремя разными способами в трех различных тестах, а потом неожиданно четвертый начинал падать, если ему не повезло запуститься после тех трех.
  • И последний момент — ресурсы никак не шарились. Было, например, четыре TeamCity agent, к каждому было подключено пять машин, и запускать тесты можно было только на своих пяти машинах. Не было какой-то централизованной системы управления ресурсами, из-за этого, когда приходит всего одна задача, она шла на пяти машинах, а все остальные 15 простаивали. Из-за этого билды шли очень долго.

Новая модель

Мы решили перейти к красивой новой модели.

Эта машина теперь может находиться на *nix или даже на Windows, если вам так захотелось. Убрали все тесты на одну машину, где TeamCity agent. Все симуляторы и физические девайсы будут находиться где-то там, а тесты будут запускаться здесь, запрашивать девайс по HTTP и дальше с ним работать. Они будут общаться по HTTP с какой-то вещью, которую мы назовем device server. Схема очень простая, всего два элемента на диаграмме.

Но теперь это никого не волнует, потому что ребята, пишущие тесты, в этой схеме находятся наверху, и у них есть какой-то определенный интерфейс для работы с локальным или удаленным симулятором, поэтому у них все хорошо. В реальности, конечно, позади сервера остался ssh и прочее. Приходится с этим жить. А я теперь работаю ниже, и у меня все как было.

Что это дает?

  • Во-первых, разделение ответственности. В какой-то момент при автоматизации тестирования надо рассматривать ее как обычную разработку. В ней работают те же принципы и подходы, которые используют разработчики.
  • Получился строго определенный интерфейс: напрямую делать что-то с симулятором нельзя, для этого надо открыть тикет в device server, а мы придумаем, как это сделать оптимально, не ломая другие тесты.
  • Тестовая среда стала дешевле, потому что мы ее подняли в *nix, которые значительно дешевле маков в обслуживании.
  • И появился шеринг ресурсов, потому что есть единая прослойка, с которой все общаются, она может спланировать распределение машин, находящихся за ней, т.е. разделение ресурсов между агентами.

Слева условные единицы времени, допустим, десятки минут. Выше изображено, как было раньше. Через 20 минут приходит еще один, и занимает то же время. Есть два агента, к каждому подключено 7 симуляторов, в момент времени 0 приходит билд и занимает 40 минут. Но и там, и там есть серые квадратики. Все вроде замечательно. Они означают, что мы потеряли деньги, так как не использовали имевшиеся ресурсы.

Для этого ничего не надо было делать. Можно сделать так: приходит первый билд и видит все свободные симуляторы, распределяется, и тесты ускоряются в два раза. Хотя иногда такое происходит, и начинаются «шашечки», «пирамиды» и тому подобное. В реальности такое часто происходит, потому что разработчики редко пушат свои бранчи в одну и ту же минуту. Тем не менее, в большинстве случаев бесплатное ускорение в два раза можно получить, просто поставив централизованную систему управления всеми ресурсами.

Другие причины перейти к этому:

  • Black boxing, то есть теперь device server — это черный ящик. Когда вы пишете тесты, вы думаете только о тестах и считаете, что этот черный ящик всегда будет работать. Если не работает, вы просто идете и стучите тому, кто его должен делать, то есть мне. А мне приходится его чинить. Не только мне на самом деле, всей инфраструктурой занимается несколько человек.
  • Нельзя никак испортить внутренности симулятора.
  • Не надо ставить миллион утилит на машину, чтобы все запускалось — вы просто ставите одну утилиту, скрывающую всю работу в device server.
  • Стало легче обновлять инфраструктуру, о чем мы поговорим где-то в конце.

Резонный вопрос: почему не Selenium Grid? Во-первых, у нас существовало много legacy-кода, 1500 тестов, 130 тысяч строк кода для разных платформ. И все это управлялось parallel_cucumber, который создавал жизненный цикл симулятора вне теста. То есть был специальная система, которая загружала симулятор, ждала его полной готовности и отдавала его в тест. Чтобы все не переписывать, мы решили попробовать не использовать Selenium Grid.

Основная часть тестов на Calabash, а WebDriver только вспомогательно. Еще у нас много нестандартных действий, и мы очень редко используем WebDriver. То есть мы не используем Selenium в большинстве случаев.

Потому что весь проект начался просто с идеи, которую решили проверить, реализовали за месяц, все завелось, и она стала основным решением у нас в компании. И, конечно, мы хотели, чтобы все было гибко, легко прототипировалось. Тесты оказались на Ruby, а сервер на Kotlin. Кстати, вначале мы написали на Ruby, а потом переписали device server на Kotlin.

Device server

Теперь подробнее про сам device server, как он работает. Когда мы только начали исследовать этот вопрос, у нас использовались следующие инструменты:

  • xcrun simctl и fbsimctl — утилиты командной строки для управления симуляторами (первая официально от Apple, вторая от Facebook, она немного удобнее в использовании)
  • WebDriverAgent, также от Facebook, для того чтобы драйвить приложения вне процесса, когда приходит push-нотификация или что-то подобное
  • ideviceinstaller, чтобы ставить приложение на физические девайсы и потом как-то автоматизировать на устройстве.

К моменту, когда начали писать device server, мы поисследовали. Оказывается, fbsimctl к тому моменту уже умел делать все, что умели xcrun simctl и ideviceinstaller, так что мы их просто выкинули, оставили только fbsimctl и WebDriverAgent. Это уже какое-то упрощение. Дальше мы подумали: зачем нам что-то писать, наверняка у Facebook уже все готово. И действительно, fbsimctl может работать как сервер. Можно так его запустить:

Это поднимет симулятор и запустит сервер, который будет слушать команды.

Когда вы остановите сервер, он автоматически завершит работу симулятора.

Например, с помощью curl отправить list, и он выведет полную информацию об этом устройстве: Какие команды можно отправлять?

У них реализована огромная куча команд, которая позволяет делать с симулятором что угодно. Причем это все в JSON, то есть легко парсится из кода, и с этим легко работать.

Команда open позволяет открывать deep links в приложении. Например, approve — это дать разрешение на использование камеры, локации и нотификации. Но оказалось, что там не хватает таких команд: Казалось бы, можно ничего не писать, а взять fbsimctl.

То есть кто-то должен заранее сходить на машину и поднять симулятор. Тут легко догадаться, что без них вы не сможете запустить новый симулятор. Без этих команд сервер полностью удаленно использовать не получается, поэтому мы решили составить полный список дополнительных требований, которые нам нужны. И самое важное: вы не сможете запустить тесты на симуляторе.

  • Первое — это создание и загрузка симуляторов по требованию. То есть liveshot’ы могут в любой момент попросить iPhone X, а потом iPhone 5S, но при этом большинство тестов будет запускаться на iPhone 6s. Мы должны уметь по требованию создавать нужное количество симуляторов каждого типа.
  • Еще мы должны как-то уметь запускать WebDriverAgent либо другие XCUI-тесты на симуляторах или физических девайсах, чтобы драйвить саму автоматизацию.
  • И мы хотели полностью скрыть удовлетворение требований. Если у вас тесты хотят что-то протестировать на iOS 8 для обратной совместимости, то они не должны знать, на какую машину идти за этим устройством. Они просто запрашивают у device server iOS 8, и если такая машина есть, то он сам ее найдет, каким-то образом подготовит и вернет устройство с этой машины. Этого не было в fbsimctl.
  • Наконец, это различные дополнительные действия вроде удаления cookies в тестах, что позволяет сэкономить целую минуту в каждом тесте, и другие различные хитрости, о которых мы поговорим в самом конце.
  • И последний момент — это пулинг симуляторов. У нас была идея, что раз device server теперь живет отдельно от тестов, в нем можно заранее запускать все симуляторы одновременно, а когда придут тесты, симулятор уже будет готов моментально начать работать, и мы так сэкономим время. В результате мы это так и не сделали, потому что загрузка симуляторов и без того оказалось очень быстрой. И об этом тоже будет в самом конце, вот такие спойлеры.

Здесь многоточия — это различные фейсбучные методы, которые мы просто продублировали. На картинке просто пример интерфейса, мы написали какую-то обертку, клиент для работы с этим удаленным девайсом. Все остальное — собственные методы, например, быстрый сброс девайса, очистка кук и получение различной диагностики.

Все позади него скрыто от тестов, там находятся какие-то бэкграунд-потоки для очистки диска и выполнения других важных действий и fbsimctl с WebDriverAgent. Вся схема выглядит примерно так: есть Test Runner, который будет запускать тесты и готовить среду; есть Device Provider, клиент к Device Server, чтобы просить у него девайсы; Remote Device — это обертка над удаленным девайсом; Device Server — сам девайс-сервер.

Из тестов или из Test Runner запрашиваем девайс с определенным capability, например iPhone 6. Как это все работает? По этому токену вы можете ходить на Device Server и просить девайс. Запрос уходит в Device Provider, а он пересылает device server, который находит подходящую машину, запускает на ней бэкграунд-поток для подготовки симулятора и тут же возвращает в тесты некий референс, токен, обещание, что в будущем устройство будет создано и загружено. Этот токен превращаем в инстанс класса RemoteDevice и с ним можно будет работать уже в тестах.

Сейчас мы, например, грузим симуляторы в headless-режиме. Все это происходит почти моментально, а в фоне параллельно начинается загрузка симулятора с помощью fbsimctl. Они как-то грузятся, вы даже ничего не увидите. Если кто помнит первую картинку с железом, на ней можно было увидеть много окон симуляторов, раньше мы их грузили не в headless-моде. Просто ждем, пока симулятор полностью загрузится, например, в syslog появляется запись о SpringBoard и прочие эвристики для определения готовности симулятора.

Параллельно в это время запускается цикл, ожидающий состояние «ready» у девайса. Как только он загрузился, запускаем XCTest, который на самом деле поднимет WebDriverAgent, и мы начнем у него спрашивать healthCheck, потому что WebDriverAgent иногда не поднимается, особенно если система очень сильно загружена. Как только девайс полностью загружен и стал готов для тестирования, вы выходите из цикла. Это на самом деле те же healthCheck.

Тут все элементарно. Теперь вы можете поставить на него приложение просто отправив запрос к fbsimctl. После этого можно запускать тесты. Можно еще создать драйвер, запрос проксируется к WebDriverAgent, и создает сессию.

В конце надо освободить девайс (release), он завершается, у него почистятся все ресурсы, сбрасывается кэш и тому подобное. Тесты — это такая маленькая часть всей этой схемы, в них вы можете продолжать общаться с device server, чтобы совершать действия вроде удаления кук, получения видео, запуска записи и так далее. Понятное дело, что device server сам это делает, потому что иногда тесты падают вместе с Test Runner и явным образом не освобождают девайсы. На самом деле освобождать устройство необязательно. Эта схема значительно упрощена, в ней нет многих пунктов и бэкграунд-работ, которые мы выполняем, чтобы сервер мог работать целый месяц без каких-либо проблем и перезагрузок.

Результаты и следующие шаги

Самая интересная часть — это результаты. Они простые. От 30 машин перешли к 60. Это виртуальные машины, не физические машины. Самое главное, что мы сократили время с полутора часов до 30 минут. И тут возникает вопрос: если машин стало вдвое больше, то почему время уменьшилось в три раза?

Я показывал картинку про шеринг ресурсов — это первая причина. На самом деле все просто. Он дал дополнительный подъем в скорости в большинстве случаев, когда разработчики в разное время запушили задачи.

После этого мы наконец поняли, как все работает, и смогли оптимизировать каждую из частей по отдельности и добавить еще небольшое ускорение в систему. Второй момент заключается в разнесении тестов и инфраструктуры. Separation of Concerns — это очень важная идея, потому что когда все переплетено, целиком охватить систему становится невозможно.

Например, когда я только пришел в компанию, мы проводили обновление на Xcode 9, который занял больше недели с тем небольшим количеством машин. Стало проще делать обновления. 2, и это прошло буквально за полтора дня, причем большая часть времени — копирование файлов. Последний раз мы обновлялись на Xcode 9. Мы не участвовали, оно само там что-то делало.

Теперь все это выкинуто и работает где-то на *nix, в Docker-контейнерах. Мы значительно упростили Test Runner, в котором были rsync, ssh и прочая логика.

Но уже сейчас вы можете взять device server, разрешить на всех машинах ssh, и тесты реально пойдут на них без проблем. Следующие шаги: подготовка device server к опенсорсу (уже после доклада он был размещён на GitHub), и подумываем об удалении ssh, потому что это требует дополнительной настройки на машинах и в большинстве случаев приводит к усложнению логики и поддержки всей системы.

Tips & tricks

Теперь самое главное — всякие хитрости и просто полезные вещи, которые мы нашли, создавая device server и эту инфраструктуру.

Как вы помните, у нас были MacBook Pro, все тесты запускались на лэптопах. Первое — это самое простое. Теперь мы их запускаем на Mac Pro.

Это фактически топовые версии каждого из устройств. Здесь приведены две конфигурации. Если вы пытаетесь одновременно загрузить больше, симуляторы начинают проваливаться из-за того, что они сильно нагружают процессор, у них есть взаимные локи и прочее. На макбуке мы стабильно могли запускать 6 симуляторов параллельно. Умножаем на три — у вас влезает примерно 18 симуляторов. На Mac Pro можно запустить 18 — это очень легко посчитать, потому что вместо 4 стало 12 ядер. Хотя там будет хитрость с этими 18 симуляторами, не все так просто. На самом деле можно попробовать запустить чуть больше, но их надо как-то разнести во времени, нельзя, например, в одну минуту запускать.

Я не помню, сколько это в рублях, но и так понятно, что стоят они много. А это их цена. Это уже около £70 экономии на каждом симуляторе. Стоимость каждого симулятора у MacBook Pro обходится почти в £400, а у Mac Pro почти £330.

И надо было покупать переходник, чтобы подключить Ethernet, потому что столько устройств рядом в железной коробке на Wi-Fi фактически не работают, он становится нестабилен. Кроме того, эти макбуки приходилось ставить определенным образом, у них были зарядки на магнитах, которые приходилось приклеивать на скотч, потому что иногда они отваливались. Но, если вам не нужна эта супер-параллелизация, у вас всего 20 тестов и достаточно 5 симуляторов, на самом деле проще купить MacBook, потому что его можно найти в любом магазине, а Mac Pro в топовой комплектации придется заказывать и ждать. Переходник тоже стоит около £30, когда вы поделите на 6, то у вас получится еще по £5 на каждое устройство. Еще Mac Pro можно купить с маленькой памятью, а потом апгрейдить самим, сэкономив еще больше. Нам они обошлись, кстати, чуть дешевле, потому что мы их брали оптом и была какая-то скидка.

Нам пришлось разбить их на три виртуальных машины, поставить туда ESXi. Но с Mac Pro есть одна хитрость. Он сам является хостом, поэтому мы можем запускать три виртуалки. Это bare metal-виртуализация, то есть гипервизор, который ставится на голую машину, а не на хостовую систему. Пришлось разбивать, потому что в CoreSimulator, в основном сервисе, управляющем симуляторами, оказались внутренние локи, и одновременно больше 6 симуляторов просто не грузится, они начинают ждать чего-то в очереди, и общее время загрузки 18 симуляторов становится неприемлемым. А если вы будете ставить на macOS какую-то обычную виртуализацию, например Parallels, то вы сможете запускать только 2 виртуалки из-за лицензионных ограничений Apple. Кстати, ESXi стоит £0, это всегда приятно, когда что-то ничего не стоит, а работает хорошо.

Отчасти потому, что мы ускорили сброс симулятора. Почему мы не стали делать pooling? Самое простое решение — это завершить (shutdown) симулятора, явно стереть (erase) и загрузить (boot). Допустим, тест упал, вы хотите полностью почистить симулятор, чтобы следующий не падал из-за оставшихся непонятных файлов в файловой системе.

А еще полгода или год назад занимало почти минуту. Очень просто, одна строка, но занимает 18 секунд. Загрузить симулятор и скопировать его рабочие директории в папочку backup. Спасибо Apple, что они оптимизировали это дело, но можно сделать хитрее. И тогда вы выключаете симулятор, удаляете рабочую директорию и копируете бэкап, запускаете симулятор.

При этом ничего сложного делать не пришлось, то есть в Ruby-коде это занимает буквально две строки. Получается 8 секунд: загрузка ускорилась более чем в два раза. На картинке я привожу пример на баше, чтобы легко было переводить в другие языки.

Есть приложение Bumble, оно похоже на Badoo, но с несколько другой концепцией, гораздо более интересной. Следующий прием. Во всех наших тестах, так как мы каждый раз используем нового пользователя из пула, приходилось разлогиниваться из предыдущего. Там надо логиниться через Facebook. Вроде бы хорошо, но занимает почти минуту в каждом тесте. Для этого мы с помощью WebDriverAgent открывали Safari, заходили на Facebook, жали Sign out. Сто лишних минут. А тестов сто.

Внезапно пачка тестов упадет, и все будут крайне недовольны. Кроме того, Facebook любит иногда делать A/B-тесты, поэтому они могут поменять локаторы, текст на кнопках. Поэтому мы через fbsimctl делаем list_apps, который находит все приложения.

Находим MobileSafari:

А там есть путь до DataContainer, а в нем есть бинарный файл с куками:

Тесты стали проходить на 100 минут быстрее, стали стабильнее, потому что они не могут упасть из-за Facebook. Мы просто удаляем его — это занимает 20 мс. Можно находить места для оптимизации, легко минус 100 минут, ничего делать не надо. Так что параллелизация иногда не нужна. В коде это две строки.

Следующее: как мы готовим хостовые машины для запуска симуляторов.

У симулятора есть привычка при вводе текста в симуляторе подключать хардварную клавиатуру на компьютере, а виртуальную полностью скрывать. С первым примером многие, кто запускали Appium, знакомы — это отключение хардварной клавиатуры. Соответственно, после локального дебага тестов на ввод остальные тесты могут начать проваливаться из-за отсутствия клавиатуры. А Appium пользуется виртуальной клавиатурой для ввода текста. Этой командой можно отключить хардварную клавиатуру, и мы делаем это перед подъемом каждой ноды для тестов.

И очень часто надо запускать тесты так, чтобы изначально она была отключена. Следующий пункт более актуален для нас, потому что приложение завязано на геолокацию. Почему так? Можно в LocationMode задать 3101. Теперь это просто магическая константа в коде, на которую мы все молимся и надеемся, что она не сломается. Раньше была статья в документации Apple, но потом они ее зачем-то удалили. С другой стороны, мы легко об этом узнаем, потому что все будут в Сан-Франциско. Потому что как только она сломается, все пользователи окажутся в Сан-Франциско, потому что fbsimctl при загрузке ставит такую локацию.

При запуске автотестов она не нужна. Следующее — это отключение Chrome, рамочки вокруг симулятора, которая имеет различные кнопки. Теперь мы так не делаем, потому что у нас все headless. Раньше ее отключение позволяло разместить больше симуляторов слева направо, чтобы видеть, как все идет параллельно. Если же это необходимо, то можно стримить поток с нужного симулятора. Сколько не заходи на машины, сами симуляторы видны не будут.

Из них упомяну только SlowMotionAnimation, потому что у меня был очень интересный второй или третий день на работе. Есть еще набор разных опций, которые можно включать-выключать. Не находили элементы в инспекторе, хотя он был. Я запустил тесты, а они все начали падать по таймаутам. В этот момент симулятор стал активным и перехватил команду. Оказалось, что я в это время запустил Chrome, нажал cmd+T, чтобы открыть новую вкладку. Эту опцию тоже надо всегда автоматически отключать, если хотите запускать тесты на машинах, к которым имеют доступ люди, потому что они могут случайно сломать тесты замедлением анимации. А для него cmd+T — это замедление всех анимаций в 10 раз для отладки анимации.

60 виртуальных хостов (на самом деле 64 + 6 TeamCity agents) никто вручную раскатывать не хочет. Наверное, самое интересное для меня, так как я это не так давно делал, — управление всей этой инфраструктурой. Дальше мы взяли Ansible, написали playbooks, чтобы раскатывать везде fbsimctl нужной версии, Xcode и деплоить конфиги для самого device server. Мы нашли утилиту xcversion — сейчас это часть fastlane, gem на Ruby, который можно использовать как утилиту командной строки: он частично автоматизирует установку Xcode. Когда мы переходим на iOS 11, то оставляем iOS 10. И еще Ansible для удаления и апдейта симуляторов. Иначе они занимают много места на диске. Но когда команда тестирования говорит, что полностью отказывается от автоматического тестирования на iOS 10, мы просто проходимся Ansible и подчищаем старые симуляторы.

Если вы возьмете просто xcversion и будете вызывать его на каждой из 60 машин, это займет много времени, так как он ходит на сайт Apple и качает все образы. Как это работает? В кэш будет скачан установочный пакет. Чтобы обновить машины, которые есть в парке, надо выбрать одну рабочую машину, на ней запустить xcversion install нужную версию Xcode, но при этом ничего не ставить и ничего не удалять. Установочный пакет кладется в ~/Library/Caches/XcodeInstall. То же самое можно сделать для любой версии симуляторов. Я привык к Python, поэтому запускаю на машинах питоновский HTTP-сервер. Дальше вы грузите все с Ceph, а если его нет, запускаете какой-нибудь web-сервер в этой директории.

Он скачает с указанной машины xip (если локальная сеть быстрая, то это произойдет фактически моментально), распакует пакет, подтвердит лицензию — в общем, сделает все за вас. Теперь на любой другой машине разработчика или тестировщика можно сделать xcversion install и указать ссылку до поднятого сервера. К сожалению, с симуляторами так удобно не сделали, поэтому приходится делать curl или wget, качать пакет с того сервера на вашу локальную машину в ту же директорию, запускать xcversion simulators --install. Останется полностью рабочий Xcode, в котором можно будет запускать симуляторы и тесты. Основное время заняло сетевое копирование файлов. Мы поместили эти вызовы внутрь Ansible-скриптов и обновили 60 машин за день. Мы перезапустили Ansible два или три раза, чтобы догнать отсутствовавшие во время переезда машины. Помимо этого, мы в этот момент переезжали, то есть часть машин отключалась.

По первой части: мне кажется, важны приоритеты. Небольшое подведение итогов. Если вы погонитесь только за скоростью, начнете все параллелизировать, то тесты будут работать быстро, но на них никто никогда не будет смотреть, просто все будут перезапускать, пока все внезапно не пройдет. То есть в первую очередь у вас должна быть стабильность и надежность тестов, а потом уже скорость. Или вообще забивать на тесты и пушить в мастер.

Если сейчас ваша инфраструктура тесно связана с тестами и планируется масштабирование, то это хороший момент, чтобы сперва разделить, а потом уже масштабировать. Следующий момент: автоматизация — это та же самая разработка, поэтому можно просто взять паттерны, которые уже придумали за нас, и использовать их.

На самом деле очень часто надо не добавлять, а внимательно проанализировать код и парой строк все оптимизировать, как в примере с cookies. И последний пункт: если стоит задача ускорить тесты, то первым в голову приходит добавить еще симуляторов, чтобы на какой-то фактор стало быстрее. По деньгам и ресурсам это обойдется значительно дороже. Это лучше параллелизации, потому что двумя строками кода было сэкономлено 100 минут, а для параллелизации придется написать много кода и после поддерживать железную часть инфраструктуры.

Тех, кого заинтересовал этот доклад с конференции Heisenbug, может также заинтересовать следующий Heisenbug: он пройдёт в Москве 6-7 декабря, и на сайте конференции уже есть описания ряда докладов (и, кстати, приём заявок на доклады ещё открыт).

Показать больше

Похожие публикации

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

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

Кнопка «Наверх»