Тестовые проекты без боли. Доклад Яндекса
Мы в команде Яндекс.Карт для iOS создаем тестовые проекты с помощью маленького плагина для CocoaPods и нескольких классов утилит. Создание проекта происходит быстро и надежно. Но может, мы слишком заморачиваемся и собрать проект вручную с нужными настройками и зависимостями не так сложно? В докладе я пошел от противного: сначала разобрал ручной процесс, потом наш.
— Сначала маленькая предыстория. Яндекс.Карты собираются больше минуты. На моем компьютере сборка приложения занимает чуть больше трех минут. Мы разрабатываем в тестовых проектах, чтобы тратить меньше времени на каждую сборку. У нас достаточно прокачанная модульность, и для каждого модуля мы делаем тестовый проект. В этом тестовом проекте ведется разработка фичи.
Создание одного тестового проекта сейчас занимает от 15 минут до часа. Это связано с тем, что проект надо настроить. То есть надо настроить signing, чтобы мы могли разрабатывать на тех же девайсах, как привыкли. Надо настроить необходимые библиотеки. В процессе разработки самой фичи мы постоянно возвращаемся и допиливаем тестовый проект, чтобы он удовлетворял нашим запросам.
Зачем нужны тестовые проекты? Чтобы экономить время на сборке. Чем меньше проект, тем быстрее он собирается, тем чаще мы сможем запускать наше приложение на девайсе и тем комфортнее нам будет разрабатывать.
Что должно быть в тестовом проекте? Мы должны иметь возможность запускать, дебажить наше приложение на тех же девайсах, на которых мы привыкли запускать и дебажить наше основное приложение.
Проект должен быть настроен максимально похоже на основной проект, чтобы в процессе переноса фичи из тестового проекта в основной мы не получили сюрприза. Или хотя бы свели количество получаемых сюрпризов к минимуму.
Также нам необходимо минимальное окружение в тестовом проекте, чтобы мы не тратили время на создание UIWindows, настройку библиотек, опрокидывание вызова открытия приложения в библиотеку, которая занимается авторизацией, и т. д.
CocoaPods app_spec
Весь мой сегодняшний рассказ будет построен вокруг фичи CocoaPods, которая называется app_spec.
App_spec — это как обычная subspec, только из нее генерируется не фреймворк, который можно подключить, а приложение, которое можно запустить на девайсе.
И из коробки CocoaPods позволяет нам пропихнуть в app_spec значение Info.plist и xcconfig. Это нам уже позволяет достаточно хорошо настроить наш проект.
Но если мы будем в каждой pods_spec писать огромные словарики, со значениями для Info.plist и xcconfig, то у нас сами podspec разрастутся и модифицировать их будет неудобно. Если нам понадобится что-то поменять, придется лезть в каждый podspec-файл и там править ручками что-то. В любом случае это неудобно.
Чтобы избавиться от этого, мы сделали очень простой локальный плагинчик для CocoaPods. Он даже целиком влезает в один слайд. И он делает одну-единственную вещь: дает нам разрешение DSL CocoaPods, который создает app_spec с уже забитыми значениями в Info.plist и xcconfig. Это уже нам позволяет настроить signing, настроить те важные штуки, которые мы хотим видеть настроенными в нашем тестовом проекте.
Вопрос: как передать путь до entitlements, точнее, где хранить entitlements так, чтобы мы могли путь захардкодить в плагинчики.
Тут есть два пути. Либо указывать путь для какого-то, просто лежащего в нашем репозитории файла, например, до entitlements основного проекта. Либо хранить в плагине специальный файл entitlements для тестовых проектов. И с помощью того же плагина копировать его в наш developmentpod.
Мы выбрали первый путь, и тут у нас возникли некоторые сложности. Дело в том, что у нас в репозитории developmentpods лежат в разных подпапочках. Значит, мы не можем для них указать один относительный путь от developmentpod к файлу entitlements.
Тут важно подчеркнуть, что обязательно нужно указывать относительный путь, потому что этот путь остается в pods_spec. Значит, он будет участвовать в подсчете чек-суммы, которая хранится в Podfile.lock. И если там будет абсолютный путь, то это будет влиять на чек-сумму. Значит, чек-сумма будет меняться в зависимости от того, из какой папки вызвать $ pod install, где у вас лежит репозиторий, на какой машине вы запускаете $ pod install. В общем, это приводит к конфликтам.
Теперь я покажу еще одно небольшое расширение того же самого плагинчика, который позволяет нам посчитать относительный путь до этих entitlements.
Мы знаем относительный путь от нашего плагина до файла с entitlements, если они лежат в одном репозитории. И также во время запуска $ pod install мы знаем путь до нашего developmentpod. Вся магия, которая есть в этом коде, — выделенная строчка, где мы просто считаем относительный путь, зная эти два абсолютных пути.
Итак, что мы получили, уже имея подобным образом настроенную app_spec? Мы можем запускать наш тестовый проект на тех же девайсах сразу. Мы получили огромное количество настроек, в том числе entitlements, которое нам необходимо.
Окружение
Следующее, о чем я хочу поговорить, — окружение, то самое минимально необходимое окружение, которое нам нужно, чтобы сразу начать писать код.
В нашем случае нам нужен MapKit, уже настроенный, с переданными ключами, с прокидыванием вызовов в AccountManager. Это наша библиотека для авторизации. Ей тоже нужно прокидывать системные вызовы, ей тоже нужно передавать ключи, в том числе передавать открытие приложения по ссылке.
Также нам нужен стартовый экран с картой и navigation stack, чтобы мы могли использовать тот же самый navigation stack, что и в Картах, и проще переносить фичи из тестового проекта в основной. И точка входа, чтобы мы, не думая, сразу могли начать писать полезный код.
В вашем случае это может быть, например, настройка Facebook SDK, к которому тоже нужно передать ключи; ваши библиотеки для логина, в которые почти наверняка понадобятся прокинуть открытие приложения по ссылке. Какой-то стартовый Windows стартовым Controller, и та же самая точка входа для запуска основного сценария, который вы хотите разрабатывать в вашем тестовом проекте.
Как мы это сделали? Сделав один-единственный шаблон AppDelegate. Там мы инициализируем все необходимое окружение, библиотеки. Прокидываем все нужные вызовы, передаем нужные ключи. Создавая тестовый проект, нам остается создать новый AppDelegate для тестового проекта и отнаследоваться от шаблонного.
После этого мы переопределяем один-единственный метод, который вызывается после того, как шаблонный AppDelegate сделал все необходимое. И там мы уже имеем стартовый ViewController и поверх него можем запускать наш сценарий, который мы хотим отлаживать в тестовом проекте.
Было/стало
Как можно создать тестовый проект в лоб, и как его можно создать с помощью того способа, который я сегодня описал?
Создание тестового проекта в лоб:
— Мы идем в XCode, создаем новый проект (File > New Project).
— Вводим имя, желательно без ошибок, подписываем Bundle ID. Настраиваем этот проект.
— Меняем версию Swift, меняем target version. Пытаемся это все запустить на девайсе, правим signing и так далее. Это достаточно муторный процесс.
— Дальше мы создаем podfile, подключаем туда зависимости, устанавливаем поды.
— После этого мы идем и начинаем очень муторно и нежно настраивать наш AppDelegate, перенося по кусочкам инициализации тех библиотек, которые нам нужны.
— Создаем стартовый UIWindows с нашим ViewController
И только после этого мы можем начинать полезный код. Достаточно неприятная процедура, согласитесь.
Как мы можем создать тестовый проект, используя те наработки, которые я описал в сегодняшнем докладе?
— Мы создаем sample_app_spec, используя то расширение, для CocoaPods, с которого мы начали.
— Создаем AppDelegate, и наследуем его от шаблонного AppDelegate.
После этого мы можем начинать писать полезный код.
Создание тестового проекта таким способом проходит намного быстрее и надежнее. Мы точно не ошибемся в настройках signing, точно не ошибемся в настройках проекта. К нам сразу подъезжает все окружение, необходимое для начала работы.
А можно проще?
Вы можете задать вопрос — можно ли это сделать проще? Конечно.
Я выделил три уровня сложности. Первый — это то, о чем я сегодня рассказывал, все и сразу, когда мы делаем прокаченный плагин для CocoaPods, который умеет чинить пути до entitlements, и шаблонный AppDelegate.
Второй способ идеален по соотношению усилий к профиту. Он содержит только тот плагин для CocoaPods, который был первым в сегодняшнем рассказе. То есть просто подставляет значения в Info.plist и xcconfig. Это уже значительно облегчает жизнь.
Если вам не нужно, чтобы в ваших тестовых приложениях был файл entitlements, если у вас тестовые приложения достаточно сильно отличаются друг от друга и им не нужны общие компоненты, этот способ очень хорошо вам подойдет.
Третий способ самый простой, не требует никакой подготовки. Просто начните использовать app_spec. Проект, который генерируется из app_spec, создается в том же Workspace, что и ваш основной проект. Поэтому он уже наследует, например, Swift, target version и все настройки, которые у вас есть именно в Workspace. Вам уже будет значительно проще начать с ним работать. На этом все, спасибо за внимание.Сегодня я расскажу, как создавать тестовые проекты без боли, и о том, как мы создаем тестовые проекты в Яндекс.Картах.
— Сначала маленькая предыстория. Яндекс.Карты собираются больше минуты. На моем компьютере сборка приложения занимает чуть больше трех минут. Мы разрабатываем в тестовых проектах, чтобы тратить меньше времени на каждую сборку. У нас достаточно прокачанная модульность, и для каждого модуля мы делаем тестовый проект. В этом тестовом проекте ведется разработка фичи.
Создание одного тестового проекта сейчас занимает от 15 минут до часа. Это связано с тем, что проект надо настроить. То есть надо настроить signing, чтобы мы могли разрабатывать на тех же девайсах, как привыкли. Надо настроить необходимые библиотеки. В процессе разработки самой фичи мы постоянно возвращаемся и допиливаем тестовый проект, чтобы он удовлетворял нашим запросам.
Зачем нужны тестовые проекты? Чтобы экономить время на сборке. Чем меньше проект, тем быстрее он собирается, тем чаще мы сможем запускать наше приложение на девайсе и тем комфортнее нам будет разрабатывать.
Что должно быть в тестовом проекте? Мы должны иметь возможность запускать, дебажить наше приложение на тех же девайсах, на которых мы привыкли запускать и дебажить наше основное приложение.
Проект должен быть настроен максимально похоже на основной проект, чтобы в процессе переноса фичи из тестового проекта в основной мы не получили сюрприза. Или хотя бы свели количество получаемых сюрпризов к минимуму.
Также нам необходимо минимальное окружение в тестовом проекте, чтобы мы не тратили время на создание UIWindows, настройку библиотек, опрокидывание вызова открытия приложения в библиотеку, которая занимается авторизацией, и т. д.
CocoaPods app_spec
Весь мой сегодняшний рассказ будет построен вокруг фичи CocoaPods, которая называется app_spec.
App_spec — это как обычная subspec, только из нее генерируется не фреймворк, который можно подключить, а приложение, которое можно запустить на девайсе.
И из коробки CocoaPods позволяет нам пропихнуть в app_spec значение Info.plist и xcconfig. Это нам уже позволяет достаточно хорошо настроить наш проект.
Но если мы будем в каждой pods_spec писать огромные словарики, со значениями для Info.plist и xcconfig, то у нас сами podspec разрастутся и модифицировать их будет неудобно. Если нам понадобится что-то поменять, придется лезть в каждый podspec-файл и там править ручками что-то. Или не ручками. В любом случае это неудобно.
Чтобы избавиться от этого, мы сделали очень простой локальный плагинчик для CocoaPods. Он даже целиком влезает в один слайд. И он делает одну-единственную вещь: дает нам разрешение DSL CocoaPods, который создает app_spec с уже забитыми значениями в Info.plist и xcconfig. Это уже нам позволяет настроить signing, настроить те важные штуки, которые мы хотим видеть настроенными в нашем тестовом проекте.
Вопрос: как передать путь до entitlements, точнее, где хранить entitlements так, чтобы мы могли путь захардкодить в плагинчики.
Тут есть два пути. Либо указывать путь для какого-то, просто лежащего в нашем репозитории файла, например, до entitlements основного проекта. Либо хранить в плагине специальный файл entitlements для тестовых проектов. И с помощью того же плагина копировать его в наш developmentpod.
Мы выбрали первый путь, и тут у нас возникли некоторые сложности. Дело в том, что у нас в репозитории developmentpods лежат в разных подпапочках. Значит, мы не можем для них указать один относительный путь от developmentpod к файлу entitlements.
Тут важно подчеркнуть, что обязательно нужно указывать относительный путь, потому что этот путь остается в pods_spec. Значит, он будет участвовать в подсчете чек-суммы, которая хранится в Podfile.lock. И если там будет абсолютный путь, то это будет влиять на чек-сумму. Значит, чек-сумма будет меняться в зависимости от того, из какой папки вызвать $ pod install, где у вас лежит репозиторий, на какой машине вы запускаете $ pod install. В общем, это приводит к конфликтам.
Теперь я покажу еще одно небольшое расширение того же самого плагинчика, который позволяет нам посчитать относительный путь до этих entitlements.
Мы знаем относительный путь от нашего плагина до файла с entitlements, если они лежат в одном репозитории. И также во время запуска $ pod install мы знаем путь до нашего developmentpod. Вся магия, которая есть в этом коде, — выделенная строчка, где мы просто считаем относительный путь, зная эти два абсолютных пути.
Итак, что мы получили, уже имея подобным образом настроенную app_spec? Мы можем запускать наш тестовый проект на тех же девайсах сразу. Мы получили огромное количество настроек, в том числе entitlements, которое нам необходимо.
Окружение
Следующее, о чем я хочу поговорить, — окружение, то самое минимально необходимое окружение, которое нам нужно, чтобы сразу начать писать код.
В нашем случае нам нужен MapKit, уже настроенный, с переданными ключами, с прокидыванием вызовов в AccountManager. Это наша библиотека для авторизации. Ей тоже нужно прокидывать системные вызовы, ей тоже нужно передавать ключи, в том числе передавать открытие приложения по ссылке.
Также нам нужен стартовый экран с картой и navigation stack, чтобы мы могли использовать тот же самый navigation stack, что и в Картах, и проще переносить фичи из тестового проекта в основной. И точка входа, чтобы мы, не думая, сразу могли начать писать полезный код.
В вашем случае это может быть, например, настройка Facebook SDK, к которому тоже нужно передать ключи; ваши библиотеки для логина, в которые почти наверняка понадобятся прокинуть открытие приложения по ссылке. Какой-то стартовый Windows стартовым Controller, и та же самая точка входа для запуска основного сценария, который вы хотите разрабатывать в вашем тестовом проекте.
Как мы это сделали? Сделав один-единственный шаблон AppDelegate. Там мы инициализируем все необходимое окружение, библиотеки. Прокидываем все нужные вызовы, передаем нужные ключи. Создавая тестовый проект, нам остается создать новый AppDelegate для тестового проекта и отнаследоваться от шаблонного.
После этого мы переопределяем один-единственный метод, который вызывается после того, как шаблонный AppDelegate сделал все необходимое. И там мы уже имеем стартовый ViewController и поверх него можем запускать наш сценарий, который мы хотим отлаживать в тестовом проекте.
Было/стало
Как можно создать тестовый проект в лоб, и как его можно создать с помощью того способа, который я сегодня описал?
Создание тестового проекта в лоб:
— Мы идем в XCode, создаем новый проект (File > New Project).
— Вводим имя, желательно без ошибок, подписываем Bundle ID. Настраиваем этот проект.
— Меняем версию Swift, меняем target version. Пытаемся это все запустить на девайсе, правим signing и так далее. Это достаточно муторный процесс.
— Дальше мы создаем podfile, подключаем туда зависимости, устанавливаем поды.
— После этого мы идем и начинаем очень муторно и нежно настраивать наш AppDelegate, перенося по кусочкам инициализации тех библиотек, которые нам нужны.
— Создаем стартовый UIWindows с нашим ViewController
И только после этого мы можем начинать полезный код. Достаточно неприятная процедура, согласитесь.
Как мы можем создать тестовый проект, используя те наработки, которые я описал в сегодняшнем докладе?
— Мы создаем sample_app_spec, используя то расширение, для CocoaPods, с которого мы начали.
— Создаем AppDelegate, и наследуем его от шаблонного AppDelegate.
После этого мы можем начинать писать полезный код.
Создание тестового проекта таким способом проходит намного быстрее и надежнее. Мы точно не ошибемся в настройках signing, точно не ошибемся в настройках проекта. К нам сразу подъезжает все окружение, необходимое для начала работы.
А можно проще?
Вы можете задать вопрос — можно ли это сделать проще? Конечно.
Я выделил три уровня сложности. Первый — это то, о чем я сегодня рассказывал, все и сразу, когда мы делаем прокаченный плагин для CocoaPods, который умеет чинить пути до entitlements, и шаблонный AppDelegate.
Второй способ идеален по соотношению усилий к профиту. Он содержит только тот плагин для CocoaPods, который был первым в сегодняшнем рассказе. То есть просто подставляет значения в Info.plist и xcconfig. Это уже значительно облегчает жизнь.
Если вам не нужно, чтобы в ваших тестовых приложениях был файл entitlements, если у вас тестовые приложения достаточно сильно отличаются друг от друга и им не нужны общие компоненты, этот способ очень хорошо вам подойдет.
Третий способ самый простой, не требует никакой подготовки. Просто начните использовать app_spec. Проект, который генерируется из app_spec, создается в том же Workspace, что и ваш основной проект. Поэтому он уже наследует, версию Swift, target version и все настройки, которые у вас есть именно в Workspace. Вам уже будет значительно проще начать с ним работать.
Спасибо за внимание.