Перевод неустаревающей статьи Мартина Фаулера про Непрерывную Интеграцию. Несмотря на то, что статья написана в 2006 году и упоминаемые в ней инструменты уже устарели, описание самой практики остаётся актуальной и по сей день.
Непрерывная интеграция ― это практика разработки программного обеспечения, в которой участники команды часто выполняют интеграцию своих изменений. Как правило, каждый участник выполняет интеграцию как минимум раз в день, и в итоге достигается такой режим работы, при котором интеграция выполняется несколько раз в день. Каждая интеграция проверяется путем автоматической сборки (включающей тестирование), что позволяет находить ошибки интеграции как можно скорее. Многие команды убедились, что такой подход существенно снижает количество интеграционных проблем и позволяет быстрее разрабатывать целостный программный продукт. Эта статья представляет собой обзор непрерывной интеграции, описывающий эту технику и то, как она используется в настоящее время.
1 мая 2006 г
Содержание
- Сборка фичи при непрерывной интеграции
- Практики непрерывной интеграции
- Обслуживание единого репозитория
- Автоматизация сборки
- Делайте ваши сборки самопроверяемыми
- Ежедневный коммит каждого участника в основную ветку
- Сборка основной ветки на интеграционном сервере после каждого коммита
- Быстрое исправление сломанных сборок
- Сборка должна быть быстрой
- Тестирование на копии боевой среды
- Простота получения последнего исполняемого файла для каждого участника
- Каждый может видеть, что происходит
- Автоматическое развертывание
- Преимущества непрерывной интеграции
- Внедрение непрерывной интеграции
- Мысли напоследок
- Рекомендуемая литература
Я четко помню свое первое впечатление о работе над большим проектом по разработке программного обеспечения. Я проходил летнюю стажировку в крупной английской компании, производящей электронику. Мой менеджер, по совместительству руководитель группы контроля качества, ввел меня в курс дела, после чего мы пришли на огромный склад, загроможденный коробками и наводящий уныние. Мне сказали, что этот проект разрабатывался в течение двух лет, а сейчас выполняется его интеграция, причем эта интеграция длится уже несколько месяцев. Мой гид сказал мне, что никто на самом деле не знает, как скоро она закончится. Таким образом, первый вывод, который я сделал о процессах разработки, был таким: интеграция ―долгий и непредсказуемый процесс.
Но это необязательно должно быть так. В большинстве проектов, над которыми работают мои коллеги в ThoughtWorks и многие другие разработчики по всему миру, интеграция не считается проблемой. Работа каждого отдельного разработчика лишь на несколько часов опережает общую копию проекта и может быть интегрирована в нее в считанные минуты. Любые ошибки интеграции быстро находятся и быстро исправляются.
Чтобы достичь такой организации работы, вам не нужны дорогостоящие и сложные инструменты. Ее секрет лежит в простой практике, когда каждый участник команды часто (как правило, ежедневно) интегрирует свои изменения в репозиторий хранения исходного кода.
Наша первая статья о непрерывной интеграции описывает наш опыт использования этой практики в одном из проектов ThoughtWorks в 2000 году, который Мэтт помог собрать воедино.
Когда я описывал эту практику разным людям, я обычно встречал две реакции: «Это не будет работать (здесь)» или «Вряд ли это что-то изменит». Но когда люди пробовали эту методику, они понимали, что она гораздо проще, чем кажется, и, кроме того, она существенно влияет на процесс разработки. Поэтому третья реакция была такой: «Да, мы это делаем ― как же мы жили без этого раньше?»
Термин «Непрерывная интеграция» впервые использовал Кент Бек в своей книге Экстремальное программирование, называя им одну из двенадцати оригинальных методик процесса разработки. Когда я начал работать в ThoughtWorks, то взялся продвигать эту методику в своем проекте. Мэттью Фоммель помог мне перейти от неясных призывов к конкретным действиям, и в итоге мы увидели, как наш проект перешел от редких и сложных интеграций к простой процедуре, которую я описал выше. Мы с Мэттью описали наш опыт в первой версии этой статьи, которая стала одной из самых популярных статей на моем сайте.
Хотя непрерывная интеграция ― практика, не требующая специальных инструментов, мы обнаружили, что в ней полезно использовать сервер непрерывной интеграции. Самый известный из подобных серверов ― это CruiseControl, решение с открытым исходным кодом, созданное несколькими сотрудниками ThoughtWorks и теперь поддерживаемое большим сообществом. С тех пор появилось еще несколько серверов непрерывной интеграции (CI-серверов), как с открытым кодом, так и коммерческих, в частности, Cruise, созданный в ThoughtWorks Studios.
Сборка фичи при непрерывной интеграции
Наиболее простой для меня способ объяснить вам, что такое непрерывная интеграция и как она работает, на примере разработки небольшой фичи. Давайте предположим, что мне нужно что-то доработать в программном обеспечении, неважно, что именно, но будем считать, что фича небольшая и может быть разработана за несколько часов. (Позже мы рассмотрим более долговременные задачи и связанные с ними проблемы).
Я начинаю с получения актуальной копии интегрированного исходного кода продукта на мой компьютер. Для этого я использую систему управления исходным кодом, получая рабочую копию из основной ветки.
Предыдущий абзац будет понятен тем, кто использует системы управления исходным кодом, но остальным читателям может показаться непонятным набором слов. Поэтому давайте я кратко объясню, что имею в виду. Система управления исходным кодом хранит весь код по проекту в репозитории. Текущее состояние системы обычно называют «основной веткой». Каждый разработчик в любой момент может получить управляемую копию основной ветки на свою машину. Копия на машине разработчика называется «рабочей копией». (На самом деле, большую часть времени вы занимаетесь тем, что обновляете свою рабочую копию из основной ветки. По сути, это то же самое).
Затем я беру свою рабочую копию и вношу в нее все изменения, необходимые для решения моей задачи. В частности, изменяю рабочий код и добавляю или изменяю автоматизированные тесты. Непрерывная интеграция подразумевает использование большого количества тестов, автоматически встроенных в продукт: в таких случаях говорят о самотестируемом коде. Для этого часто используется популярный тестовый фреймворк XUnit.
Когда я завершил работу (а также на некоторых этапах работы, пока она еще не завершена), я запускаю автоматическую сборку продукта на своей рабочей машине. Это означает что код из моей рабочей копии, компилируется и линкуется в исполняемый файл, а затем запускает автоматизированные тесты. И только если сборка и тестирование прошли без ошибок, полученный билд считается работоспособным.
Имея работоспособную сборку, я могу подумать о том, чтобы выполнить коммит моих изменений в репозиторий. На этом этапе следует иметь в виду, что другие люди могли внести изменения в основную ветку (и, скорее всего, они их уже внесли), прежде чем я получил возможность выполнить свой коммит. Поэтому сначала я обновляю свою рабочую копию, добавив их изменения, а затем снова выполняю сборку. Если их изменения вступают в конфликт с моими, я получу ошибку либо во время компиляции, либо во время тестирования. В этом случае я должен исправить эту ошибку и повторять свои действия до тех пор, пока не смогу собрать рабочую копию, которая будет корректно синхронизирована с основной веткой.
Как только я получу сборку рабочей копии, которая корректно синхронизируется с основной веткой, я могу выполнить коммит. После этого основная ветка обновляется в репозитории.
Однако после коммита моя работа не завершена. В этот момент мы снова выполняем сборку, теперь уже на сервере интеграции, на кодовой базе основной ветки. И только если эта сборка проходит успешно, можно сказать, что мои изменения завершены. Всегда есть вероятность, что я что-то упустил, работая на своем компьютере, или что репозиторий не был корректно обновлен. Только когда добавленные мной изменения успешно собираются на сервере интеграции, моя работа считается завершенной. Эту интеграционную сборку я могу выполнить вручную или автоматически при помощи Cruise.
Если между изменениями, выполненными двумя разработчиками, возникает конфликт, он обычно проявляется в тот момент, когда второй разработчик собирает свою обновленную рабочую копию. Если же конфликт не проявился на этом этапе, он проявится во время интеграционной сборки. В любом случае ошибка находится быстро. На этом этапе самая важная задача ― исправить ошибку и добиться, чтобы сборка вновь начала работать корректно. В окружении непрерывной интеграции у вас не должно быть неудачных интеграционных сборок, которые долго не исправляются. В хорошей команде бывает по несколько рабочих сборок в день. Неудачные сборки случаются время от времени, но исправляются они очень быстро.
В результате у вас есть стабильный программный продукт, который корректно работает и содержит мало ошибок. Каждый участник команды начинает разработку с этой стабильной базовой копии, и никогда не удаляется от нее настолько, чтобы интеграция с ней заняла много времени. На поиск ошибок тратится значительно меньше времени, потому что они проявляются быстро.
Практики непрерывной интеграции

