Построение программного обеспечения при каждом изменении |
09.12.2008 16:33 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Глава из книги ''Непрерывная интеграция: улучшение качества программного обеспечения и снижение риска''Авторы: Поль М. Дюваль
|
ПРИМЕЧАНИЕ |
Существует множество инструментов построения. К наиболее популярным относятся Ant для Java и NAnt для .NET. Использование инструментов выполнения сценариев, разработанных специально для построения программного обеспечения, вместо собственного набора пакетных файлов оболочки, является более эффективным способом создания однозначного и воспроизводимого решения построения.
Помните: построение должно осуществляться нажатием одной кнопки. Когда вы нажимаете кнопку <Integrate>, как показано на рис. 4.1, на сборочном конвейере запускается процесс, который создает работоспособное программное обеспечение. Иногда организации не способны задействовать CI потому, что они не могут реально автоматизировать свое построение. В некоторых случаях такая неспособность обусловлена жесткой связью и зависимостями, например с библиотеками сторонних производителей и жестко заданными ссылками. Я не раз видел проекты с яркими примерами следующего.
В обоих случаях сценарии становятся неработоспособными не только на машине с операционной системой, отличной от Windows, они могут также не сработать на машине разработчика, если он иначе подключит сетевой диск или переместит инструменты с диска C:\ в другой каталог. Попытка запуска подобного сценария при невозможности найти зависимый элемент приводит к сбою и неудаче построения.
Случалось ли вам видеть программное обеспечение, которое, не проходя проверок, оказалось работоспособным? А как насчет программного обеспечения, которое было проверено, но не проинспектировано? Предположим, некто вам скажет, что "здесь все работает, кроме базы данных" - это что, работоспособное программное обеспечение? Некоторые разработчики полагают, что их программное обеспечение функционирует, если оно компилируется. Существуют разные типы построения (рассматриваемые далее в этой главе), поэтому вы будете балансировать между тяжеловесным построением, включающем все тесты, но производящем рабочее, развертываемое программное обеспечение (как правило, прошедшее многие типы тестов, а также инспекций), и необходимостью получения быстрой обратной связи.
Мартин Фаулер советует: "Получайте все необходимое из хранилища с контролем версий, чтобы можно было построить целую систему, отдав одну команду". Концепция кнопки <Integrate> реализуема только тогда, когда вы можете запускать построение одной командой. Например, ввод в командной строке команды nant integrate, как представлено в листинге 4.1, является примером единой команды, инициализирующей интеграционное построение.
> nant integrate |
Для работы в автоматизированном режиме сервер CI нуждается в "безголовом" процессе, например в едином сценарии команды. При выполнении интеграционного построения на отдельной машине нельзя полагаться на IDE. Кроме того, для осуществления интеграционного построения единой командой необходимо получить доступ ко всем элементам программного обеспечения (из хранилища с контролем версий).
Автоматизированное построение - это как кнопка <Integrate>: нажмите кнопку, и ваше программное обеспечение будет построено (и развернуто). Это означает, что все элементы последнего будут связаны, а их функционирование проверено. Рис. 4.2 иллюстрирует действия, обычно выполняемые сценарием построения.
Рисунок 4.2. Логические процессы сценария построения.
В общих чертах построение программного обеспечения подразумевает примерно следующие этапы.
Листинги 4.2-4.6 демонстрируют примеры использования инструмента построения NAnt для платформы .NET; но вы вполне можете получить тот же результат при помощи других средств сценариев построения, таких как Ant или Maven для Java, MSBuild для .NET, Rake для Ruby и т.д. Вопрос не в том, какой инструмент выбрать; главное - использовать существующий инструмент, а не создавать собственное решение.
Листинг 4.2 демонстрирует сценарий NAnt, использующий задачу delete для удаления всех каталогов и файлов перед новым построением. Это уменьшает вероятность того, что файлы предыдущего построения неблагоприятно повлияют на новое построение.
<target name="clean"> |
Листинг 4.3 демонстрирует компиляцию кода C# с использованием задачи csc, которая компилирует все файлы в некотором каталоге и перемещает полученный файл .dll в другой каталог. Вторая часть данного примера демонстрирует запуск сценария SQL, выполняющего определения данных и создающего таблицы в базе данных.
<target name="build"> |
Листинг 4.4 содержит пример выполнения задачи nunit2 в NAnt, выполняющей комплекс проверок NUnit. Обратите внимание, что при сбое любой из проверок происходит общий сбой построения (как можно заметить, атрибуту failonerror задачи nunit2 присвоено значение true). Как упоминалось в главе 2, "Введение в непрерывную интеграцию", для корректности построения все тесты и инспекции должны быть пройдены.
<target name="run-tests" depends="compile-src"> |
Листинг 4.5 демонстрирует выполнение задачи fxcop, запускающей FxCop, бесплатный инструмент для платформы .NET, который инспектирует и оповещает о предопределенных нарушениях кода, связанных с производительностью, защитой, соглашениями именования и т.д.
<target name="fxcop"> |
Последнее действие построения, представленного на рис. 4.2, - развертывание. Листинг 4.6 иллюстрирует использование задачи NAnt для простого развертывания на сервере FTP.
<target name="deploy"> |
Если сценарий построения выполнен разработчиком безо всякой обратной связи, то он даже не будет знать, прошло ли построение успешно. Очень простой пример уведомления об отказе включен в листинг 4.4. Если любая из проверок, запущенных задачей nunit2, закончится неудачей, то все построение будет считаться сбойным. Фактически, NAnt прекратит работу с сообщением BUILD FAILED, так что никаких сомнений не будет.
Это ни в коем случае не исчерпывающий пример сценария построения. Сценарий построения, который полностью имитирует кнопку <Integrate>, должен был бы включать намного больше процессов.
Следует избегать применения IDE в сценариях построения. IDE может зависеть от сценария построения, но сценарий построения не должен зависеть от IDE. Рис. 4.3 иллюстрирует соответствующую зависимость. Она иногда более тонкая, чем вы можете думать. Например, IDE способна облегчить создание сценария построения, но при этом может помещать файлы построения и зависимые элементы в структуре того каталога, в котором установлен IDE. Чтобы проверить возможность многократного использования сценария построения, созданного IDE, возьмите сценарий построения и запустите его на новой машине с только что установленной операционной системой (и соответствующим инструментом построения).
Рисунок 4.3. Отсоединение сценария построения от IDE
Создание отдельного сценария построения важно по двум причинам.
Для эффективного построения программного обеспечения все его элементы следует централизовать. Централизуя элементы программного обеспечения в системе контроля версий, вы способствуете построению единой командой, описанному ранее в этой главе. Кроме того, централизация помогает предотвратить проблему "но на моей машине это работает", когда разработчик не способен воспроизвести дефект, который произошел в некой другой системе, например на проверочной машине или машине пользователя. В этом разделе мы рассмотрим различные методы централизации элементов программного обеспечения.
Один из подходов централизации элементов программного обеспечения подразумевает использование для хранения всех файлов хранилища с контролем версий. В книге Стивена Беркзука (Stephen Berczuk) и Бреда Апплетона (Brad Appleton) Software Configuration Management Patterns это называется "Repository pattern" (схема хранилища) и подразумевает, что "рабочее пространство состоит не только из кода", а включает следующее:
При использовании хранилища с контролем версий для централизации всех элементов программного обеспечения остается определить, что же именно составляет это "все". Можно использовать уровни или рискнуть и решиться на минимум типов элементов программного обеспечения, которые будут находиться в хранилище с контролем версий. Например, одним из рисков для долго существующего продукта является то, что использование последующих версий компиляторов и инструментов могут породить в этом продукте проблемы, иногда незаметные и труднообнаружимые. Это риск обусловлен тем, что вам может понадобиться перекомпилировать прежнюю версию.
Кроме того, некоторые версии инструментальных средств плохо совмещаются с другими. Разработчику достаточно просто задействовать любую версию инструмента, которую он считает подходящей, однако гоняясь за новыми преимуществами, можно обнаружить и новые недостатки. Аналогично, возвращение и попытка воссоздания прежнего построения (например, для воспроизведения проблемы клиента или устранения ошибки) может потребовать определенного набора инструментальных средств, которые использовались при разработке в прежнее время. Таким образом, приходим к заключению, что, вероятно, практически нет никаких элементов вашего проекта, которые не пригодились бы в будущем по разным причинам. В этом и проявляется ценность для проекта централизованного хранилища элементов с контролем версий.
Использование хранилища с контролем версий для управления всеми элементами программного обеспечения может остаться лишь разговорами, поскольку для его реального воплощения сценарии должны уметь находить все необходимое на сервере CI. Чтобы обеспечить реальную возможность находить и получать из хранилища все бесчисленные комбинации элементов, которые могут быть использованы в проекте, вам придется создать однозначную, логичную структуру каталога.
Один из подходов предлагает основывать структуру каталога на типичных действиях рабочего проекта, например требованиях, проектировании, реализации и тестировании. Используете ли вы такую структуру или другую, главное, чтобы она четко разделяла содержимое и однозначно отражала его изменения. Кроме того, очень важно, чтобы каждая задача в построении выполнялась из каталога, который содержит только исходный код и сценарии, связанные с этой задачей, а не весь проект. Например, ваш сценарий интеграционного построения может получать весь исходный код и связанные сценарии из каталога implementation. Это может значительно ускорить построение, поскольку поиск всех необходимых файлов (документов и двоичных файлов) может проходить довольно долго. Простая структура каталога, подобная приведенной ниже, поможет отделить файлы исходного кода от других файлов, упростив запуск построения.
Безусловно, все каталоги верхнего уровня могут содержать множество вложенных каталогов. Каталог implementation должен содержать только файлы исходного кода и может выступать в роли первичного каталога для построения.
В хорошо организованном построении неудача проявляется быстро. Весьма огорчает сбой после успешного выполнения множества других элементов построения, когда уже потрачено драгоценное время. Чтобы сбой при построении происходил как можно раньше, нужно предпринять следующие действия.
Это лишь одна из рекомендуемых последовательностей построения. Все зависит от того, где наиболее вероятен сбой в специфическом проекте. Чем больше вероятность сбоя некоего процесса, тем раньше его следует запустить в сценарии построения. Имейте также в виду, что порядок выполнения иногда диктуется последовательностью построения. Например, компиляция исходного кода происходит перед его тестированием. Эффективность построения выше, если склонный к отказу процесс выполняется раньше. Раздел "Выполняйте быстрое построение" далее в этой главе описывает способы организации построения, позволяющие уменьшить его продолжительность и обеспечить более быструю обратную связь.
Зачастую проект предусматривает развертывание в различных средах. Для этого может пригодиться наличие в хранилище различных файлов конфигурации, предназначенных для настройки разных систем (разработка, интеграция, тестирование, QA и работа), включая файлы .properties, .xml или .ini. Каждой платформе, языку и инструменту сценариев будет соответствовать собственный вариант конфигурации. Возможность перенастройки построения основана на использовании сценариев, выбирающих предопределенные конфигурации, доступные в программном обеспечении, не изменяя базовые функции сценариев построения. В большинстве случаев вы можете обеспечить такое поведение, изменяя конфигурационные файлы, используемые приложением. Можно также настроить среды выполнения и API, с которыми взаимодействует приложение. Конкретная методика зависит от платформы и принятых соглашений. Ниже приведен список перестраиваемых значений конфигурации, присущих большинству сред.
Хотя среды, в которых осуществляются тестирование или развертывание, могут различаться, сценарии построения не должны быть разными. Файлы конфигурации (типа .properties или .include) позволяют перебирать варианты без необходимости копировать и вставлять значения, подходящие для каждой среды, внутрь сценариев построения. Это еще одна область, где, как и в исходном коде, дублирование ведет к большим проблемам и снижению надежности. Для гарантии возможности создания работоспособного программного обеспечения в любой среде следует улучшить способность сценария построения к настройке за счет применения параметров. Как показано на рисунке 4.4, вы можете запускать один и тот же сценарий построения, предоставляя ему соответствующий файл свойств, настраивающий построение для каждой среды. Например, при развертывании в среде QA вы можете вызывать сценарий построения так:
ant -f build.xml -D environment=qa |
Здесь environment - свойство, которое было определено в сценарии Ant. Параметр -D означает, что это системный параметр, передаваемый сценарию Ant.
Рисунок 4.4. Настраиваемое построение для разных сред
Существуют разные типы построений, предназначенные для различных целей. Построение может быть запущено по-разному, например: пользователем, по расписанию, в связи с внесением изменений или в результате некоего события.
Иерархия типов построения включает три уровня: для отдельных разработчиков, команды и пользователей, т.е. закрытое построение, интеграционное построение (объединяющее результат работы с работой остальной части группы) и финальное построение, в результате которого получается программное обеспечение, передаваемое пользователям.
Разработчик выполняет закрытое построение (private build) перед передачей своего кода в хранилище. Осуществляя закрытое построение, вы интегрируете свои изменения с последними изменениями, доступными в хранилище с контролем версий. Это может предотвратить сбой построения. При закрытом построении выполняются следующие шаги.
Интеграционное построение (integration build) интегрирует изменения, внесенные в хранилище группой с общей линией (mainline) (называемой также головой (head) или магистралью (trunk)). В идеале оно должно осуществляться на выделенной машине.
Фаулер рассматривает различные типы построения, которые могут быть выполнены в ходе интеграционного построения. Он называет это "поэтапным построением" ("staged builds"), которое включает "передающее построение" ("commit build") и "последующие построения" ("secondary builds"). Передающее построение - это ваше самое быстрое интеграционное построение (меньше десяти минут), включающее компиляцию и юнит-тесты. Последующее построение - это интеграционное построение, которое запускает более медленные тесты, например тестирование компонентов, системы и производительности. Здесь могут также осуществляться автоматизированные инспекции соблюдения стандартов программирования и сложности кода.
Финальное построение (release build) готовит программное обеспечение к выпуску для пользователей. Одной из задач CI является создание развертываемого программного обеспечения. Финальное построение, происходящее в конце итерации или некоторого другого промежуточного этапа, может включать более обширные тесты, в том числе проверку производительности, загруженности и все приемочные испытания. Кроме того, при большинстве финальных построений создается инсталляционный пакет, предназначенный для запуска в системе пользователя. Финальное построение может также применяться для проверки готовности к QA, если группа использует отдельный, поэтапный процесс.
Не все построения запускаются одинаково. Для выбора подходящего способа запуска построения необходимо учитывать его назначение и частоту запуска. В некоторых ситуациях сценарии могут оказаться настолько большими или иметь так много зависимостей, что запускать их автоматически не стоит; лучше делать это по требованию. В других случаях автоматический запуск может выполняться под управлением CI. Типы механизмов построения описаны ниже.
Таблица 4.1 демонстрирует взаимосвязь между типом построения и способом его запуска.
Тип построения | Механизм построения |
---|---|
Закрытое | По требованию |
Интеграционное | По требованию, опрос изменений, по расписанию, управляемое событиями |
Финальное | По требованию, по расписанию |
Когда вы выделяете машину для интеграционного построения, вы решительно ограничиваете предположения о среде и конфигурации, а также способствуете предотвращению слишком позднего проявления проблемы "а на моей машине это работает". Рабочие станция обычно имеют несколько отличающиеся конфигурации и множество зависимостей, что зачастую невозможно отследить из среде разработки. Если разработчик внесет локальные изменения, но забудет передать несколько файлов в хранилище с контролем версий, то система CI, выполняющаяся на отдельной машине, запустит интеграционное построение и обнаружит их отсутствие. Кроме того, вы можете устанавливать серверы приложений и баз данных в определенное состояние каждый раз, когда происходит интеграционное построение. Это также позволит не только уменьшить количество предположений, но и существенно быстрей обнаруживать и решать проблемы. Когда сотрудники узнают, что последнее интеграционное построение потерпело неудачу, они могут избежать получения сбойного исходного кода из хранилища с контролем версий. Машина интеграционного построения действует как сеть безопасности, гарантируя, что программное обеспечение работает как ожидалось.
Многих зачастую интересует, во сколько обойдется приобретение выделенной машины для интеграционного построения. Это важный вопрос, а ответ на него еще важнее. Приведенный ниже диалог демонстрирует, как быстро этот вопрос теряет значение.
Питер (технический руководитель). Я хотел бы приобрести для нашего проекта Logistics выделенную машину интеграционного построения.
Билл (руководитель проекта). Зачем вам отдельная машина?
Питер. Чтобы мы могли строить наше программное обеспечение непосредственно из хранилища Subversion при каждом изменении. Мы также будем иметь возможность переустанавливать среду, включая проверочные данные. Все это позволит быстрее находить и устранять проблемы.
Билл. Звучит заманчиво, но, Питер, у нас действительно нет денег на это. Я полагаю, что вам потребуется по крайней мере 1 000 долларов, не так ли?
Питер. Кстати, причина, по которой мы провели последнюю субботу на работе, была связана с проблемой интеграции. Получив машину интеграционного построения, мы сможем значительно сэкономить время и деньги. Сэкономленное время с лихвой окупит стоимость машины, причем много раз. В понедельник мы были вынуждены вручную интегрировать и проверять демонстрационную версию приложения. Нам действительно нужна эта машина, чтобы автоматически интегрировать программное обеспечение при каждом изменении.
Билл. Ладно, но все, что мы можем сделать сейчас, это задействовать одну из дополнительных машин в серверной. Вы можете удалить с нее все и сделать машиной интеграционного построения.
Судя по диалогу Билла и Питера, вам, возможно, и не придется тратить деньги на приобретение новой машины. Машиной CI может стать любая неиспользуемая дополнительная машина. Если выделенная машина интеграционного построения недоступна, используйте для начала собственную машину разработки. Это лучше, чем отказ от интеграции вообще, но это не долгосрочное решение. Убедитесь, что использовали отдельное место на своей машине (т.е. каталог или раздел).
При создании машины интеграционного построения следует учитывать несколько факторов. Сосредоточившись на них, вы извлечете максимум преимуществ.
Наличие выделенной машины построения, которая способна его эффективно выполнять, позволяет запускать его часто. Кроме того, среда построения становится четко воспроизводимой, уменьшая количество предположений, относящихся именно к ней.
ПРИМЕЧАНИЕ |
При реализации непрерывной интеграции имеет смысл использовать сервер CI. Безусловно, вы можете создать свой собственный инструмент или выполнять интеграцию вручную; но сейчас на рынке доступно множество превосходных инструментов, которые предоставляют ценнейшие возможности, а также позволяют расширять их. Следовательно, необходимость в создании собственного сервера CI отпадает. Но если вам все же придется делать это, то вы, вероятно, захотели бы включить в него большинство следующих возможностей.
И так далее. В большинстве серверов CI эти средства уже реализованы. Уверен, что можно без проблем подобрать инструмент, который полностью удовлетворяет вашим потребностям и подходит к среде разработки. CruiseControl, Luntbuild, Continuum, Pulse, и Gauntlet - вот лишь некоторые из инструментов, которые можно использовать для реализации CI. В приложении Б,"Обсуждение инструментальных средств CI", рассматриваются и оцениваются различные инструменты CI, присутствующие на рынке на момент написания этой книги.
Так что же использовать: сервер CI, интеграцию вручную или их комбинацию? Выбор за вами. Мы безусловно одобряем применение сервера CI. Но иногда возникают резонные основания для выполнения интеграции вручную, особенно с учетом минимальной инструментальной поддержки для предотвращения передачи сбойного кода в хранилище.
Альтернативой или дополнением к использованию сервера CI является выполнение интеграции вручную. Практика ручного интеграционного построения подразумевает, что только один человек одновременно может передавать изменения в хранилище. Разработчики по очереди вручную запускают интеграционное построение на отдельной машине, гарантируя, что построение всегда остается в исправности.
Используя сервер CI, ваша группа осуществляет автоматизированное (непоследовательное) интеграционное построение, которое может быть запущено в любое время. Как уже упоминалось ранее, сервер CI может опрашивать хранилище на предмет изменений. Как только он обнаруживает изменения, он запускает автоматизированное построение. Проблема автоматизированной интеграции заключается в том, что ее результатом может оказаться сбойное построение. В такой ситуации CI скорее недостаток. Проблема зачастую не обнаруживается до тех пор, пока ее причина не окажется в хранилище, а это означает, что другие разработчики могут успеть получить из хранилища сбойный код. Кроме того, сбойное построение может нарушить процесс работы разработчиков.
Чтобы предотвратить попадание сбойного кода в хранилище, некоторые группы используют последовательную интеграцию вручную или применяют физический маркер (я знал одну группу, которая использовала кнопку скобок) или обычную блокировку файла, чтобы обозначить только одного разработчика (или пару), который осуществляет интеграцию в данный момент.
Ручная интеграция может быть эффективна для предотвращения сбойных построений, но она не слишком хорошо масштабируется для больших групп. Кроме того, побочный эффект этой формы интеграции заключается в том, что участники группы могут накопить в очереди много изменений, а поскольку интеграции проходят реже, они оказываются крупней и сложней, что затрудняет получение качественного развертываемого программного обеспечения. К тому же при осуществлении интеграции исключительно вручную нет никакой гарантии, что эта практика будет соблюдаться. Использование сервера CI обеспечивает защищенную сеть, оповещающую о ходе интеграции. Некоторые группы применяют комбинацию последовательных и автоматизированных интеграций. Например, группа, которая использует исключительно автоматизированную CI, может комбинировать данный подход с отдельными закрытыми построениями, выполняемым каждым разработчиком, чтобы предотвратить нарушение интеграционных построений. Однако это вопрос личных предпочтений. Мы, безусловно, склоняемся к автоматизированной интеграции, но не отрицаем уникальных преимуществ ручной и последовательной интеграций, позволяющих поддерживать построение в рабочем состоянии.
Остановка работы на время ожидания результата построения замедляет ритм разработки проекта. Следовательно, если построение занимает слишком много времени, это бросает тень на практику CI. Быстрый отклик критичен для CI. Чем короче продолжительность интеграционного построения, тем быстрее вы получите результат.
ПРИМЕЧАНИЕ |
Если разработчики редко передают код в хранилище с контролем версий, причина может крыться в медленном интеграционном построении. Начинать уменьшение продолжительности построения имеет смысл с общего анализа системы интеграционного построения на предмет выявления узких мест. Затем нужно проанализировать результаты и, определив наиболее подходящие средства, попробовать внести изменения в процесс построения; возможно, они уменьшат его продолжительность. Наконец, еще раз оцените продолжительность построения и определите, стоит ли принимать дальнейшие меры.
На высоком уровне подход к диагностике и уменьшению продолжительности построения можно описать так:
ПРИМЕЧАНИЕ |
Сбор показателей построения - это первый шаг к его ускорению. Таблица 4.2 содержит список наиболее общих показателей, которые можно использовать при качественном анализе процесса интеграционного построения. Собирать все эти показатели каждый раз не обязательно, но если вы не уверены в причине ошибки или предпочли бы не тратить время впустую на поиск и устранение несуществующих проблем, это может оказаться полезным упражнением.
Показатель интеграционного построения | Описание |
---|---|
Время компиляции | Время, необходимое для компиляции программного обеспечения. Подлежит сравнению с прошлым временем компиляции |
Количество строк исходного кода (Source Lines Of Code - SLOC) | Обозначает размер системы или ориентировочный объем кода, подлежащего компиляции |
Количество и типы инспекций | Количество выполняемых инспекций разных типов. Выявите и удалите избыточные |
Среднее время создания сборок | Время, необходимое для создания сборки, архива или упаковки программного обеспечения |
Время проверки (по категориям) | Время, необходимое для выполнения проверок на каждом уровне: модуль, компонент и система (они описаны в главе 6, "Непрерывная проверка") |
Соотношение успешных и неудачных построений | Чтобы определить соотношение успешных и неудачных построений, разделите число последних на общее количество построений |
Время инспекции | Время, необходимое для выполнения всех автоматизированных инспекций |
Время развертывания | Время, необходимое для развертывания программного обеспечения в среде назначения после интеграционного построения |
Время перепостроения базы данных | Время, необходимое для перепостроения базы данных |
Системные ресурсы машины интеграционного построения и их использование | Увеличивая память, скорость диска и процессора, можно улучшить производительность интеграционных построений. Это поможет выяснить, имеет ли машина интеграционного построения сервер приложений, сервер баз данных и некоторые другие процессы, которые растрачивают память и скорость процессора |
Загрузка системы контроля версий | Помогает выявить пиковую загруженность системы с контролем версий, продолжительность проверки и загрузки проекта с машины интеграционного построения и адекватность пропускной способности сети, процессора, памяти и дисков |
Собрав показатели, проанализируйте их, используя рисунок 4.5 как общее руководство для определения усовершенствований, которые максимально сократят продолжительность построения. Тактические приемы здесь расположены по приоритетам с использованием следующих критериев: масштабируемость, производительность и сложность реализации. Многие решения могут зависеть от размера базового кода и ряда автоматизированных процессов построения, выполнение которых требует много времени (например, автоматизированные проверки). Вы можете документировать подход, рационализировать его и применить в следующий раз, когда понадобится уменьшить продолжительность построения.
Рисунок 4.5. Снижение продолжительности интеграционного построения
Вооружившись стратегией усовершенствований, вы можете приступать к их реализации.
Преимущества использования выделенной машины интеграционного построения мы рассматривали в предыдущем разделе. Ее применение дает множество преимуществ по повышению производительности, включая снижение количества отказов построения и его ускорение.
Зачастую замена аппаратных средств машины на более быстрые - самый дешевый способ снижения продолжительности интеграционного построения. Вы, вероятно, слышали выражение, что "время процессора дешевле времени человека"; однако машину можно модернизировать лишь до определенного уровня. Ниже приведен список вопросов, который поможет вам определить, являются ли возможности аппаратных средств машины интеграционного построения максимальными.
ПРИМЕЧАНИЕ |
В зависимости от ответов на эти вопросы имеются несколько способов повышения производительности и масштабируемости построения.
Даже в хорошо функционирующей системе CI выполнение множества автоматизированных тестов способно существенно увеличить время интеграционного построения. Оценка и повышение производительности таких тестов может значительно уменьшить продолжительность построения. Учет следующих факторов поможет вам повысить производительность тестирования.
После общей оценки среды тестирования вы будете иметь лучшее представление о способах повышения ее производительности. Возможно несколько стратегий, включая следующие.
Разделите автоматизированные тесты по категориям (модуль, компонент и система) и запускайте их в разное время (например, юнит-тестрование при каждом сохранении кода в репозиторий, а тесты компонентов и системы при последующем построении). Более подробная информация о классификации тестов приведена в главе 6, "Непрерывное тестирование".
Как уже упоминалось, еще один подход снижения продолжительности построения заключается в выполнении облегченного построения, сопровождаемого "тяжеловесным" построением (Фаулер называет это поэтапным построением (staged build): передающее построение, сопровождаемое последующим построением). Рисунок 4.6 иллюстрирует данный подход. При поэтапных построениях вы сначала запускаете "передающее", или облегченное интеграционное построение, в ходе которого интегрируются компоненты программного обеспечения и запускаются проверки модуля, позволяющие обнаружить наиболее очевидные проблемы. Если облегченное построение прошло успешно, запускается более полное интеграционное построение, включающее проверки компонентов и системы, инспекции, а также развертывание. Так реализуется описанная ранее в этой главе практика "ранний сбой построения".
Рисунок 4.6. Процесс поэтапного построения
Вы можете обнаружить, что замедление интеграционного построения связано с инфраструктурой системы. Возможно, сеть недостаточно быстра или медленно выполняется соединение по виртуальной закрытой сети. Географически рассредоточенные системы при ненадежных аппаратных средствах и программном обеспечении также способны вызвать проблемы производительности. Исследуйте и усовершенствуйте все ресурсы инфраструктуры, поскольку это может существенно ускорить построение.
Большой объем кода может привести к тому, что интеграция компонентов программного обеспечения потребует значительного времени. Чтобы выяснить, связана ли проблема с размером кода или интеграцией этих компонентов, определите период времени, занимаемый этапом компиляции. Если данный период слишком большой, выполняйте инкрементное построение вместо полного.
При инкрементном построении (incremental build) компилируются только измененные файлы. Это может быть небезопасно, поскольку в зависимости от реализации вы можете не получить всех преимуществ CI. Эффективная система CI должна снижать риски, а следовательно, система интеграции должна была бы удалять все прежние файлы, а затем обновлять и компилировать код, чтобы гарантированно обнаруживать возможные проблемы. Таким образом, использовать инкрементное построение следует как последнее средство, после исследования других областей, ведущих к замедлению процесса построения.
Инкрементное построение можно применить в нескольких областях. Например, при наличии системы Java с базовыми библиотеками DLL или совместно используемой библиотекой объектных модулей, которая редко изменяется, вполне приемлемо перестраивать такие библиотеки только один раз в день. Фактически, некоторые могли бы оспорить данный подход, заявив, что такие нечасто изменяемые библиотеки DLL и совместно используемые объекты считаются отдельным проектом CI и обращаться к ним стоит как к зависимым элементам проекта.
Иногда интеграционное построение длится долго из-за времени, которое занимает интеграция исходного кода с другими файлами. В таком случае вы можете разделить программное обеспечение на меньшие подсистемы (модули) и осуществлять построение каждой из них в отдельности.
Для индивидуального построения компонентов системы создайте отдельные проекты для каждого модуля, который может быть изолирован. Это можно осуществить внутри системы CI, достаточно сделать одну из подсистем главным проектом. Если в один из проектов внесены какие-либо изменения, то на основании зависимостей другие проекты тоже перестраиваются. Рисунок 4.7 демонстрирует типичную компоновку проекта при разделении на дискретные компоненты для ускорения построения.
Рисунок 4.7. Раздельное построение компонентов системы
Аналогично низкой производительности тестирвания низкая производительность инспекции также может замедлить систему CI. Чтобы выяснить, не тормозит ли инспекция интеграционное построение, используйте следующий список вопросов.
Ниже приведен список возможных решений по увеличению производительности инспекции программного обеспечения.
Если объем кода чрезвычайно велик, и вы уже увеличили скорость процессора, памяти и диска на машине интеграционного построения, а также приняли другие меры для уменьшения продолжительности построения, включая снижение частоты проверок компонентов и системы, но считаете, что построение все еще слишком продолжительно, следует обратить внимание на распределенное интеграционное построение.
Существуют инструменты интеграционного построения, которые позволяют объединить мощности нескольких машин. Такие инструменты, как BuildForge и ParaBuild, предоставляют средства для распределения интеграционных построений. Данными способностями обладают и другие серверы CI, например CruiseControl, однако распределенные интеграционные построения - это сложная проблема с еще более сложным решением. Перенос части процесса построения на другую машину может подразумевать копирование больших файлов, что может даже больше замедлить построение. Перед тем как пытаться реализовать этого решение, попробуйте принять все другие меры по уменьшению продолжительности построения.
Итак, мы обсудили несколько подходов, включая повышение производительности тестирования, процесса построения, модернизацию аппаратных средств и изменение дизайна проекта. Какие из улучшений оказались эффективны и какова продолжительность построений сейчас? Теперь пришло время попытаться распространить усовершенствования на остальную часть группы и решить, нужен ли дополнительный цикл улучшений. Если вы уже осуществили данный процесс один раз, то новый цикл усовершенствований пройдет быстрее и проще.
На настоящий момент вы, вероятно, вполне согласны с тем, что выполнение интеграционного построения при каждом изменении программного обеспечения позволит снизить множество рисков в проекте. Но вы можете подумать: "Это прекрасно сработало в вашем проекте, но это не сработает в нашем, поскольку у нас нет ни времени, ни ресурсов, ни денег" или "У нас совершенно другой тип проекта, вовсе не похожий на описанный здесь". Приведенные ниже вопросы и ответы основаны на ряде подобных мнений.
Да ладно, не преувеличивайте, ваш проект вряд ли имеет именно "семь миллиардов" строк кода, скажем, вы работаете над очень большим проектом и полагаете, что для внедрения CI в нем слишком много препятствий. Однако чем больше проект, тем больше количество изменений, а следовательно, тем необходимей CI. Это подобно высказыванию: "Я предпочел бы не знать о проблемах в нашем коде или предпочел бы подождать, пока не забуду, над чем я работал тогда". Кроме того, внедрение CI в большой проект не займет больше времени, чем в малый. Просто в большом проекте и преимуществ будет больше, и вероятность успеха выше, и больше гибкости в работе с большим количеством элементов проекта.
Основной интерес большого проекта - это быстрое построение, а CI позволяет запускать длительные процессы периодически (или поэтапно, как описано ранее), а не непрерывно. Сюда относится проверка компонентов, системы, функций и инспекции. Разделение базового кода на отдельные составляющие также может помочь снизить продолжительность интеграционного построения.
Если вы еще не используете систему CI, то сначала может понадобиться некоторое время на создание сценария построения для вашего исходного кода. Сценарии построения следует писать так, чтобы они могли запускаться автоматически. Но даже если вы не имеете автоматизированных тестов, вы можете начинать добавлять их для каждого изменения (т.е. обнаружив дефект, первым делом надо написать тест для него). Затем включите запуск такого теста в сценарий построения и выполняйте его с составе системы CI.
Этот вопрос зачастую связан с вопросом о распределенной разработке. Давайте предположим, что вы имеете один проект в хранилище Subversion Project Management System (система управления проектом) и другой - в хранилище Financial Management System (система управления финансами). Если изменения внесены в хранилище Financial Management System, то проект в хранилище Project Management System следует перепостроить, поскольку он использует API из хранилища Financial Management System. Ваш сервер CI должен обеспечить вам возможность построения зависимости. Это запуск построения одного проекта в результате инициации построения другого.
Вы имеете группы разработки, функционирующие дистанционно, и испытываете затруднения во внедрении CI? Причиной может быть медленное сетевое подключение или высокая степень защиты интеллектуальной собственности. Большинство серверов CI позволяют использовать зависимости проекта. Предположим, что в Вирджинии разрабатывается проект программного продукта, алгоритмы и некоторые компоненты которого создает группа из Калифорнии. Для хранения специальных алгоритмов компания использует хранилище с контролем версий CVS в Вирджинии. Кроме того, в Калифорнии компания установила новое хранилище Subversion. Технический руководитель в Вирджинии настраивает сервер CI CruiseControl так, чтобы поддерживать оба проекта: один в Вирджинии, второй в Калифорнии. Прежде чем интеграционное построение будет успешно выполнено для группы в Калифорнии, сервер CI запускает его в Вирджинии. Но это будет работать, только если компоненты разделены корректно.
См. раздел "Выполняйте быстрое построение" ранее в этой главе.
Просто вы передаете в хранилище неработающий код! Возможно, он не компилируется, не проходит тестирование или инспекции, либо ваши скрипты базы данных содержат ошибки. Один из способов решения данной проблемы заключается в проведении на машинах разработки закрытых построений (см. главу 2, "Введение в непрерывную интеграцию"), в максимально возможной степени имитирующих среду интеграции, перед передачей изменений в хранилище с контролем версий. Это означает, что каждый разработчик помещает последние изменения в хранилище с контролем версий, добившись успешного прохождения на машине разработки всех тестов, инспекций, а также перепостроения базы данных с проверочными данными. Это также означает, что каждый разработчик должен иметь в среде собственное "пространство", в котором выполняются те же процессы, что и при интеграционном построении. Важнейший принцип, который следует уяснить: избежание больших построений предотвращает ошибки. Это означает, что передаче изменений в хранилище с контролем версий должен предшествовать процесс интеграции и проверки на машине разработчика. Рисунок 4.8 демонстрирует этапы выполнения закрытого построения перед передачей изменений в хранилище.
Рисунок 4.8. Выполнение закрытого построения уменьшает вероятность ошибки интеграционного построения
Аппаратные средства обходятся дешевле времени сотрудников, которое может быть потрачено на поиск причин проблем интеграции. По деньгам это не дорого. Как указано в диалоге Билла и Питера ранее в этой главе, вы можете найти неиспользуемый компьютер и настроить его как машину построения. Затем, после того как группа привыкнет к преимуществам интегрированного построения, можно вложить деньги в более мощную машину. Без отдельной машины построения вы будете также тратить время на поиск проблем, вызванных тем, что разработчик забыл передать файл в хранилище с контролем версий. Это тоже требует времени, а время - деньги. Постарайтесь убедить своего "казначея" и руководство, что приобретение машины построения в конечном итоге сэкономит деньги. Здесь под "периодом окупаемости капиталовложений" следует понимать несколько недель (в зависимости от размера вашей группы), а не месяцы или годы. Кроме того, вы получите качественное преимущество, воспроизводимый процесс построения, который позволит получать работоспособное программное обеспечение в любой момент.
Это совершенно очевидная причина создания системы CI, поскольку вы, вероятно, тратите слишком много времени на выполнение избыточных процессов. Если ваше программное обеспечение сложно и имеет много зависимостей - это даже более важная причина формирования системы, которая объединяет все элементы, а также запускает комплект проверок и инспекций, гарантируя, что все будет работать правильно и непрерывно. Мы не имеем в виду, что вам будет легко создать воспроизводимый процесс построения, в большой инфраструктуре разработки это займет больше времени, но и его экономия окажется ощутимей.
Создать систему построения будет проще, если разделить процесс на ряд малых этапов. Сначала упростите структуру каталога для хранилища с контролем версий, чтобы исходный код, код тестов, конфигурационные файлы и т.д. были легко доступны. Затем используйте инструмент сценариев построения, чтобы написать простой сценарий построения, который только компилирует исходный код. Теперь добавьте проверки и инспекции. Старайтесь наращивать сложность построения постепенно, не пытайтесь сделать все сразу. Фактически, именно так создавалось большинство наших систем CI. Получив награду за несколько первых этапов, у вас появится повод двигаться дальше. Продолжайте в таком же духе, работая по сценарию "немного написать, немного проверить".
Это важный момент. CI выполняется для основной линии. Вы должны гарантировать, что она будет стабильна всегда. Группа разработки может быть рассредоточенной или распределенной по нескольким задачам, что существенно затрудняет коммуникацию. Использование ветвления - хорошая идея, но изменения должны применяться к основной линии.
Хотя большинство систем управления построением может запускать его для нескольких линий разработки, "интеграционное построение CI" выполняется с основной линией.
В этой главе описаны некоторые из практик построения программного обеспечения. Построение состоит из действий, в результате которых создается работоспособное ПО: компиляция исходного кода, интеграция базы данных, тестирование, инспекция, развертывание и обратная связь. Это не исчерпывающий список, есть множество других действий, которые могут стать частью процессов, запускаемых кнопкой <Integrate>.
Таблица 4.3 подводит итог практик, описанных в данной главе.
Практика | Описание |
---|---|
Автоматизируйте построения | Пишите сценарии построения, которые отделены от IDE. Впоследствии они будут выполняться системой CI, чтобы программное обеспечение строилось при каждом изменении в хранилище |
Выполняйте построение одной командой | С учетом возможности загрузки некоторых инструментальных средств вы можете ввести одну команду и выполнить построение из вашего сценария, включая получение последнего кода и запуск всего построения |
Отделяйте сценарии построения от вашего IDE | Вы должны уметь запускать автоматизированные построения без участия IDE |
Централизуйте элементы программного обеспечения | Для уменьшения количества нарушенных зависимостей централизуйте все элементы программного обеспечения. Это снизит вероятность сбойных построений при перемещении на другую машину |
Создайте строгую структуру каталога | Создайте однозначную, логичную структуру каталога, которая облегчит построение программного обеспечения |
Ранний сбой построения | Чем быстрее обратная связь, тем раньше устранение проблемы. Выполните операции построения в таком порядке, чтобы действия с наибольшей вероятностью неудачи выполнялись сначала |
Осуществляйте построение для каждой среды | Проводите на своей рабочей станции то же самое автоматизированное построение, что и на машине интеграционного построения, а при необходимости и для всех других сред |
Используйте выделенную машину для интеграционного построения | Используйте для выполнения построения выделенную машину. Удостоверьтесь, что в области интеграции не остались прежние элементы |
Используйте сервер CI | В дополнение или как альтернативу выполнению ручных интеграционных построений используйте сервер CI, такой как CruiseControl, для автоматического опроса хранилища с контролем версий на предмет изменений и запуска интеграционного построения на отдельной машине |
Выполняйте интеграционное построение вручную | Запускайте последовательное интеграционное построение вручную, используя автоматизированное построение как способ уменьшения ошибок интеграционного построения. Некоторые применяют данный подход как альтернативу серверу CI |
Выполняйте быстрое построение | Постарайтесь довести время интеграционного построения до десяти минут, увеличив ресурсы компьютера, ограничив медленные проверки, ограничив и пересмотрев инспекции и применив поэтапные построения |
Поэтапное построение | Применяйте облегченное "передающее" построение, осуществляющее компиляцию, юнит-тестирование и развертывание, сопровождаемое "последующим", исчерпывающим построением, которое включает проверки компонентов, системы и другие медленные проверки и инспекции |