что такое ночная сборка
Что означает «Ночные сборки»?
Я использовал проекты с открытым исходным кодом некоторое время и разрабатывал приложения с открытым исходным кодом, и время от времени я сталкивался со словами «Ночная сборка», и мне всегда было любопытно, что это на самом деле означает. Означает ли это буквально, что проекты выполняются исключительно как побочные проекты (обычно ночью, после того, как все закончили свои дневные работы), и нет никакого реального участника / специальной группы разработчиков, или это более сложно?
Нет, это означает, что каждую ночь создается все, что было проверено в системе контроля версий. Эта сборка является «ночной сборкой».
Обычно это означает автоматическую сборку, которая выполняется один раз в день, как правило, после окончания дня для большинства разработчиков. Для проектов с разработчиками в нескольких часовых поясах это обычно компромиссное время. Идея состоит в том, что каждый, кто собирается проверить код «сегодня», сделал это, и автоматическая сборка убедится, что все скомпилировано, и, мы надеемся, запустим модульные тесты и любые другие существующие автоматические тесты и т. Д., А затем создаст окончательный установщик. / исполняемый и т. д.
Это означает, что сборка выполняется в конце каждого дня разработки. Если вы используете сервер непрерывной интеграции, он, как правило, будет настроен для построения кода и запуска модульных тестов при каждой регистрации. В конце каждого дня вы можете запускать более масштабные тесты, например, регрессионные и интеграционные тесты: которые занимают слишком много времени при каждой регистрации, и они будут срабатывать после ночной сборки. Если у вас есть полный непрерывный конвейер доставки, ночная сборка может также использоваться для развертывания встроенного кода в средах для пользовательского тестирования.
Этот термин часто используется для крупных проектов, где полная переработка готового продукта из исходного кода занимает слишком много времени для отдельного разработчика, чтобы сделать это как часть их обычного цикла разработки.
Вместо этого полная перестройка выполняется автоматически в течение ночи, поэтому сборочный компьютер имеет 8-10-12 часов для сборки и готовит ее для разработчиков, приходящих на следующее утро, чтобы они могли продолжить работу над своим крошечным кусочком сверху. новой версии.
В наши дни проект часто включает в себя множество тестов, обеспечивающих правильную работу кода, а также генерирует и публикует документацию из исходного кода (например, javadoc).
Что означает «Ночные сборки»?
Некоторое время я использовал проекты с открытым исходным кодом и разрабатывал приложения с открытым исходным кодом, и время от времени я сталкивался со словами «Ночная сборка», и мне всегда было любопытно, что это на самом деле означает. Означает ли это буквально, что проекты выполняются исключительно как побочные проекты (обычно ночью, после того, как все закончили свои дневные работы), и нет реального участника/специальной группы разработчиков, или это более сложно?
Нет, это означает, что каждую ночь создается все, что было проверено в системе контроля версий. Эта сборка является «ночной сборкой».
Это означает, что сборка выполняется в конце каждого дня разработки. Если вы используете сервер непрерывной интеграции, он, как правило, будет настроен для построения кода и запуска модульных тестов при каждой регистрации. В конце каждого дня вы можете запускать более масштабные тесты, например, регрессионные и интеграционные тесты: которые занимают слишком много времени при каждой регистрации, и они будут срабатывать после ночной сборки. Если у вас есть полный непрерывная доставка конвейер, ночная сборка может также использоваться для развертывания встроенного кода в средах для пользовательского тестирования.
Этот термин часто используется для крупных проектов, где полная переработка готового продукта из исходного кода занимает слишком много времени для отдельного разработчика, чтобы сделать это как часть их обычного цикла разработки.
Вместо этого полная перестройка выполняется автоматически в течение ночи, так что сборочный компьютер имеет 8-10-12 часов для сборки и готовит ее для разработчиков, приходящих на следующее утро, чтобы они могли продолжить работу над своим крошечным кусочком сверху. новой версии.
В наши дни проект часто включает в себя множество тестов, обеспечивающих правильную работу кода, а также генерирует и публикует документацию из исходного кода (например, javadoc).
Автотесты, ночные сборки, экстремальный Agile. Как мы тестируем наши продукты
Наше тестирование — большой продукт, сжатые сроки, огромная ответственность.
В каждой компании свои взгляды на организацию рабочих процессов. И они могут сильно отличаться.
Сегодня мы хотим рассказать о том, как мы тестируем наши продукты. Возможно, с чем-то вы поспорите, а что-то возьмёте на вооружение.
Наши продукты состоят из десятков модулей. Мы регулярно обновляем их, и эти апгрейды — завершенные мини-продукты.
Разработчики, собирают пачку кода в отдельную версию. И пишут описание: «Новый интернет-магазин в облаке. Изменения такие-то. Добавили кучу новых страниц». И обязательно прикладывают огромный changelog с несколькими сотнями коммитов.
Всё это поступает к тестировщикам. Этот процесс я называю «экстремальным Agile» — мы работаем по чётким итерациям. Отклоняться от этих правил тестирования нельзя.
Это необходимая мера — продукт огромен, как и изменения в нём. И чтобы не затягивать тестирование, мы жертвуем определённой свободой.
Получив задание на проверку обновления, мы сверяемся с планом тестирования. Он разработан отдельно под каждый модуль. В плане перечислены все важные сценарии использования.
Раньше мы делали так. Составляли подробные описания прецедентов: «Нажимаем сюда. В поле ввода пароля должны появиться кружочки. Если они не появляются, что-то не так».
От этой практики мы отказались, когда количество прецедентов превысило все разумные пределы.
В результате мы пришли к тому, что наш план тестирования — это перечисление важных бизнес-сценариев.
Пример — список кейсов по работе с задачами в «Битрикс24».
— Сохранение задачи работает? Отлично, идём дальше.
— Комментарии к задаче добавляются? Прекрасно, следующий пункт.
Сначала мы начинаем с верхнеуровневых, главных сценариев — например, создание и сохранение заказа магазина. А потом переходим к сценариям уровнем ниже — например, корректная работа крайнего срока в задачах.
Дальше мы за несколько этапов проверяем, как выполняются системные действия.
Этапы тестирования
В самом начале мы запускаем автотесты для конкретных модулей. Мы прогоняем через них все прибыльные модули.
Параллельно с прогоном автотестов мы делаем:
1 этап
— Изучаем описание изменений от разработчиков.
Смотрим, как должно быть по ТЗ. Сравниваем с тем, как модуль сделан в реальности. Но главное — учимся смотреть на продукт и изменения с точки зрения здравого смысла и обычного пользователя.
Удобен ли рабочий сценарий? Всё сделано «по-человечески»? Параллельно проводим ещё и usability-тестирование.
Это не всегда получается — изменений огромное количество. Но мы всё равно подсказываем разработчикам, когда какой-то сценарий можно сделать удобнее.
Мы автоматизировали огромную часть рутины. И постоянно думаем что можно «докрутить».
Вопросы к разработчикам мы обсуждаем в отдельных чатах под каждое обновление. У нас нет «сломанного телефона» — в чате присутствуют все сотрудники, причастные к выпуску.
2 этап
Итак, мы изучили описание обновления, и дальше вчитываемся в лог изменений — это второй этап.
— На этом этапе мы «отлавливаем» большинство ошибок. Разработчик может что-то упустить в «обычном» описании, но changelog покажет все ошибки.
Недавно был курьёз. Разработчик внёс изменения в текст локализованной версии продукта. На французском языке.
Казалось бы, просто текст. Но в этой хитрой комбинации был апостроф. В итоге на определённом этапе тестирования у нас просто начал падать JavaScript.
Оказалось что система просто не умеет обрабатывать эту комбинацию. Но автор изменения не упомянул о нём в описании. Ошибку мы нашли только в результате внимательного изучения лога изменений.
3 этап
Затем начинается третий этап — анализ обращений клиентов по ошибкам в продукте:
Обращения регистрируются в нашей системе трекинга ошибок Mantis. Почему мы её используем? Это историческое наследие, Mantis «трудится» у нас уже лет 15.
Я несколько раз предлагал коллегам найти что-то другое. Начинали анализировать и каждый раз оказывалось, что в Mantis есть все что нам нужно. Мы отлично интегрировали её в работу: связали с «открытыми линиями» и техподдержкой.
Весь журнал обращений клиентов по поводу ошибок мы берем в Mantis.
В каждое мини-обновление у нас включаются и фичи, и исправления багов, которые нашли клиенты и тестировщики.
Разработчик утверждает, что ошибка исправлена — мы проверяем. Если ошибка не исправлена, возвращаем тикет в работу.
Если разработчик несколько раз не может исправить одну и ту же ошибку, то убираем эту неработающую часть из обновления, отправляем на доработку разработчику.
4 этап
После верификации переходим к четвертому этапу — повторному прогону тестового плана.
Ситуация на рынке может поменяться. Если вчера условия были одни, то завтра они могут измениться. У нас нет жесткой привязки к ТЗ, которое мы разработали полгода назад. Поэтому мы прогоняем тестовый план еще раз.
5 этап
Пятый этап — сборка пакета обновлений и выгрузка в эталонную среду тестирования: Она находится в облаке Amazon и представляет собой отдельный портал «Битрикс24».
Здесь проводится окончательная проверка бизнес-сценариев. Ночью прогоняется весь комплект автотестов, они отслеживают изменения в показателях инфраструктуры.
6 этап
На шестом этапе мы организуем группы закрытого тестирования.
В группах состоят не тестировщики, а реальные клиенты и наши партнеры. Мы делаем это, чтобы получить обратную связь от реальных пользователей нашего продукта. Тех, кто непосредственно будет с ним работать.
К этому этапу почти все ошибки уже выловлены. Пользователи рассказывают, насколько им удобно работать по разным сценариям. Можно назвать это дополнительyым usаbility-тестированием.
7 этап
Потом наступает очередь седьмой этап — полузакрытое тестирование:
Как правило, у себя в Facebook я приглашаю клиентов первыми протестировать наши новинки. Поучаствовать может любой желающий, это бесплатно, просто напишите мне.
Для полузакрытого тестирования у нас есть stage-среда. В нее добавляются порталы клиентов, пожелавших принять участие. Обычно мы набираем несколько тысяч порталов, но все зависит от масштабов обновления.
Здесь мы тоже оцениваем удобство работы с продуктом, удобство сценариев.
Есть вещи, которые иногда кажутся неочевидными и на предыдущем этапе партнёры пишут, что так делать нельзя. А этот этап позволяет обкатать решение на гораздо большей выборке клиентов, проверить заложенные гипотезы.
Этот этап может занимать 2-3 недели. Мы постоянно вносим изменения по фидбэкам клиентов.
После тестирования на стейдже обновление выкатывается в production. А мы радостно пишем обучающие статьи и снимаем видео.
О своих впечатлениях вы можете писать в Facebook, лично или на публичной странице. Задавайте вопросы, высказывайте мнение, критикуйте — я всегда приветствую конструктив.
Cloud first
Многие клиенты жалуются, что мы выпускаем обновления сначала для облачной версии «Битрикс24». Пока нам не удалось построить процесс тестирования и выпуск продукта так, чтобы синхронно выпускать обновления и для «облака», и для «коробки».
Выпуск коробочного обновления — более сложный процесс. Если вы знакомы с обкаткой сырого продукта, то знаете, что такое тестовая среда. В облаке у нас уже есть готовая инфраструктура с заданными версиями PHP и MySQL со своей кодировкой. Там всё настроено и работает — можно ставить продукт и спокойно тестировать.
С «коробкой» все по-другому. Вариативность «сред» у клиентов огромна. Мы стараемся стимулировать пользователей к переходу на более высокие версии PHP, но многие делают это неохотно.
По факту, немалая доля клиентов начинают менять версию PHP, только когда их заставляют хостеры. Добавьте к этому разнообразие версий MySQL и кодировок.
Вот почему тестирование «коробки» намного сложнее и дольше по времени.
Автотестирование
Выше я подробно рассказал о процессе ручного тестирования, но это лишь часть всего процесса. Не менее важную роль играет автотестирование.
Созданием автотестов у нас занимается специальный отдел.
Раньше мы писали большие сценарные проекты. И эти сценарии были связаны между собой. Но их сложно поддерживать, если за небольшое время в продукте выкатывается огромное количество изменений.
Поэтому мы разбили большие сценарные автотесты на более мелкие независимые сценарии, кейсы.
Как проходит среднестатистический автотест?
Раньше мы тестировали только через интерфейс.
Есть мнение, что чем больше мы прокликаем интерфейс, тем лучше. Но мы от этого ушли, потому что сегодня важно тестирование по прецедентам.
Smoke-тесты и ночные автотесты
Также мы проводим smoke-тесты. Они работают очень быстро и проверяют самые верхнеуровневые, самые популярные бизнес-сценарии.
Обычно мы используем их для финальной проверки.
Если мы возвращаемся к процессу большого тестирования, большой итерации, большого пакета обновлений, то «ставим» пакет на эталонную среду. И ночью по расписанию запускаются автотесты. Все спят, а робот кликает по заданным сценариям.
Сейчас наш полный цикл ночных автотестов наконец-то стал укладываться в ночное время. Раньше он занимал около 32 часов. Ускорить автотесты удалось с помощью виртуальных машин и постоянных оптимизаций фреймворка и самих тестов.
А вот чего в нашем процессе тестирования нет, так это постоянной интеграции. Она нам не подходит, потому что есть накопленное «историческое наследие».
Мы не можем отказаться от какой-то части инфраструктуры, которая очень велика и сложна. Так что мы пока робко пробуем внедрить небольшие элементы классической continuous integration.
Ночные сборки
Пользователи коробочной версии знают, что каждые три месяца мы выпускаем новый дистрибутив. И на протяжении трех месяцев каждую ночь автоматически собирается и выкладывается новая сборка.
Как это происходит?
Самописная система обращается к сборщику обновлений и автоматически ставит все возможные обновления. Смотрит, что упало на этапе установки.
Затем машина собирает все имеющиеся дистрибутивы — у нас их 8 редакций — и ночью развёртывает локальные установки.
Ночные билды мы не выкладываем в открытый доступ. Система опять фиксирует, всё ли установилось. Если всё в порядке, запускаются все автотесты.
Утром тестировщик проверяет логи и смотрит, где и что упало, где разработчик допустил ошибку, где нужно поправить автотест.
Модульные-тесты
Большая часть наших тестов — UI, а не Unit. Модульные тесты мы используем только для важных компонентов: главный модуль, CRM, интернет-магазин.
В некоторых компаниях есть разногласия по поводу того, кто должен писать модульные тесты.
На мой взгляд, их могут писать и тестировщики, и разработчики. У нас сложилось так, что их пишут разработчики.
Для прогона модульных тестов используем стандартный инструмент PHP unit. Просто вызываем метод. Смотрим, как он работает с параметрами, которые даются на вход. И смотрим ответ.
URL checker
Это моя давняя идея. Возможно, кому-то она будет полезна.
Всё, что можно найти и вытащить из кода страницы, робот складывает в лог со скриншотами.
Написать такого робота крайне просто, но он может сэкономить много времени.
Component checker
Наш component checker работает так:
Если вы наш партнер, и когда-либо отдавали модуль нашему модератору в «Маркетплейс» на модерацию, знайте, что мы обязательно прогоняем все ваши решения с помощью этого чекера.
Визуальный эксперимент
Сейчас мы проводим небольшой эксперимент — переводим планы тестирования из текста в визуальные схемы. Они похожи на диаграммы связей.
На них сразу видно, какие элементы продукта с чем связаны: на что влияет сохранение заказов, или что влияет на само сохранение? Что и как может повлиять на схему складского учета?
Так тестировщик может быстро оценить — на какие элементы повлияют изменения в том или ином компоненте.
Качество тестирования
Мы отказались от избыточного тестирования. Я сторонник принципа достаточности.
Сейчас мы очень мало тестируем на «защиту от дурака». Например, в поле ввода денежной суммы, скорее всего, не будем вводить буквы.
В некоторых компаниях это практикуется. Некоторые считают, что любой хороший тестировщик не обязан ограничивать себя прописанными прецедентами тестирования. Он может их просто выдумать.
Но для нас этот подход перестал работать — в наших реалиях хватает «достаточного» тестирования.
Достаточность определяется потребностями рынка и сроками выпуска продукта. Мы можем тестировать новую фичу год, но опоздать с выпуском.
Поэтому мы придерживаемся принципа достаточности. Не выпускаем сырой продукт, потому что нас выкинут с рынка. И не тянем с выпуском вылизанной фичи, которая будет устаревшей на момент релиза.
* * *
Мне интересно ваше мнение о прочитанном. Как проводится тестирование в вашей компании?
Эволюция CI в Android
“Твою ж мать, какая же это хтонь!”. Примерно так можно было охарактеризовать все наши инфраструктурные скрипты до недавнего времени. Нужно было что-то менять, и мы сделали это.
Меня зовут Павел Стрельченко, я – Android-разработчик компании hh. Я расскажу вам как эволюционировали наши CI скрипты на протяжении трех лет, с какими проблемами мы сталкивались, как анализировали их и пытались изменить, а также что вообще делали и к чему в итоге пришли.
Это текстовая расшифровка выпуска нашего влога, поэтому если вам удобнее смотреть, а не читать, добро пожаловать на наш Youtube-канал. В статью получилось добавить множество дополнительных ссылок, так что можно почитать ещё и их.
Очень важный дисклеймер
После просмотра видео или чтения этой статьи может сложиться впечатление, что настройка CI для сборок Android-приложений — невероятно сложное и трудное занятие, с которым вам может помочь утилита fastlane.
Это неправильное ощущение, гоните его прочь.
Во-первых, настроить CI в 80-90% случаев — это довольно просто, благо в 2021 году уже есть множество инструментов, которые максимально упрощают этот процесс (вы можете посмотреть в сторону Jenkins / CircleCI, Github Actions, и так далее).
Во-вторых, fastlane — не панацея от всего, и команда hh пока не готова рекомендовать его использовать. Важно помнить, что перед использованием любого инструмента необходимо проводить его изучение, чтобы понимать, насколько он подойдет в каждом конкретном случае, оценить все риски и взвесить стоимость его адаптации.
Не верьте никому на слово, перепроверяйте информацию сами.
Всем стабильного CI.
Как вам жилось без CI?
Начнем с истории трехлетней давности. Об этом периоде времени хорошо рассказал Саша Блинов в своем видео про удивительную историю рефакторинга. В те далекие времена одновременно с изменением кодовой базы происходили и инфраструктурные изменения.
На тот момент у нас вообще не было инфраструктурных скриптов. Роль билд-сервера взял на себя специальный разработчик по имени Антон. По требованию тестировщиков он в нужные моменты собирал релизные АПК, дебажные АПК и отдавал их тестировщикам на тестирование.
Это выглядело как-то так
Разумеется, ни о каких регулярных сборках речи вообще ни шло. У нас не запускались на регулярной основе никакие тесты. Следовательно, любой коммит в develop или в мастер-ветку мог разломать абсолютно всё.
Во-вторых, у нас не запускались никакие проверки статического анализа. Поэтому code style не соблюдался по всему проекту. Например, в истории коммитов нашего проекта можно было обнаружить специальные коммиты, которые применяли в очередной раз принятый код style на всю кодовую базу. Такое себе удовольствие.
Ну-ка, реально много?
Отсутствие чётко зафиксированного код-стайла в настройках IDE часто приводило к необходимости что-то подправить в нескольких файлах внутри коммита. Проходило несколько дней и править приходилось уже сразу большой объём файлов.
Как пытались улучшить ситуацию?
Мы решили, что больше так жить нельзя, пора заводить CI.
Настроили build-машины — Мы пошли к команде инфраструктуры hh и заказали у них специальные build-машины с нужной нам конфигурацией. Нам нужно было не так много: Java да Android SDK;
Сформулировали типы сборок — Мы сделали специальную табличку, в которую выписали название сборки, критерии, по которым нужно запускать тот или иной флоу, что конкретно надо запускать в рамках прогона.
Посмотреть на табличку
В таблице мы подробно описали: зачем нам нужен определённый тип сборки, по какому триггеру он должен отрабатывать, надо ли внутри него прогонять Unit-тесты, UI-тесты, стат. анализ, какие приложения каких build-типов он должен собирать.
И таким образом, у нас получилось четыре основных типа сборки:
Сборка pull request-а (PR) — разработчик создает PR, и мы запускаем сборку на нашем CI-сервере. Эта сборка проверяет компиляцию приложений, прогоняет стат. анализ и Unit-тесты;
Ночная сборка (Night) — это регулярные ночные сборки. Наши разработчики ведут работу надо фичами в отдельных фиче-ветках. И эти фиче-ветки мы будем собирать каждый день до тех пор, пока они не будет смерждены в develop. Здесь мы запускаем прогоны Unit-тестов, UI-тестов, проверяем компиляцию всех приложений. Короче говоря, максимально собирающий билд;
Сборка PR to Develop — сборка запускается при попытке merge-а в develop. Когда разработчик заканчивает разработку своей фичи, он пытается ее смерджить в develop. В этот момент мы должны хорошенько проверить эту фичу: прогоняются все тесты, прогоняется статический анализ и компиляция всего и вся;
Custom build — это билд, который можно было настроить абсолютно для всего. Потенциально он мог собирать и релизные версии, и дебажные версии, и различные флейворы, а также прогонять или не прогонять Unit-тесты etc.
Как мы реализовали эти четыре flow?
Мы написали один-единственный большой Gradle-скрипт, который, по нашей задумке, должен был уметь делать всё, учитывать любые шаги, запускать какие угодно тесты и так далее. То есть реализовывал бы Custom build-план.
На один экран весь скрипт не влезет, в этом же файле были размещены все нужные ему Groovy-классы и множество утилит.
И с помощью различных переменных с нашего CI-сервера мы настраивали включение тех или иных особенностей билда. Благодаря этому единому скрипту мы настроили все четыре наших flow.
is_crashlytics_release — флажок, от которого зависела отправка готовой сборки в Crashlytics Beta (скупая слеза ностальгии);
step_app_names — тут можно было указать список приложений для сборки, в данном плане было указано только соискательское;
step_build_types — можно было указать через запятую все необходимые типы сборок: Debug, PreRelease, Release;
step_extra — тут можно было добавить дополнительный запуск какой-нибудь gradle-задачи;
step_to_fabric — уже неясно, почему тут стояло true, ведь этот шаг требовал проброса пар вида “AppName:BuildType” для сборки дополнительных тестовых приложений;
step_ui_tests — надо ли запускать UI-тесты;
step_unit_tests — указание gradle-задачи для прогона тестов именно для данной сборки;
Если кратко, кастомизировалось всё довольно гибко, пусть и не очень консистентно.
Ура, у нас заработал CI!
Какие выводы сделали?
Жизнь без CI – это боль. И чем раньше вы его включите и начнете на регулярной основе запускать тесты, прогонять статический анализ, тем раньше качество вашего приложения и кодовой базы резко улучшится.
Так прошло полгода.
Что бывает, когда не очень понимаешь, что делаешь
За полгода использования и периодических фиксов нашего супер-скрипта мы поняли, что на самом деле он не так хорош, как нам бы того хотелось.
Скрипт был длинной простыней Groovy-кода — Он находился в одном-единственном Gradle-файле. Большую часть скрипта занимала огромная Gradle-таска, которая была написана совершенно без учета каких-то best practices Gradle-а;
То, что скрипт был написан на Groovy на самом деле не так уж и плохо. Потому как в те времена про Kotlin Scripts мало кто знал, и сам KTS находился в зачаточном состоянии.
Мы не сразу поняли, что делала gradle-таска
Формировала текст bash-команд;
Записывала их в специальный shell-файлик;
А потом этот специальный shell-файлик запускала.
То есть у нас происходило так: на CI мы запускали Gradle-таску. Она запускала bash, который… снова запускал Gradle.
В видео я вставил совсем не ту шутейку, которую хотел, так что вставлю её в статью.
В какой-то момент мы обнаружили, что наши сборки на PR-ах идут примерно по часу. А билд сканы мы посмотреть толком не можем — из-за того, что запускаемая gradle-задача была странно написана.
Непонятки со статическим анализом — Эти скрипты запускались через неведомые тропы, поэтому мы не могли нормально обновить тот же detekt;
Добавьте к вышесказанному то, что одновременно со всем этим довольно интенсивно зашевелилась и разработка соискательского приложения. Из-за огромного количества новых фич и рефакторингов нам потребовалось ускорить прохождение регрессов.
Как решали новые проблемы?
Постарались уйти от использования супер-кастомного скрипта — Для этого мы вынесли генерацию bash-команд просто в bash-скрипты, которые мы сконфигурировали с помощью UI нашего CI-сервера. На Bamboo так можно. Мы пока вообще не думали про проблемы, заключенные внутри этих bash-скриптов, мы их просто вынесли и начали запускать на регулярной основе;
В Bamboo есть специальный тип задач — Script Configuration, вот в него мы и скопировали нужные скрипты.
Начали настраивать прогон наших UI-тестов — попросили помочь с этим наших коллег из инфраструктурных команд. Они подняли кластер Kubernetes, настроили для нас запуск эмуляторов (про всё это можно посмотреть отдельный доклад). Со своей стороны мы описали небольшой Docker-контейнер, внутрь которого положили java, Android SDK и утилиту Marathon, с помощью которой собирались запускать наши UI-тесты.
Итоги работ за полгода
Что у нас получилось сделать?
Перестали использовать скрипт Custom Build-а на всех flow, кроме релизного — Скрипт до недавнего времени всё ещё жил в нашей кодовой базе, а избавились от него мы буквально месяц назад;
Логика инфраструктурных скриптов стала чуть понятнее — Мы визуализировали ее в UI-интерфейсе нашего CI-сервера, разбили на шаги, и поэтому жить стало чуть проще;
Исправили проблему, связанную с прогоном наших PR — Они больше уже не шли по часу, плюс мы восстановили работу build scan-ов;
Регулярные прогоны UI-тестов ускорили регрессы — Мы получили возможность чаще релизиться.
Чему мы научились за этот период?
Мы научились тому, что все-таки не стоит писать эти инфраструктурные скрипты на скорую руку. Лучше разобраться в используемой технологии, чтобы не получалось, что “gradle запускает bash, который запускает gradle”.
А ещё, UI-тесты – это классно. Чем раньше вы их затащите на регулярной основе, тем лучше. Но помните, что для них понадобится подготовить инфраструктуру, принять множество решений про инструменты, обсудить процесс работы и так далее.
Так прошло еще полгода.
Я знаю, что вы делали 2 года назад
За полгода в команде произошли некоторые изменения и накопилось несколько проблем:
Сменились люди, работавшие над инфраструктурными скриптами — Из-за низкого bus-фактор-а, нам пришлось заново разбираться во всех build-скриптах;
Долго не рефакторили bash-скрипты — При этом мы постепенно расширяли их функциональность, они становились всё сложнее и сложнее;
Дублирование логики в скриптах — Мы вскоре заметили, что скрипты дублировали друг друга почти в каждом flow, который мы запускали на CI. Они отличались в мелочах: где-то нужен был специальный gradle-флаг, где-то необходимо было запускать тесты, где-то нет. Изменения или обновления, которые мы хотели внести в эти все скрипты, приходилось дублировать почти во всех вкладках настроек разных планов и flow;
А покажи как выглядело
А теперь представьте, что таких вкладок не две, а около десяти.
Изменения в CI-скриптах раскатывались сразу на всех — Конфигурация планов CI была единой для всех разработчиков, поэтому приходилось либо создавать какой-то промежуточный план для тестов обновлений, либо выдумывать специальные трюки;
В логах на CI было трудно разобраться — Наш CI-сервер, конечно, пишет в лог, когда запущен тот или иной шаг внутри нашего билда. Но в потоке логов, которые занимали несколько мегобайт, эти строчки легко было пропустить.
Иногда менялись настройки build-машины — Иногда команда инфраструктуры что-то меняла на уже настроенных машинах, и там исчезали нужные нам для запусков утилиты.
Как мы жили, что мы делали?
Попробовали решить задачу обновления sh-скриптов — Для этого мы вытащили их из нашего CI в отдельные sh-файлы и добавили внутрь репозитория. Теперь CI запускал не какой-то зафиксированный для всех sh-скрипт, а запускал тот скрипт, который находился внутри нашего репозитория. Бонусом к этому мы получили возможность изменять кусочек любого sh-скрипта и тестировать его внутри определённой ветки. Да, добавлять новые шаги на CI по-прежнему было нельзя, потому что это сразу раскатывалось на всех. Однако bash-скрипт мог запустить какой-нибудь другой bash-скрипт, и таким образом мы могли проверить новые шаги;
И много ли скриптов было?
Много. И многие из них ещё и дублировались раньше в разных планах.
Немного упростили чтение логов — Мы добавили к нашим скриптам небольшую утилитку, которая на вход принимала название скрипта, который нужно запустить и его аргументы. При помощи специальной конструкции в bash мы понимали, какой именно скрипт хотим запустить, писали понятное описание, добавляли кучу специальных символов, чтобы их можно было легко отличить в логах. И дальше уже запускали нужный нам скрипт.
А что за конструкция?
Если вам когда-нибудь в жизни было интересно, как выглядит выражение “switch-case” на bash-е, то вот оно:
Перенесли часть задач в Docker — Для уменьшения рисков, связанных с переносом сборок на различные машины, мы начали потихонечку задумываться над тем, чтобы перенести выполнение наших задач, которые пока что выполнялись на билд-машинах, внутрь Docker-контейнера. В частности, например, сборку APK.
Мы тогда вообще многое поняли
Нужно увеличивать bus-фактор — Расширяйте экспертизу всех разработчиков в работе инфраструктурных скриптов, чтобы не терять важную информацию при уходе людей. С тех пор мы проводим демки, пишем статьи в wiki, чтобы знания, которые хранятся в головах разработчиков, хранились еще где-нибудь;
Docker упрощает перенос инфраструктуры — Сейчас мы в любой момент можем перенести наши сборки с одной машины на другую, главное чтобы там были bash и Docker;
Не пишите сложные конструкции на bash-е — Объемные функции и всякие switch-case-ы на bash-е выглядят сложно и трудно поддерживаются.
В таком состоянии наша кодовая база жила почти до сегодняшнего дня. Сейчас мы переместимся чуть ближе к маю 2021 года.
Дела не так давно минувших дней
Мы уже год как возобновили активную фазу разработки нашего нового работодательского приложения. До этого оно почти год находилось в заморозке и никаким образом не запускалось на CI.
А ещё мы продолжали изменять функциональность существующих инфраструктурных скриптов без их глобального рефакторинга. Да, мы понимали, что там есть проблемы. Мы знали, как можно что-нибудь улучшить. Но повода как-то не представлялось. И ежики кололись, бодались, но продолжали жрать кактусы.
Наш редактор очень просил вставить эту картинку
Я не мог ему отказать.
С какими вообще проблемами мы сталкивались?
Запутанные bash-скрипты — Нам сильно аукнулось то, что мы переносили bash-скрипты из нашего старого Groovy-скрипта без каких-либо изменений. Мы особо не вчитывались в логику bash-скриптов, когда переносили их на Bamboo, мы не вчитывались в них и когда переносили их обратно в репозиторий. Это сыграло злую шутку с нами, потому что в какой-то момент мы поняли, что эти скрипты очень запутаны. Один скрипт вызывает другой, второй вызывает третий, и все это как-то непоследовательно. Аргументы передаются из скрипта в скрипт и конвертируются непонятно во что;
Каждый второй скрипт использует переменные Bamboo — Почему это плохо? Потому что мы оставались сильно привязаны к нашему CI-серверу. И если бы мы, допустим, захотели в это время перейти на какой-нибудь другой CI-сервер (ну скажем, Jenkins), то нас бы ждал очень неприятный сюрприз и большое количество дополнительной работы;
Что за CI-переменные?
В нескольких скриптах нам нужно было использовать название текущей ветки, плана, на котором запущена сборка и т.д., в других скриптах нам могли понадобиться значения номера сборки, пути к артефактам, и многое другое.
Не вся работа выполняется внутри Docker-контейнера — На дворе 2021 год, а мы все еще не перевели всю работу наших инфраструктурных скриптов внутрь Docker-контейнера. Часть скриптов выполнялась по-прежнему на билд-машинах, и она страдала от переноса сборок на те или иные машины. Часть выполнялась внутри Docker-контейнера;
Зоопарк языков программирования в инфраструктурных скриптах — За три года существования этих инфраструктурных скриптов у нас сформировался целый зоопарк языков, на которых мы писали эти скрипты. Там был и Groovy, и Kotlin, и Bash, и Python, и даже Go!
“Посторонние скрипты” — часть инфраструктурных скриптов находилась в совершенно постороннем репозитории, который был никак не связан с мобильными разработчиками. И когда эти скрипты ломались, мы даже не знали, куда идти, кого спрашивать и как вообще фиксить проблемы, с ними связанные.
О версионировании скриптов
Добавьте ко всем проблемам еще и то, что все наши инфраструктурные скрипты по-прежнему настраивались через UI-интерфейс нашего CI-сервера.
Почему это плохо? Потому что эта конфигурация планов никаким образом не версионировалась. И она была единой для всех. Из этого вытекал целый океан новых проблем.
Усложнялось добавление новых шагов — нам приходилось либо терпеть и страдать от каких-то падений инфраструктурных скриптов, либо ждать, когда скрипт с фиксом будет подмержен в develop. Был и третий вариант: проверять, существует ли определенный файлик внутри нашего репозитория. А если существует, то запускать его. Это очень сильно осложняло работу по обновлению скриптов;
Дублирование шагов между планами Bamboo — Для переиспользования шагов описанных flow нам приходилось дублировать описания этих шагов между настройками плана на Bamboo. Допустим, один план запускал сборку APK, и другой план его запускал. Нам приходилось дублировать все эти запуски различных скриптов между планами;
Отсутствие код-ревью для настроек планов — а это значит, что их качество может сильно страдать;
Не каждый разработчик может поправить настройки — потому что доступ к конфигурации планов на CI имеет только ряд избранных, и только они могут что-то исправлять;
Нельзя переиспользовать общую логику между платформами — а такая логика была. Например, отправка нотификаций в Slack, проставление ссылок в Jira, Github. Но так как скрипты конфигурировались на Bamboo, мы не могли это переиспользовать.
Всё это образовало ту самую ХТОНЬ. Нужно было от этого уходить, но нам не хватало какого-то триггера, повода всё изменить.
Как мы собирались решать проблемы
Пока триггер не появлялся на горизонте, мы всё равно планировали наши улучшения.
Перенос инструментов в Docker — Вопрос запуска тех или иных утилит вне Docker-а можно было решить переносом всех инструментов, которые мы используем, и перемещением их в Docker-контейнер. Мы описали Docker-файл, внутри которого установили всё, что нам нужно: и Java, и Android SDK, и Marathon, и Allure, и Python, и Gradle-Profiler и массу всего другого;
Единая точка входа в Docker — Мы описали специальный bash-скриптик, который сегодня стал новой единой точкой входа для наших инфраструктурных скриптов. Этот bash-скрипт просто запускает Docker-контейнер и передает управление в него, чтобы нужные нам команды и утилиты вызывались внутри контекста Docker-контейнера;
Выглядело это довольно просто
Написав такой скрипт, можно использовать его на CI вот так:
Конвертация CI-переменных в ENV — Проблему сильной привязки наших инфраструктурных скриптов к нашему CI-серверу можно было решить описанием списка всех CI-переменных в одном месте. Мы пробросили их внутрь Docker-контейнера как ENV-переменные, после чего используем внутри всех скриптов только их. Если мы захотим когда-нибудь уйти от Bamboo, нам будет достаточно поменять только один файл, внутри которого используются эти Bamboo-переменные;
А в Docker это можно пробросить вот так:
Это пробросит только те ENV-ы, где значение после знака ‘=’ не является пустой строкой.
С остальными проблемами, вроде отсутствия версионирования скриптов, зоопарка языков, трудностей обновления скриптов и отсутствия review, нам мог бы помочь такой подход, как Infrastructure as Code, то есть, описание инфраструктуры как какого-то кода.
Полностью честный Infrastructure as Code мы вряд ли осилили бы. Однако по максимуму перевести инфраструктуру в код мы могли себе позволить. И нам оставалось только выбрать инструмент: как именно мы будем реализовывать все эти инфраструктурные вещи внутри нашей кодовой базы.
Из-за леса, из-за гор показал мужик QA
И тут появились они. Наши тестировщики, наш великолепный QA-отдел. В одном из выпусков “Охэхэнных историй” наш тестировщик Даня уже рассказывал об автоматизации нашего релизного флоу. В связи с этим в кодовой базе Android-приложения появились такие инструменты как Ruby и fastlane.
И наши тестировщики были настроены серьезно. Им не хотелось переписывать уже работающие под iOS скрипты на какие-то другие инструменты, поэтому они топили за использование fastlane.
Несмотря на то, что для Android-разработчиков более привычными инструментами являются Kotlin, Gradle-скрипты и Bash, мы все-таки решили попробовать и дать шанс fastlane с Ruby.
Сможем отказаться от зоопарка языков — Потому что всё, что может Python и Groovy, в целом может и Ruby;
Возможность использовать наработки iOS-команды — iOS-разработчики у нас уже имеют большую экспертизу в использовании Ruby и Fastlane. Поэтому мы могли, использовать их опыт для review и созданные ими наработки для наших автоматизаций;
Обширная экосистема Ruby и fastlane — У Ruby и fastlane есть довольно обширная экосистема, которая используется в задачах, связанных с continuous integration и continuous delivery. Мы провели небольшое исследование и поняли, что в целом все задачи, которые нам нужны, мы можем реализовать через Ruby и fastlane, отказавшись от использования старых скриптов;
И вообще, в ходе каких-нибудь революционных изменений, типа перехода на fastlane и Ruby, мы могли разом исправить и те проблемы, о которых я уже упоминал. Мы подумали: «Почему бы и нет?».
Да, мы понимали все риски. Android-разработчикам совершенно непривычно работать с Ruby. И перевод вообще всех скриптов на новые рельсы – очень долгое и непростое дело. Однако объем накопившихся проблем перевесил все эти риски, поэтому мы начали нашу подготовку к изменениям.
Масштабные изменения в скриптах
На этот раз мы решили подойти к изменению инфраструктурных скриптов системно, поэтому мы создали специальную Miro-доску, в которую выписали всю имеющуюся у нас информацию про наши CI flow: какие шаги где используются, какие из них можно переиспользовать, какие аргументы подаются на вход, что нам надо запускать параллельно, а что можно последовательно и так далее.
Мы начали изменять наши скрипты, начиная с плана, связанного с pull request. Так как мы твердо решили запускать все эти действия на CI внутри Docker-контейнера, соответственно, мы туда добавили и инструменты, которые помогут нам запускать Ruby и fastlane (rvm / bundler).
И после этого мы начали переводить существующие инфраструктурные скрипты на язык Ruby. Попутно пытались переиспользовать уже имеющиеся у iOS-команды утилиты.
Для этого мы создали отдельный репозиторий, который оформили как fastlane-плагин, и начали выносить туда общий код. Вынести получилось довольно много: работу с Jira, утилиты для git-а, общие константы и модели, да еще и код для нашего релизного флоу.
Структура репозитория была создана автоматически через fastlane new_plugin
Немного о боли и страданиях
После всего этого вы бы точно спросили меня: «Неужели всё так хорошо и здорово получилось с этими Ruby и fastlane?». Кажется, стоит рассказать и про наши страдания. Я вас не разочарую, потому что, разумеется, их у нас было в достатке.
Android-разработчики редко работают с Ruby — перестроить свой мозг джависта и человека, который работает с Kotlin, на такой язык довольно сложно. Исчезают привычные концепции: абстрактного класса в Ruby нет, интерфейсов а-ля Java тоже нет. Зато появляются некоторые другие интересные особенности языка, тот же duck typing.
Динамическая типизация – это больно — у нас был довольно болючий пример, который мы обнаружили у себя только через месяц после добавления кода.
Мы написали скрипт, который должен был проверять наши тестовые стенды на их актуальность. Одним из его шагов было создание json-модельки и отправки её на сервер. При создании модели нужно было сконвертировать дату в массив чисел для нашего сервера.
Из-за ошибочного формата FULL_DATE_TIME_FORMAT конвертация происходила неправильно, и результатом функции был массив с нулями. Это поломало нам кучу UI-тестов, случались какие-то дикие флакования. По-хорошему, раз функция не смогла что-то сконвертировать, она должна была выкинуть какое-то исключение, но нет, Ruby интерпретировал ошибку как 0.
Вот так работает правильно:
Недостаточная поддержка fastlane в IDE — fastlane-специфика не поддерживается и можно нехило выхватить с этого. Однажды я полчаса искал ошибку, по которой у меня не вызывался fastlane Action, и оказалось, что у меня в классе Action не было суффикса Action. IDEA-инспекции тут бы могли спасти, но не спасли. Потому что их не было.
Проблемы с приватным общим репозиторием — Потратили много времени, прежде чем научились использовать написанный fastlane plugin на CI, были какие-то проблемы с пробросом специальной ENV-переменной access token для доступа к Github-репозиторию;
Отсутствие общих правил написания Ruby-кода между платформами — Код-стайл мы легко расшарили с помощью Rubocop, а вот как мы именуем функции, как обобщаем нужный код, — таких правил у нас не было. iOS-коллеги писали код по-своему, мы по-своему. На review у нас постоянно были какие-то непонятки;
Проблемы с запуском bash-команд из fastlane-а — У нас почему-то стабильно не срабатывала команда grep, но в итоге мы её переписали на Ruby, и всё стало хорошо.
Плюсы текущего подхода
Несмотря на все трудности, переход на fastlane существенно улучшил наши скрипты инфраструктуры.
Получилось описать flow наших CI-планов в виде высокоуровневых функций — Такие функции легко читать и поддерживать;
В качестве примера
Перевели практически все CI-планы на использование fastlane — Собираемся добивать остальные, потому что нам это понравилось.
Ушли от зоопарка языков — У нас остался только bash в виде одного скрипта + Ruby;
Конфигурация CI-планов теперь лежит в исходном коде репозитория — Теперь эти скрипты проходят code review. Более того, они могут проходить ревью и у Android-разработчиков, и у iOS-команды;
Получилось переиспользовать общую логику с iOS-командой — Мы вынесли общие скрипты в отдельный репозиторий, обе команды могут ими пользоваться.
Подводим итоги всего рассказа
Какие решения мы считаем неудачными за всю историю нашей эволюции?
Отсутствие CI — Это, пожалуй, самая большая проблема и ошибка, которую мы допустили на старте. То есть, чем раньше вы запустите у себя CI, который начнет на регулярной основе собирать ваше приложение, тестировать его, проверять на валидность с помощью скриптов статического анализа, тем лучше. Не затягивайте с этим, потому что это резко улучшит качество ваших приложений и кодовой базы;
Маленький bus-фактор в вопросах инфраструктуры — Если вы потеряете сакральные знания о разных частях приложения, вам потребуется много времени, чтобы их восстановить. Распространяйте знания, устраивайте демо, пишите wiki;
Зоопарк языков в инфраструктурных скриптах – это зло — Желательно сводить к минимуму количество инструментов, которые вы используете. Меньше инструментов = проще разобраться..
Описание CI-задач только в UI-интерфейсе CI-сервера — Из этого вытекает множество проблем, о которых я уже писал выше.
Какие решения мы считаем удачными:
Подход Infrastructure as Code — Этот подход позволяет версионировать ваши инфраструктурные скрипты, позволяет их ревьювить, легко менять, обновлять, создавать новые шаги;
Переиспользование общих скриптов между платформами — Это может подойти далеко не каждой команде, но здорово, когда получается переиспользовать общую инфраструктурную логику;
Увеличение bus-фактора — Чем больше людей знает про вашу инфраструктуру и всякие тонкости, тем лучше.
На этом у меня всё. Если у вас будут какие-то вопросы, пожелания, критика оставляйте их в комментариях.