Описанная выше история кратко рассказывает о том, что такое непрерывная интеграция (Continious integration, CI), и как она работает в повседневной жизни. Чтобы понять, как обеспечить безотказную работу этого процесса, этого описания, очевидно, недостаточно. Далее я расскажу о ключевых практиках, повышающих эффективность непрерывной интеграции.
Обслуживание единого репозитория
В проектах разработки программного обеспечения обычно используется большое количество файлов, которые надо объединить, чтобы собрать продукт. Отслеживать эти файлы ― самая трудоемкая задача, особенно, если задействовано несколько человек. Поэтому неудивительно, что за последние годы команды разработки программного обеспечения создали ряд инструментов для управления файлами. Эти инструменты называют, например, инструментами управления исходным кодом, системами контроля версий или репозиториями. Они стали неотъемлемой частью большинства проектов разработки. Печально и удивительно то, что они пока еще не стали частью всех проектов. Иногда (хоть и редко) я встречаюсь с проектами, которые не используют такие системы, а вместо них используют запутанную комбинацию локальных и общих дисков.
Поэтому, в качестве простого первого шага, убедитесь, что у вас есть подходящая система управления исходным кодом. Цена таких систем ― не проблема, так как многие из них имеют открытый исходный код. В настоящее время наилучшим репозиторием с открытым исходным кодом можно назвать Subversion. (Прежняя популярная система с открытым исходным кодом CVS по-прежнему широко распространена, и она лучше, чем ничего. Но Subversion ― все же наилучший выбор в наши дни). Любопытно, что многие коммерческие инструменты управления исходным кодом, по словам разработчиков, нравятся им меньше, чем Subversion. Единственный инструмент, за который, по мнению многих моих собеседников, стоит заплатить — это Perforce.
Начиная использовать систему управления исходным кодом, убедитесь, что все хорошо знают, что исходный код надо получать именно из нее. Никто не должен спрашивать: «Где находится файл foo-whiffle?» Все должны хорошо ориентироваться в репозитории.
Хотя многие команды используют репозитории, я часто вижу, что они помещают в них не всё. И это распространенная ошибка. Если вы используете репозиторий, вы будете помещать в него код. Но в нем также должно быть и все необходимое для успешной сборки: тестовые скрипты, property-файлы, схема базы данных, установочные скрипты и сторонние библиотеки. Я знаю некоторые проекты, в которых компиляторы также помещались в репозитории (что было особенно актуально во время использования первых компиляторов C++, которые иногда вели себя непредсказуемо). Главное правило: вы должны быть в состоянии включиться в работу по проекту с «чистым» компьютером, извлечь на него рабочую копию и полностью собрать систему. На «чистом» компьютере должен быть установлен лишь минимум программных продуктов ― как правило, больших по размеру, сложных в установке и стабильных в работе. Например, операционная система, среда разработки на Java или основная система баз данных.
Вы должны помещать все необходимое для сборки в систему управления исходным кодом, однако вы можете хранить в них также и другие данные, с которыми вы обычно работаете. Хорошая практика ― помещать туда конфигурации IDE, так как это простой способ помочь другим разработчикам установить IDE с такими же настройками.
Одной из фич систем управления версиями можно назвать то, что они позволяют вам создавать новые ветки и управлять различными потоками разработки. Это полезная и даже необходимая фича, но зачастую она используется избыточно, создавая лишние проблемы. Сведите использование веток к минимуму. В частности, вам нужна основная ветка ― единственная ветка проекта, который находится в разработке. Фактически, большую часть времени все разработчики будут работать c этой основной веткой. (Имеет смысл создавать ветки для исправлений ошибок, предварительных релизов продукта и временных экспериментов).
Если обобщить, вам необходимо хранить в системе управления исходным кодом все, что нужно для того чтобы собрать всё, но ничего из того, что вы собираете. Некоторые разработчики хранят в системе управления исходным кодом продукты сборки, но я считаю это плохой практикой и признаком более глубокой проблемы ― как правило, неспособности надежным способом пересобрать продукт.
Превращение исходного кода в работающую систему может быть сложным процессом, который включает в себя компиляцию, копирование файлов, загрузку схем в базы данных и так далее. Однако, как и большая часть задач в этой части разработки программного обеспечения, этот процесс можно автоматизировать, а значит, его нужно автоматизировать. Просить людей набирать странные команды или щелкать мышкой по диалоговым окнам ― напрасная трата времени и создание благодатной почвы для возникновения ошибок.
Общая черта описанных систем ― наличие автоматизированных сред для сборки. В Unix десятилетиями использовалась утилита make, Java-сообщество разработало Ant, сообщество .NET ранее использовало Nant, а сейчас ― MSBuild. Убедитесь, что вы можете собрать и запустить свою систему с помощью этих инструментов, всего одной командой.
Распространенной ошибкой также можно назвать ситуацию, когда в автоматическую сборку включается не все. Сборка должна включать в себя получение схемы базы данных из репозитория и ее создание в среде выполнения. Я несколько переработаю основное правило, о котором писал ранее: вы должны взять «чистый» компьютер, загрузить на него исходный код из репозитория, запустить одну команду и получить на своем компьютере работающую систему.
Существует множество скриптов сборки на любой вкус, и они бывают специфичны для той или иной платформы или сообщества, хотя это и необязательно. Хотя в большинстве наших Java-проектов используется Ant, в некоторых из них используется Ruby (надо отметить, что система Ruby Rake ― очень хороший инструмент для сборки). Сборка ранних версий проекта Microsoft COM была очень успешно автоматизирована с помощью Ant.
Большая сборка часто занимает много времени, и вы не должны выполнять все ее шаги каждый раз, когда внесли небольшое изменение. Поэтому хороший инструмент сборки в ходе процесса анализирует, что именно необходимо изменить. Обычно это делается путем проверки дат исходных и объектных файлов и компиляции только в том случае, если исходные файлы изменены позже. В этом случае усложняются зависимости: если изменяется один файл, то все файлы, зависимые от него, также могут нуждаться в повторной сборке. Некоторые компиляторы могут отслеживать подобные ситуации и управлять ими, а некоторые ― нет.
Вам может понадобиться собирать разные типы файлов, в зависимости от того, что вы хотите получить. Вы можете собрать систему с тестовым кодом или без него, а также с различными наборами тестов. Некоторые компоненты могут быть собраны отдельно. Скрипт сборки должен позволять вам собирать различные типы файлов для различных целей.
Многие используют IDE (интегрированные среды разработки), а в большинстве IDE предусмотрен тот или иной встроенный инструмент управления сборкой. Однако подобные инструменты всегда являются собственностью IDE и часто бывают нестабильными. Более того, для их работы необходима IDE. Для пользователей IDE может быть вполне естественно устанавливать файлы своего проекта и использовать их для индивидуальной разработки. Однако необходимо также иметь основную сборку, которая может использоваться на сервере и запускаться с помощью других скриптов. В частности, для Java-проектов характерна ситуация, когда разработчики выполняют сборку в своих IDE, но для основной сборки используется Ant, чтобы гарантировать, что сборку можно запустить на сервере разработки.
Делайте ваши сборки самопроверямыми
Традиционно, сборка включает в себя компиляцию, линкование и все прочие дополнительные действия, необходимые для получения исполняемой программы. В результате программу можно запустить, но это еще не означает, что она будет делать то, что нужно. Современные статически типизированные языки могут отлавливать много ошибок, но большая часть ошибок все равно проскальзывает в программу.
Хороший способ отловить ошибки быстро и эффективно ― включить автоматизированные тесты в процесс сборки. Тестирование, конечно, не бывает безупречным, но все же оно помогает отловить многие ошибки, а значит, оно полезно. В частности, развитие экстремального программирования (XP) и разработки через тестирование (TDD) внесло большой вклад в популяризацию самотестируемого кода и, в результате, многие успели оценить преимущества этой техники.
Те, кто регулярно читают мои статьи, знают, что я убежденный приверженец как TDD, так и XP, однако я хотел подчеркнуть, что ни один из этих подходов не требуется для того, чтобы оценить преимущества самотестируемого кода. Как правило, при каждом из этих подходов тесты пишутся еще до того, как будет написан код, к которому они будут применяться. В этом случае тесты нужны скорее для исследования дизайна системы, чем для поиска ошибок. Это очень хорошая практика, но она необязательна для целей непрерывной интеграции, где к самотестируемому коду предъявляются не такие строгие требования. (И все же мой любимый способ написания самотестируемого кода ― подход TDD.)
Для создания самотестируемого кода вам необходим набор автоматизированных тестов, которые могут проверять большую часть кода на наличие ошибок. Эти тесты должны запускаться с помощью простой команды и быть самопроверяющими. Результат запуска тестов должен показывать, какие из них окончились неудачей. Для сборки с самотестируемым кодом неудачное завершение теста должно приводить к неудачной сборке.
Благодаря развитию TDD в последние годы популярным стало использование инструментов семейства XUnit с открытым исходным кодом, которые идеально подходят для тестирования такого типа. Инструменты XUnit очень хорошо себя зарекомендовали в ThoughtWorks, и я всегда и всем предлагаю их использовать. С помощью этих инструментов, впервые примененных Кентом Беком, можно легко создать полностью самотестируемую среду.
Безусловно, инструменты XUnit ― отправная точка для того, чтобы сделать код самотестируемым. Обратите также внимание на другие инструменты, предназначенные больше для сквозного тестирования. Их сейчас довольно много, и к ним относятся FIT, Selenium, Sahi, Watir, FITnesse и множество других, которые я не буду перечислять здесь.
Конечно, не стоит рассчитывать, что тесты найдут абсолютно все ошибки. Как говорится, тестирование не доказывает отсутствие ошибок. Но идеального тестирования и не требуется, чтобы получить отдачу от самотестируемой сборки. Неидеальные тесты, запускаемые часто ― это гораздо лучше, чем идеальные тесты, которые никогда не будут написаны.
Ежедневный коммит каждого участника в основную ветку
Интеграция — это, в первую очередь, коммуникация. Интеграция позволяет разработчикам сообщать другим разработчикам о своих изменениях. Частая коммуникация позволяет людям быстро узнавать о разрабатываемых изменениях.
Одно из предварительных условий для коммита разработчика в основную ветку ― корректно собранный код. Это, конечно, включает в себя прогон тестов на сборке. Как и при любом коммите, разработчик сначала обновляет свою рабочую копию до основной ветки, разрешает все конфликты с основной веткой, а затем выполняет сборку на своем компьютере. Если сборка прошла успешно, можно выполнять коммит в основную ветку.
Если это делается часто, может возникнуть конфликт между изменениями двух разработчиков. Чтобы исправить проблему быстро, главное ― быстро ее обнаружить. Если разработчики выполняют коммиты каждые несколько часов, конфликт может быть обнаружен через несколько часов после возникновения. В этот момент его легко исправить, так как изменений за это время произошло еще немного. Конфликты, которые не были обнаружены в течение нескольких недель, очень трудно исправлять.
Тот факт, что вы выполняете сборку сразу после обновления своей рабочей копии, означает, что вы обнаруживаете конфликты компиляции, а также буквальные конфликты. С самотестируемой сборкой вы также можете найти конфликты в исполнении кода. Такие конфликты особенно трудно обнаружить, особенно, если они находятся в коде в течение долгого времени. Так как между коммитами проходит лишь несколько часов изменений, то мест, в которых может скрываться проблема, не так много. Кроме того, поскольку изменений было немного, вы можете выполнить diff-отладку, чтобы быстрее найти ошибку.
Мое основное правило ― каждый разработчик должен ежедневно выполнять коммит в репозиторий. На практике, часто оказывается полезнее, если разработчики выполняют коммиты еще чаще. Чем чаще вы выполняете коммиты, тем меньше остается мест для обнаружения конфликтов, и тем быстрее вы можете их исправить.
Частые коммиты стимулируют разработчиков разбивать свою работу на небольшие фрагменты по несколько часов. Это помогает отслеживать прогресс и создает ощущение прогресса. Некоторые разработчики на первых этапах не чувствуют уверенности в том, что могут создать что-то значимое за несколько часов, но в этом случае им может помочь наставничество и практика.
Сборка основной ветки на интеграционном сервере после каждого коммита
С ежедневными коммитами в команде появляются частые и протестированные сборки. Стоит отметить, что при этом основная ветка остается в стабильном состоянии. На практике, однако, проблемы все еще случаются. Одна из причин ― в дисциплине, разработчики не выполняют обновление и сборку перед коммитом. Другая причина ― разное окружение на компьютерах разработчиков.
В итоге вы должны убедиться, что регулярные сборки выполняются на интеграционном сервере, и только если эти интеграционные сборки проходят успешно, коммит считается выполненным. Поскольку разработчики отвечают за свои коммиты, они сами должны отслеживать сборку в основной ветке и исправлять ошибки в случае их возникновения. Отсюда можно сделать вывод, что вы не должны уходить домой, пока в основной ветке не пройдет успешная сборка, включающая все коммиты, добавленные вами за день.
Чтобы это гарантировать, есть два основных способа: использование ручной сборки и сервер непрерывной интеграции.
Ручную сборку описать легче. В сущности, это похоже на локальную сборку, которую разработчик выполняет перед коммитом в репозиторий. Разработчик заходит на интеграционный сервер, проверяет head основной ветки (которая содержит его последний коммит) и запускает интеграционную сборку. Далее он следит за ее прогрессом, и, в случае ее успешного прохождения, считает, что коммит выполнен успешно. (Смотрите также описаниеэтого процесса в статье Джима Шора.)
Сервер непрерывной интеграции выступает в роли монитора репозитория. Каждый раз по завершении коммита сервер автоматически получает исходный код на интеграционный компьютер, запускает сборку и уведомляет автора коммита о ее результате. Коммит не считается завершенным успешно, пока его автор не получит уведомление (обычно, по электронной почте).
В ThoughtWorks мы очень активно используем серверы непрерывной интеграции ― более того, здесь мы изначально разрабатывали CruiseControl и CruiseControl.NET, широко используемые CI-серверы с открытым исходным кодом. После этого мы также создали коммерческий CI-сервер Cruise. Мы используем CI-серверы практически во всех проектах и очень довольны результатами.
Но не все предпочитают использовать CI-серверы. Джим Шор приводит хорошо аргументированное объяснение, почему он предпочитает ручной подход. Я согласен с ним в том смысле, что CI ― это гораздо больше, чем просто установка определенного программного обеспечения. Необходимо придерживаться всех описанных здесь практик, чтобы непрерывная интеграция была эффективной. В то же время многие команды, использующие подход CI, считают CI-сервер полезным инструментом.
Во многих организациях сборки выполняются регулярно по расписанию, например, по ночам. Это не то же самое, что непрерывная сборка, и этого недостаточно для непрерывной интеграции. Главный смысл непрерывной интеграции в том, чтобы выявлять проблемы как можно быстрее. Ночные сборки означают, что проблемы остаются незамеченными в течение дня, пока кто-нибудь не обнаружит их. А поскольку они находятся в системе так долго, то уходит много времени для их поиска и исправления.
Быстрое исправление сломанных сборок
Ключевой смысл непрерывной сборки заключается в том, что если сборка основной ветки завершилась ошибкой, эту ошибку необходимо исправить сразу. Главное при работе с CI ― то, что вы всегда ведете разработку на известной и стабильной базе. Сама по себе неудачная сборка основной ветки ― не слишком большая проблема, хотя она может свидетельствовать о том, что разработчики недостаточно заботятся об обновлении и сборке на своих компьютерах прежде, чем сделать коммит. Но если сборка основной ветки завершилась ошибкой, важно исправить эту ошибку как можно скорее.
Я помню фразу Кента Бека: «Ни одна задача не может быть приоритетнее, чем исправление сборки». Это не значит, что все участники команды должны немедленно прекратить свою текущую работу, чтобы заняться исправлением сборки. Как правило, достаточно пары человек, чтобы устранить проблему. Это означает сознательное присвоение наивысшего приоритета и срочности задаче исправления сборки.
Зачастую самый быстрый способ исправить сборку ― откатить последний коммит из основной ветки, вернув систему к последнему состоянию, о котором известно, что сборка в нем была успешной. Конечно, команде не стоит пытаться выполнять отладку в сломанной основной ветке. Даже если причина поломки сразу известна, откатитесь к основной ветке и запустите отладку проблемы на рабочей станции, на которой велась разработка.
Чтобы по возможности избежать нарушений сборки основной ветки, вы можете попробовать использовать pending head.
Когда команды вводят CI, им часто бывает очень сложно во всем разобраться. На ранних стадиях участникам команды может быть трудно выработать постоянную привычку работать со сборками основной ветки, особенно, если они работают на существующей базе кода. Терпение и постоянное применение этой методики может помочь преодолеть эти трудности, так что не падайте духом.
Главная цель непрерывной интеграции ― получить быстрый фидбек. Ничто так не вредит проведению CI, как слишком долгая сборка. Здесь я хотел бы немного порассуждать о том, что можно считать долгой сборкой. Большинство моих коллег считают сборку, которая выполняется больше часа, абсолютно недопустимой. Я помню, как некоторые команды мечтали о быстрой сборке, но все же случается наблюдать когда трудно получать сборку с желаемой скоростью.
Однако для большинства проектов методика «десятиминутной сборки», используемая в экстремальном программировании, вполне приемлема. Для большинства современных проектов она достижима. На достижении быстрой сборки действительно стоит сосредоточить свои усилия, так как каждая минута, которую вы экономите в процессе сборки ― это дополнительная минута для каждого разработчика, выполняющего коммит. А поскольку в CI используются частые коммиты, это позволит сэкономить много полезного времени.
Если сейчас вы вынуждены наблюдать сборку, которая продолжается один час, задача ускорения сборки может показаться вам пугающей. Пугающей может быть даже задача работы над новым проектом и ускорения этой работы. По крайней мере, для корпоративных приложений мы может назвать обычное «узкое место». Это тестирование, а особенно те тесты, которые требуют использования внешних служб, например, баз данных.
Вероятно, самым важным шагом будет начать работу по настройке delivery pipeline. Основная идея delivery pipeline (его также называют build pipeline или поэтапной сборкой) заключается в том, что несколько сборок выполняются последовательно. Коммит в основную ветку запускает первую сборку (назовем ее сборкой коммита). Сборка при коммите ― это сборка, необходимая, когда кто-либо коммитит в основную ветку. Сборка при коммите должна быть быстрой. Хотя высокая скорость также имеет и ряд недостатков, которые уменьшают вероятность выявления ошибок. Основная сложность ― найти баланс между необходимостью выявлять ошибки и скоростью. Хорошая сборка по коммиту должна быть достаточно стабильной, чтобы другие люди могли с ней работать.
Джез Хамбл и Дэйв Фарли развили эти идеи в понятие «непрерывного развертывания», которое включает в себя более подробное рассмотрение «delivery pipeline». Их книга Непрерывное развертывание была заслуженно удостоена Премии Джолта в 2011 году.
Если сборка при коммите прошла успешно, значит, остальные разработчики могут спокойно работать с кодом. Однако есть и другие, более медленные, тесты, которые вы можете начать выполнять. На дополнительных компьютерах можно проводить дальнейшие процедуры тестирования сборки, которые выполняются дольше.
Простой пример такого подхода ― delivery pipeline, состоящий из двух этапов. Первый этап ― компиляция и запуск локальных юнит-тестов с использованием заглушек вместо баз данных. Такие тесты прогоняются очень быстро, в пределах десяти минут. Однако они не могут обнаружить ошибки, влияющие на взаимодействия более высокого уровня, например, использующие настоящую базу данных. На втором этапе запускается другой набор тестов, которые затрагивают настоящую базу данных и покрывают более сквозное поведение продукта. Выполнение такого набора тестов может занять пару часов.
В этом сценарии разработчики используют первый этап в качестве сборки при коммите и опираются на него в основном цикле CI. Сборка, соответствующая второму этапу, запускается тогда, когда это возможно, с использованием исполняемого файла на базе последнего успешного коммита для дальнейшего тестирования. Если эта вторая сборка завершается ошибкой, это не означает, что нужно «все остановить», как в первом случае, но команда все же должна сосредоточиться на скорейшем исправлении этой ошибки, в то время как первая сборка по коммиту продолжает работать. Как и в этом примере, последующие сборки — это, как правило, обычные тесты, и на данном этапе это тесты, которые замедляют работу.
Если вторичная сборка выявила ошибку, это означает, что сборку при коммите имеет смысл протестировать еще раз. Постарайтесь гарантировать, что любая ошибка найденная на поздней стадии, приведет к дополнительному тестированию сборки по коммиту, в которой она была найдена, чтобы в сборке по коммиту ошибка оставалась исправленной. Таким образом тесты запускаемые при . коммите усиливаются каждый раз, когда они запускаются. В некоторых случаях невозможно создать быстрый тест, который обнаружит ошибку. Тогда вам, возможно, стоит протестировать соответствующее условие во время вторичной сборки. В большинстве случаев, к счастью, вы все же можете добавить все необходимые тесты в сборку по коммиту.
В этом примере мы рассмотрели конвейер, состоящий из двух этапов, но по такому же принципу можно добавлять к нему новые этапы. Сборки, следующие за сборкой по коммиту, можно проводить параллельно, то есть, если у вас есть вторичные тесты, на прогон которых уходит два часа, вы можете ускорить работу, прогоняя их на двух компьютерах (по половине тестов на каждом компьютере). Используя параллельные вторичные сборки, как в описанном случае, вы можете добавлять в обычную процедуру сборки автоматизированное тестирование, в частности, тестирование производительности.
Тестирование на копии боевой среды
Цель такого тестирования ― выявить при контролируемых условиях любую проблему системы в рабочей среде. Значительная часть этого процесса ― среда, в которой будет запущена рабочая система. Если вы проводите тестирование в другой среде, то любое различие приводит к риску того, результаты этого тестирования, будут отличными от поведения в боевой среде.
Поэтому необходимо настроить тестовую среду так, чтобы она была по возможности точной копией рабочей среды. Используйте те же базы данных тех же версий и ту же версию операционной системы. Добавьте все библиотеки, используемые в рабочей среде, в тестовую среду, даже если система на самом деле в них не нуждается. Используйте те же IP-адреса и порты и то же аппаратное обеспечение.
Конечно, в реальной жизни возможны ограничения. Если вы пишите ПО для настольных компьютеров, не имеет смысла тестировать его на клонах всех возможных настольных компьютеров со всеми возможными сторонними программами, которые запускают разные люди. Кроме того, может оказаться слишком дорого клонировать некоторые рабочие среды (хотя я нередко встречал случаи ложной экономии, когда люди отказывались от клонирования умеренно дорогостоящих сред). Несмотря на эти ограничения, вашей целью по-прежнему остается максимально полное дублирование реальной рабочей среды и понимание рисков, которые вызывает любое различие между тестовой и рабочей средами.
Если вы используете простую установку без сложных связей, вы можете запускать свою сборку по коммиту в полностью идентичной среде. Однако иногда вам может понадобиться использование тестовых дублеров из-за медленного или прерывистого ответа системы. В итоге общей практикой стало использование изолированной среды для быстрого запуска тестов сборки по коммиту, а копии рабочей среды ― для вторичного тестирования.
Я заметил рост интереса к виртуализации для создания тестовых сред. Виртуальные машины можно сохранять со всеми необходимыми элементами, благодаря встроенным возможностям виртуализации. В результате установка последней сборки и прогон тестов становятся относительно простой задачей. Более того, таким способом вы можете запускать несколько тестов на одном компьютере или эмулировать сеть из нескольких компьютеров на одном компьютере. Поскольку снижение производительности, характерное для виртуализации, в последнее время уменьшается, применение этот вариант становится все более целесообразным.
Простота получения последнего исполняемого файла для каждого участника
Одна из главных трудностей процесса разработки программного обеспечения ― убедиться, что вы создаете нужный продукт. Мы заметили, что людям бывает очень сложно заранее корректно сформулировать, что они хотят. Гораздо проще посмотреть на что-то, что требует доработки, и сформулировать, что именно надо изменить. Процессы гибкой разработки явно учитывают эту особенность человеческого поведения и извлекают из нее выгоду.
Чтобы такие процессы работали эффективно, каждый участник проекта должен иметь возможность получить последний исполняемый файл и запустить его: для демонстраций, для исследовательского тестирования или просто для просмотра изменений за неделю.
Добиться этого очень просто: убедитесь, что у вас есть хорошо известное место, где каждый участник может найти последний исполняемый файл. В подобном хранилище может быть полезно размещать несколько исполняемых файлов. В этом случае для тестирования коммита следует использовать самый последний исполняемый файл. Этот файл должен быть очень стабильным, чтобы гарантировать надежность коммита.
Если вы работаете по четко определенным итерациям, имеет смысл помещать сборки в хранилище, в конце каждой итерации. В частности, для демонстраций необходимо программное обеспечение с хорошо знакомыми функциями, поэтому стоит пожертвовать чем-то более поздним в пользу того с чему тот, кто демонстрирует хорошо знаком.
Каждый может видеть, что происходит
Непрерывная интеграция — это, прежде всего, коммуникация, поэтому необходимо гарантировать, чтобы каждый ее участник мог легко увидеть текущее состояние системы и внесенные в нее изменения.
В частности, очень важно, чтобы все знали, в каком состоянии находится сборка основной ветки. Если вы используете Cruise, то на встроенном в него сайте вы можете посмотреть, выполняется ли в данный момент сборка, и узнать состояние последней сборки основной ветки. В некоторых командах эту информацию делают еще более наглядной, встраивая в систему сборки монитор, непрерывно показывающий состояние сборки. Когда сборка выполняется, на мониторе обычно показываются зеленые огоньки, а при возникновении ошибок ― красные. Также популярен подход, когда используются красные и зеленые лава-лампы, которые не только показывают состояние сборки, но и как долго сборка находится в том или ином состоянии. Пузырьки в красной лампе означают, что сборка сломана уже долгое время. Каждая команда сама выбирает, какие датчики состояния сборки использовать ― и хорошо бы здесь можно применять игровой подход (недавно я видел, как кто-то экспериментировал с танцующим кроликом).
Если вы используете процесс непрерывной интеграции вручную, такая наглядность по-прежнему важна. Монитор на физическом компьютере сборки может показывать состояние сборки основной ветки. Иногда на стол разработчика, выполняющего очередную сборку, помещают какой-либо опознавательный знак или предмет (это может быть что-то глупое и смешное, например, резиновая курица). Иногда разработчики оповещают команду об успешной сборке каким-либо звуком (например, звоном колокольчика).
Но, конечно, сайты на CI-сервере могут дать гораздо больше информации. Cruise показывает не только, кто в данный момент выполняет сборку, но и какие изменения были внесены. Кроме того, Cruise показывает историю изменений, позволяя участникам команды всегда быть в курсе недавней активности в проекте. Я знаю руководителей команд, которые используют подобные сайты, чтобы быть в курсе того, чем занимаются участники команды и какие изменения вносятся в систему.
Другое преимущество использования сайтов заключается в том, что удаленно работающие участники команды могут быть в курсе статуса проекта. В общем случае, я предпочитаю ситуацию, когда все, кто активно работает над проектом, сидят рядом, но иногда о состоянии проекта важно узнавать людям извне. Кроме того, некоторым группам может быть полезно собирать воедино информацию о сборке по нескольких проектам, чтобы получать статус различных проектов простым и автоматизированным способом.
Эффективно отображать информацию можно не только на экране компьютера. В одном из проектов, внедряющих CI, мы использовали один из моих любимых способов отображения информации. По ряду причин, о которых долго рассказывать, в этом проекте не удавалось создавать стабильные сборки. Мы повесили на стену календарь, на котором был показан весь год, и каждый день был обведен маленьким квадратиком. Каждый день группа тестирования должна была помещать на соответствующий день зеленый стикер, если они получили стабильную сборку, которая прошла все тесты при коммите, и красный стикер ― в противном случае. Спустя некоторое время календарь выявил состояние процесса сборки, показывая стабильное улучшение. Вскоре зеленых квадратиков стало так много, что календарь мы убрали, так как он выполнил свою функцию.
Для использования непрерывной интеграции вам понадобится несколько сред ― одна для запуска тестов по коммиту, и несколько для запуска вторичных тестов. Поскольку вам придется перемещать исполняемые файлы между этими средами, вы наверняка захотите делать это автоматически. Поэтому важно создать скрипты, которые позволят вам быстро развертывать приложение в любой среде.
Естественным следствием такого подхода станет наличие скриптов, которые с такой же легкостью позволят вам развернуть приложение и в рабочей среде. Вам необязательно развертывать приложение в рабочей среде каждый день (хотя я встречал проекты, где делают именно так), но автоматическое развертывание поможет вам ускорить этот процесс и сократить количество ошибок. Кроме того, это недорого, так как этот скрипт будет использовать те же средства, что и скрипты для развертывания в тестовых средах.
Многие беспокоятся о том, как поступать с базами данных при частых релизах. Мы с Прамодом Садаладжем написали эту статью, чтобы рассказать о том, как выполнять автоматический рефакторинг и миграцию баз данных.
Если вы автоматически устанавливаете приложение в боевой среде и при этом автоматизировали что-то лишнее, необходимо подумать о возможности автоматического отката. Время от времени случаются неудачи, и если дурно пахнущее коричневое вещество начинает попадать на вращающийся металл, хорошо бы быть в состоянии быстро вернуться к последнему известному стабильному состоянию. Кроме того, возможность автоматического отката снижает напряжение во время развертывания, и поощряет людей выполнять развертывание чаще и, таким образом, быстрее доставлять пользователям новые функции. (Сообщество Ruby on Rails разработало инструмент Capistrano, который, в частности, может эффективно выполнять подобные откаты).
В кластерных средах мне приходилось наблюдать поэтапное развертывание(rollout), когда новое программное обеспечение развертывается на одном узле за определенный период времени, постепенно заменяя установленное приложение в течение нескольких часов.
В частности, я встречал интересную вариацию этого подхода при развертывании публичного веб-приложения ― когда тестовая сборка постепенно развертывается для определенного набора пользователей. В этом случае команда может посмотреть, как используется тестовая сборка, прежде чем развернуть ее для всех пользователей. Такой подход позволяет вам протестировать новые функции и пользовательский интерфейс, прежде чем сделать окончательный выбор. Автоматическое развертывание в связке с хорошей CI-дисциплиной ― необходимы для успешного выполнения такой работы.
Преимущества непрерывной интеграции
В общем и целом, я думаю, что самое большое и широко известное преимущество непрерывной интеграции заключается в снижении рисков. Я все еще мысленно возвращаюсь к раннему проекту разработки, о котором я упоминал в первом абзаце этой статьи. Тогда участники команды находились на последнем этапе (как они надеялись) большого проекта, но при этом понятия не имели, сколько времени займет этот этап.
Проблема отложенной интеграции заключается в том, что очень сложно предсказать, сколько времени она займет, и, что еще хуже, очень сложно понять, как далеко вы продвинулись в этом процессе. В результате вы оказываетесь в «слепом пятне» во время самой напряженной стадии проекта, даже если ваш проект ― один из тех редких случаев, когда сроки не превышены.
Непрерывная интеграция полностью избавляет вас от этой проблемы. Интеграция длится недолго, и «слепые пятна» при этом отсутствуют. В любой момент времени вы знаете, на какой стадии вы находитесь, что работает, а что нет, и какие неисправленные дефекты есть в вашей системе.
Дефекты — это та самая неприятная вещь, которая разрушает ваше чувство уверенности, мешает выполнению сроков, и снижает вашу репутацию. Дефекты в развернутом программном продукте приводят к тому, что пользователи злятся на вас. Дефекты в текущей работе создают вам препятствия и мешают вам добиться корректной работы остальной части программного продукта.
Непрерывная интеграция не избавляет вас от дефектов, но существенно упрощает их обнаружение и исправление. В этом отношении она имеет много общего с самотестируемым кодом. Если вы внесли в код ошибку и быстро ее обнаружили, вам будет намного проще избавиться от нее. Поскольку вы изменили при этом лишь маленький фрагмент системы, вам не придется долго ее искать. А поскольку над этим фрагментом системы вы только что работали, вы еще хорошо его помните, что также облегчает поиск ошибки. Вы также можете выполнить diff-отладку ― сравнение текущей версии системы с предыдущей, в которой этой дефекта еще не было.
Дефекты также имеют свойство накапливаться. Чем больше у вас ошибок, тем сложнее исправлять каждую из них. Отчасти это объясняется тем, что дефекты взаимодействуют между собой, и тогда результат нескольких дефектов создает новый дефект. Из-за этого отдельные ошибки труднее искать. Кроме того, работает психологический фактор ― когда ошибок много, у людей меньше энергии, чтобы искать и исправлять их. Авторы книги «Прагматичные программисты» называют это явление теорией разбитых окон.
В результате проекты с непрерывной интеграцией обычно имеют гораздо меньше ошибок, как в рабочем продукте, так и в работе. Однако я хотел бы подчеркнуть, что это преимущество напрямую связано с тем, насколько хорош ваш набор тестов. Вы можете обнаружить, что заметно улучшить набор тестов не так уж и трудно. Тем не менее обычно проходит некоторое количество времени, пока участники команды привыкнут к низкому уровню ошибок, которого они потенциально могут достичь. Для достижения этого уровня необходимо постоянно работать над улучшением своих тестов.
Если вы используете непрерывную интеграцию, вы тем самым устраняете одно из самых больших препятствий на пути к частому развертыванию. Частое развертывание имеет большое значение, так как оно позволяет пользователям быстрее получать новые функции и быстрее давать по ним обратную связь и в целом лучше взаимодействовать с вами во время цикла разработки. Это помогает преодолеть барьеры между клиентами и разработчиками ― а это, по моему убеждению, самые большие барьеры на пути к успешной разработке программного обеспечения.
Внедрение непрерывной интеграции
Итак, вы заинтересовались и хотите попробовать непрерывную интеграцию. С чего начать? Полный набор практик, который я описал выше, поможет вам получить все преимущества непрерывной интеграции, но вам необязательно начинать использовать все эти практики сразу.
Здесь нет единого рецепта, многое зависит от вашей структуры и команды. Но я могу дать подсказать несколько вещей, которые мы изучили.
Один из первых шагов ― автоматизация сборки. Поместите все, что вам необходимо, в систему управления исходным кодом, чтобы можно было запустить сборку с помощью одной команды. Для многих проектов это может оказаться непростой задачей, однако это сделать необходимо, чтобы все остальные аспекты работали. Для начала вы можете выполнять сборку время от времени по необходимости или просто автоматизировать ночную сборку. Хотя это еще не будет непрерывной интеграцией, автоматизация ночной сборки будет важным первым шагом к ней.
Добавьте в свою сборку некоторое количество автоматизированных тестов. Попробуйте выделить основные области, в которых часто возникают проблемы, и добавьте автоматизированные тесты для проверки этих областей. На существующем проекте особенно трудно быстро получить по-настоящему хороший набор тестов ― необходимо потратить некоторое время на их создание. Однако вам придется с чего-то начать, поговорка «Рим не сразу строился» здесь вполне применима.
Попробуйте ускорить сборку при коммите. Непрерывная интеграция, при которой сборка длится несколько часов ― лучше, чем ничего, но лучше все-таки постараться достичь магических десяти минут. Иногда это требует серьезной переделки кодовой базы, при которой вы удаляете зависимости от медленных участков системы.
Если вы начинаете новый проект, начните применять непрерывную интеграцию с самого начала. Наблюдайте за временем выполнения сборки и предпринимайте меры, если оно начинает превышать десять минут. Действуя быстро, вы сможете выполнить необходимую реструктуризацию, прежде чем кодовая база увеличится настолько, что превратится в сущее наказание.
И, главное, попросите помощи. Найдите того, кто уже вводил непрерывную интеграцию раньше и может вам помочь. Вводить ее сложно, как и любую другую технику, если вы не знаете, как должен выглядеть конечный результат. Найти наставника может быть дорого, но вы в любом случае заплатите за потерю времени и производительности, если не найдете его. (Реклама: да, мы в ThoughtWorks оказываем некоторые консультации в этой области. В конце концов, мы можем делать это после того, как прошли практически через все возможные ошибки).
За несколько лет, которые прошли с того момента, когда мы с Мэттом написали первую статью, непрерывная интеграция стала одной из ведущих техник разработки программного обеспечения. В ThoughtWorks она используется практически во всех проектах, и мы наблюдаем, как она используется во всем мире. Я почти никогда не слышал негативных отзывов об этой технике, в отличие от других более противоречивых практик экстремального программирования.
Если вы не используете непрерывную интеграцию, я настоятельно рекомендую вам попробовать это делать. А если используете, то, возможно, некоторые идеи из этой статьи могут помочь вам делать это более эффективно. За последние несколько лет мы узнали о непрерывной интеграции много нового, и я надеюсь, что есть еще много того, что можем узнать и улучшить.
Что еще почитать
Эссе, наподобие этого, может только поверхностно покрыть эту тему, но так как это важная тема, я создал страницу с руководствомна моём сайте для поиска большей информации.
Для того чтобы исследовать Непрерывную Интеграцию более детально я советую вам взглянуть на одноименную книгу Пола Дюваля по этой теме (она кстати выиграла Jolt award — я даже не мог этого представить). Тема непрерывного развертывания более широго рассмотрена в книге Джеза Хамбла и Дэйва ФарлиНепрерывное развертывание, которая тоже завоевала Jolt award.
Больше информации по непрерывной интеграции вы также можете найти на сайте ThoughtWorks.