
Привет! На связи разработчик Максим и инженер по качеству Евгения из Т-Банка. Как-то мы задумались о переходе на TBD, чтобы избежать develop-ветки со всеми вытекающими.
Внедрение TBD оказалось более сложным процессом, чем мы думали сначала. Углубившись в тему, осознали, насколько важно учесть множество деталей, которые нужны для успешного перехода: в каких процессах необходимо настроить автоматизацию, доработать тесты, обновить документацию и сделать многое другое.
В этой статье мы поделимся опытом перехода на TBD: планом внедрения и вопросами, с которыми мы столкнулись.
Cтатья пригодится инженерам уровня middle и ниже и тимлидам. Для senior-инженеров статья не будет откровением, но надеемся, что станет местом для обсуждения нюансов внедрения или возможностью посмотреть на процесс с точки зрения QA.
Погнали!
Проанализировать признаки, которые затрудняют переход
Будет сложно менять процессы и привыкать к ним, если участники не понимают конечную цель, какие изменения для команды критично важны, а где соответствовать аспектам TBD не получится.
Команда не сможет достичь «каноничного TBD», но есть подвох. При TBD ветки живут до двух дней, на практике не всегда так получается по разным причинам: например, не удается разбить изменения на небольшие задачи, потому что проще провести все изменения за раз вместо использования практики Branch by Abstraction.
Мы заметили, что не в каждом сервисе легко переходить на TBD. В легаси-сервисах мы отказались переходить на TBD, но пришлось пересмотреть подход к тестированию и выполнить все шаги, которые перечислили ниже.
Для себя мы выделили список признаков, из-за которых сложнее перейти на TBD — не путать с блокерами:
Нет желания или возможности автоматизировать процессы в команде. К теме автоматизации еще вернемся, на наш взгляд, это — важная часть процесса перехода на TBD.
Cложный релизный процесс. Если в команде многоступенчатый процесс релиза, это затормозит процесс перехода на TBD. Например, когда релиз не проходит быстро и самостоятельно из-за ручных апрувов. Представьте: задачи разрабатываются за день, высокое покрытие кода, изменения автоматически деплоятся на тест, релиз продолжаете собирать в течение недели, а потом катите по четвергам.
Низкий процент покрытия кода автотестами, частая потребность в ручном тестировании, плотная связь с шареным статическим тестовым стендом. Здесь проблема — в серьезном вложении в автотесты и проработке процесса тестирования. Чем дольше и сложнее вручную тестируются фичи в команде, тем труднее пройдет процесс.
Работа на шареном стенде, и неважно — для ручных или автотестов, влияет на скорость работы над задачами хотя бы потому, что если контур будет недоступен, тестирование блокируется.
Поддерживаются легаси- и монолитные сервисы. Монолит монолиту рознь, равно как и легаси-сервисы. Но все же оставляем этот пункт, потому что вокруг таких сервисов может быть слабо выстроена автоматизация кода и процессов или сложно нарастить эту автоматизацию по разным причинам.
Автоматизировать и покрыть тестами целевую пирамиду
TBD подразумевает постоянную и быструю поставку задач и ценности потребителю, поэтому мы заранее подумали о качестве этой поставки и скорости тестирования — подложили соломки, так сказать.
Что нам помогло:
Заняться автотестами. Причем здесь говорим и о code coverage, и об автоматизации тестов на всех уровнях пирамиды.
Рассчитать и определить не покрытый тестами код поможет, например, JaCoCo-репорт с предварительной настройкой "html.required.set(true)". Еще по ссылке найдете минимальную настройку джобы для сбора репорта.
Минимально допустимый процент покрытия тестами определяет команда самостоятельно. Например, для себя мы поставили минимальный порог покрытия 80%, а при достижении этого порога добавили валидацию на показатели покрытия, чтобы он не снижался, и время от времени стараемся поднимать эту планку. Регулируем это через violationRules.
Хороший ход — автоматизировать интеграционные и Е2Е-тесты (их можно гонять на моках) и использовать контрактное тестирование. Только при этом нужно убедиться, что функциональность проверяется как можно ниже на соответствующем уровне: это в разы снижает количество интеграционных нестабильных тестов.
Настроить процесс работы с техническим долгом QA. Проблемы у постоянно увеличивающегося QA-техдолга такие же, как у обычного, разница в том, что это — потенциальные риски из-за низкого качества. Если вы будете работать по TBD, эти риски будут еще выше — мерджить потенциально опасный код сразу в master вы вряд ли захотите.
Предлагаем остановиться на вопросе: как понять, что текущего набора интеграционных и E2E-тестов в мерж-реквесте/сервисе достаточно для уверенности в качестве и готовности перехода на TBD?
Если говорим о каждой отдельной фиче, предлагаем прорабатывать этот момент на этапе груминга/3-Амиго, где можете обсуждать на берегу, что тестируем в рамках задачи, как, на каких уровнях. И прям сразу записываете это в задачу.
Если речь о сервисе, здесь советуем посмотреть на вашу целевую пирамиду тестирования и тест-кейсы в регрессе. Допустим, начать автоматизацию тестов как раз с регресса: так вы будете уверены в работе цепочки сервисов плюс убьете второго зайца в виде снижения затрат ресурсов на ручной регресс перед релизом.
Стабилизировать тесты
Стабилизация тестов нужна для того, чтобы как можно меньше времени тратить на ручную проверку упавших тестов. Каждое ручное действие — увеличение сроков разработки и связанные с человеческим фактором риски. В худшем случае при наличии нестабильных тестов существует риск пропустить проблему, например замьютить проблему с аргументом «этот кейс часто падает, можно не смотреть».
Тут чиним Flaky-тесты, если такие есть. Как вариант, можно завести таблицу и записывать случаи «несправедливо» упавших тестов или автоматически собирать такую информацию. Еще вариант — договориться с командой, что на каждый подобный случай заводите таску. Здесь важно избавиться от толерантности к флакающим тестам.
Внешние системы закрываем моками или поднимаем контейнеры, при этом важно добавить джобу с остановкой этих контейнеров.
Встроить тесты в пайплайн, сделать проверки обязательными
Следующий шаг — включить запуск тестов на каждое изменение и не разрешать мерджить изменения, если они упадут. Это правится в CI-файле, и важно не забывать о дефолтных настройках.
После увеличения покрытия тестов может стать так много, что пайплайн, в котором тесты запускаются последовательно, начнет тормозить разработку. В этом случае предлагаем подумать над тем, какие тесты и где хотите запускать. У нас, к примеру, следующие виды пайплайнов: mr, в master и по тегу, — от этого зависит набор проверок в пайплайнах.
Одни команды запускают все тесты на всех видах пайплайнов, другие — распределяют по разным видам пайплайнов или исключают запуск тестов при определенных изменениях в мерж-реквесте. Например, не нужно запускать интеграционные тесты в случае изменения документации.
Какие вопросы нам помогли, чтобы определиться с порядком запуска тестов:
Как быстро прогоняются все тесты? Сколько длится прогон каждого уровня тестов?
Устроит ли команду, если будем прогонять все тесты в каждом пайплайне?
Какие риски, если часть тестов запускаться не будет?
Если оба варианта выше не нравятся, можем ли мы как-то ускорить тесты?
Можно ли распараллелить запуск тестов?
При этом не стоит забывать о shift-left-подходе: чем более важные, простые и базовые проверки, тем раньше они должны запускаться. Например, сначала запускаем прогон юнит-тестов, затем — все остальные.
Шаги миграции OR: раз, два и погнали
В первой части статьи постарались рассказать о пререквизитах, которые нужно в самом начале принять как данность, не оспаривая и не возводя в абсолют. Спустя время пройти стадию принятия и уже осознанно отказаться от идеи перехода на TBD попытаться подготовиться к самому страшному — процессу трансформации процессов и ввода множества подсобных практик, чтобы после гордо сказать коллегам из соседней команды: «МЫ В TBD!»
Дальше в статье опишем шаги, которые нужны для трансформации процессов, а еще трудности, с которыми мы столкнулись. Мы осознанно не стали давать полное описание каждого шага и вклад в практику TBD, чтобы не превращать статью в справочник по TBD. В качестве справочника лучше использовать первоисточник.
Отнеситесь к шагам как чек-листу с предостережениями и, надеемся, полезными инсайтами. Некоторые из них могут показаться очевидными для опытных инженеров, но мы не стали их опускать для сохранения общей картины.
Перестать использовать long-lived branches
Подход с использованием нескольких долгоживущих (long-lived) версий кода активно используется в распространенной практике — GitFlow. Многим знакомы названия бранчей — Develop, Hotfix и Release.
Подобного рода ветки живут продолжительное время независимо от основной ветки (master). За это время они могут независимо стабилизироваться, развиваться, наполняться хотфиксами, в том числе конфликтующими с master-веткой. Зачастую это выливается в объемные мерж-реквесты с основной веткой, сопровождающиеся резолвом большого числа конфликтов. В подобные моменты в жизни проекта возникают сингулярности, когда история проекта теряет равномерное течение.
В худшем случае выравнивания с основной веткой становятся вовсе невозможными и проект продолжает развиваться сразу в нескольких параллельных версиях, вырождаясь в проект «змей горыныч».
Смысл же шага — сохранить только одну long-lived-ветку (master), что по логике избавит от вышеописанного. Живя в парадигме одной долгоживущей ветки, получаем проект, который представлен в единственном исполнении кодовой базы в master-ветке.
Тут стоит сделать акцент на нюансах шага:
Вероятно, команда столкнется со сложностью стабилизации кода. Release-ветки зачастую служат для упрощения процесса стабилизации и фиксации кода перед выпуском. Устранение подобных веток может привести к тому, что стабилизировать перед выдачей потребуется основную ветку с большим числом изменений, что потенциально усложняет процесс.
Не исключены ситуации, когда одна long-lived-ветка становится бутылочным горлышком для процесса имплементации и поставки фич. В таких случаях вводится фриз на внесение изменений в основной бранч для стабилизации или выдачи критичного хотфикса, что ставит команду перед выбором: продолжать разработку в параллельных ветках или приостановить.
Перейти к короткоживущим feature-веткам с небольшими инкрементальными изменениями
Следующий шаг миграции на TBD тесно связан с предыдущим, и feature-бранчи не исключение из этого правила.
TBD запрещает продолжительную изоляцию разработки в отдельной feature-бранче, требуя частых промежуточных слияний с основной веткой небольшими блоками. Это и дает быстрее находить проблемы концептуального характера за счет более ранней стадии ревью, и побуждает команду работать в режиме коллаборации за счет запрета долгой изолированной разработки. Но и тут поджидают очередные трудности:
Нужно побороть психологический блок вносить изменения в мастер небольшими незавершенными блоками. Об этом стоит условиться каждому члену команды на этапе предварительного анализа перехода на TBD.
Могут быть сложности из-за вливания в незаконченные фичи. Такой подход требует умения планировать и разбивать поставку фичи отдельными последовательными частями. Из-за этого возникают естественные риски для стабильности кода в процессе поставки незаконченных фич и задают более высокие требования к процессу тестирования. Это приводит к процессному оверхеду, который отнимает существенную часть времени и усилий. Важно, что этот фактор становится менее существенным с течением времени и адаптации команды к новой практике.
Далеко не все активности выполняются в короткоживущих ветках. Сложные баги, исследовательские задачи, миграции зачастую требуют продолжительной и муторной работы в изолированном бранче и не подразумевают частых вливаний в основную ветку.
Long-lived-ветки содержат больше контекста предлагаемых изменений, который полезен для проведения качественного ревью.
Использовать Branch by Abstraction (BBA)
Branch by Abstraction — подход к управлению изменениями в коде, который позволяет разработчикам вносить изменения в кодовую базу, не нарушая текущего рабочего состояния приложения.
BBA предлагает изолировать незавершенные изменения в основном бранче через использование абстракции и интерфейсов для поддержания множественности реализаций, и это хорошая базовая практика разработки в ООП. По сути, этот подход призван нивелировать сложности предыдущего шага миграции. Но тут есть свои но:
Важно учитывать, что далеко не каждое изменение можно изолировать с помощью введения абстрактного слоя — инфраструктурные изменения в коде как очевидный пример, — что ограничивает кейсы использования BBA.
Разработка с BBA подразумевает сосуществование нескольких реализаций одного функционала — старого и нового, последний к тому же зачастую находится в незавершенной фазе. Подобная множественность реализации естественным образом усложняет проект в части поддержки, анализа багов, организации тестирования.
Разработать функционал с возможностью динамического включения/отключения — Feature Flags, API versioning
Внося небольшие и частые изменения в единственную long-lived-ветку, где находятся сразу несколько реализаций одного функционала, изолированных подходом BBA, важно динамически переключать реализацию. Этого можно достичь двумя подходами: с помощью Feature Flags (далее — FF) и на уровне API versioning.
Давайте начнем с FF. Идея состоит в том, чтобы в коде на этапе разработки поддерживать ветвление логики посредством флагов, благодаря которым вы переключаетесь между несколькими реализациями. Вместе с флагами этот подход подразумевает наличие механизма изменения флага в рантайме — технические детали подобного, увы, вне скоупа статьи. Так получаем возможность изменять режим работы сервиса без передеплоя приложения на разные версии. Но тут важно помнить о следующих моментах:
Большое число FF усложняет код, делает менее читаемым и трудным для сопровождения, потому что возникает проблема множества if-else-ветвлений на разных уровнях приложения. Ситуация кратно усугубляется, когда появляется вложенность флагов. Последний вариант важно отдельно отслеживать и избегать.
Поведение системы начинает описываться комбинациями значений этих фича-флагов, что усложняет поддержку на проде и траблшутинг багов.
Тестирование приложения с использованием FF требует создания дополнительных сценариев. Команда должна убедиться: функционал корректно работает с разными наборами конфигураций флагов, что увеличивает размер и сложность тестового покрытия. Появляется дополнительная зависимость на системы хранения и распространения флагов, и это требует создание фолбэк-сценариев на случай отказа.
Важно проработать процесс отслеживания жизненного цикла FF для понимания актуального списка флагов в системе и своевременного удаления рудиментарных.
Альтернатива FF — подход с версионированием API. Он отличается меньшей гибкостью по сравнению с FF, что, с другой стороны, снижает вероятность попадания в ситуацию 1000 и 1-й конфигурации приложения. С API versioning важно помнить о следующем:
Поддержка нескольких версий API может привести к значительному увеличению кода и его сложности.
Частые обновления API не всегда возможны, потому что приводят к изменениям на стороне потребителей этого API.
Тестирование различных версий API требует более сложного подхода, потому что нужно гарантировать: все версии работают корректно и не приводят к багам в существующем функционале. Это приводит к увеличению объема тестов и времени их выполнения.
Использовать Continuous Review
Практика Continuous Review предполагает быстрое ревью небольших изменений в master, что созвучно идее короткоживущих feature-веток. Практика предполагает договоренность между контрибьютером и ревьюерами: контрибьютер обязуется вносить правки небольшими логически связанными порциями, а ревьюеры обещают с высоким приоритетом проводить ревью этих изменений, чтобы время жизни MR-а не превышало нескольких десятков минут. Но давайте поговорим о нюансах, связанных с CR:
Постоянные и частые ревью повышают нагрузку на разработчиков-ревьюеров из-за необходимости соблюдать требование короткого времени жизни МР-ов и частой смены рабочего контекста для погружения в ревью.
Для оптимальной работы практики нужна автоматизация процесса назначения ревьюеров и их информирования. Один из вариантов — использовать специальных ботов, которые согласно заданным правилам выбирают и назначают ревьюеров, информируют и ведут весь флоу ревью.
Часть проверок необходимо проводить в автоматическом режиме с помощью внедрения утилит статического анализа в CI/CD-пайплайне. Это позволит снять значительную часть нагрузки с ревьюеров.
Важный атрибут процесса Continuous Review — время жизни MR-а. По канонам TBD это время не должно превышать 10—20 минут. Реальность оказывается более прозаичной, и на практике удается достичь среднего времени проведения ревью около одного-двух часов.
Адаптировать CI/CD под процессы TBD
Основная идея TDB — возможность делать быстрые и частые релизы из основной ветки проекта (master-бранча). Требование можно трансформировать в два критерия: небольшое, по субъективным меркам, время пайплайна поставки фичи в прод и стабильность кода основной master-ветки. Эти два параметра — необходимые условия для того, чтобы запускать релизы в прод часто и безболезненно. Для себя мы формализовали в список требований к пайплайну:
Полное время исполнения пайплайна не превышает 30 минут. Величина в 30 минут может показаться завышенной в сравнении с идеями, изложенными в основной документации TBD. Мы же величину в 30 минут обозначаем в качестве верхней границы, которую стоит рассматривать как компромисс, на который возможно пойти для оптимизации пайплайна. Конечно же, в идеальном случае хочется стремиться к длительности пайплайна, не превышающей 10—15 минут.
Процесс релиза автоматизирован. Требование частично связано с предыдущим пунктом и нужно для того, чтобы добиться быстрых и частых релизов. Под автоматизацией тут подразумевается тестирование и всевозможные процессы оповещения соседних команд, согласования работ и так далее. Сложно говорить о стабильном времени пайплайна поставки фичи, если он содержит в себе мануальные шаги.
Настроено обязательное прохождение автоматических тестов всех уровней (unit/component/integration/e2e/benchmark/stability). Пункт нацелен на выполнение критерия, посвященного повышению стабильности master-бранча, чтобы обеспечить постоянную готовность мастера к выдаче в Prod новой версии кода. Он выставляет более высокие требования к качеству покрытия кода и стабильности тестов.
Наличие Rollback-процессов. При этом «откат» релиза реализуется с помощью переключения Feature Flag на старую ветвь исполнения бизнес-логики. Когда Rollback не работает на уровне переключения Feature Flag, CI/CD-процесс должен позволять оперативно развернуть предыдущую версию приложения (предыдущий стабильный git tag, например).
Помимо списка требований к CI/CD-пайплайну поделимся с вами инсайтами о том, что усложняет проект в части поддержки:
В стремлении оптимизировать время исполнения CI/CD-пайплана, возможно, будет полезна идея попытаться разделить общий скоуп «тяжелых» тестов — зачастую это integration, e2e, benchmark, stability — на две группы с целью запуска одной группы на MR-пайплайне, а другой — на master-пайплайне. Это решение скорее компромисс, нежели рекомендация к действию, потому что может приводить к проникновению «жуков» в master-ветку.
Рекомендуем внедрять практику использования Feature Stand-ов для изоляции тестовых прогонов. Когда используются статические стенды, эти компоненты очень быстро становятся узким местом и точкой синхронизации для параллельных потоков разработки. Честно признаем, что внедрение Feature Stand-ов — челлендж для команды и отдельная боль, о которой можно написать отдельную статью.
Полезным будет внедрить автоматическую систему контроля качества кода, своего рода Quality Gate, которая не позволит метрикам качества опускаться ниже критически важного уровня. В качестве таких метрик можно рассмотреть: процент покрытия кода тестами уровня unit и component, метрики покрытия контрактными тестами, результаты перформанс-тестов, прогоны статических анализаторов кода.
Возможно, кому-то покажется хорошей идея делать релизы не просто с head-а master-ветки, а использовать для этого git tag-и (и, соответственно, tag pipeline). Подход позволит ввести дополнительные «маркеры» в истории master-ветки, соответствующие релизам в прод, что упростит контроль и повысит осознанность в процессе релиза.
Не стоит забывать о практике shift-left для построения шагов пайплайна для обеспечения быстрой обратной связи, а значит, большей эффективности. Это важно учитывать при определении очередности стадий, например запусков статических анализаторов, служебных и платформенных задач, запусков различных видов тестов, этапов сборки и публикации проекта. При большом количестве запусков пайплайнов экономия даже нескольких десятков секунд за счет раннего обнаружения проблем может привести к значительной экономии времени и ресурсов.
Контролировать трансформации процесса разработки
Здесь, как в случае любого другого изменения процесса разработки, не стоит забывать про измерение метрик:
Development Cycle Time — верхнеуровневая, стандартная метрика, на которую может влиять очень много факторов. Набор всех этих практик не может не влиять на нее.
Delivery Time — метрика, которая точно должна пойти вниз, если мы всё верно «приготовили». Мы мерили время нахождения задач в Jira в статусе ожидания проведения релиза.
Время нахождения задач на стадии ревью — часто это больная точка в процессе разработки. Здесь тоже ожидаем снижения показателей.
Количество и критичность багов — как бы мы ни работали над скоростью поставки задач, качество все еще остается важной метрикой.
Как видите, метрики понятные и базовые, и наверняка вы уже на них смотрите. Еще в статьях о TBD нередко упоминаются DORA-метрики — можно использовать их, если вы уже с ними работаете.
Итоги
Наш вывод — не стоит идти в TBD ради наличия TBD, потому что не везде он приживется. Если он вам подходит, сам путь к нему уже может бустануть процессы и показатели, потому что по факту это набор достаточно хороших, рабочих практик.
Надеемся, наши советы и инсайты показались полезными. Поделитесь, пожалуйста, вашим опытом внедрения! Может, у вас были дополнительные шаги или решались другие проблемы?
durnoy
Кстати, не слишком давно на хабре была подробная статья про TBD: https://habr.com/ru/articles/794246/
Из вашего опыта внедрения, получили ли вы какие-нибудь преимущества после перехода? Из статьи не ясно.
P.S. я обеими руками за TBD.