Изучаем инструменты автоматизации сборки программного обеспечения нового поколения

Упрощаем процесс тестирования с помощью CruiseControl и SCons

Если вы работаете в команде разработчиков программного обеспечения, то, вероятно, сталкивались с ситуациями, когда при изолированном тестировании вашего кода все работает отлично, а при интеграционном тестировании возникают ошибки. В этой статье вы познакомитесь с двумя инструментами – CruiseControl (непрерывная интеграция) и SCons (автоматизация сборки), упрощающими интеграционное тестирование.

Арпан Сен, технический директор, Synapti Computer Aided Design Pvt Ltd

Арпан Сен (Arpan Sen) – ведущий инженер, работающий над разработкой программного обеспечения в области автоматизации электронного проектирования. На протяжении нескольких лет он работал над некоторыми функциями UNIX, в том числе Solaris, SunOS, HP-UX и IRIX, а также Linux и Microsoft Windows. Он проявляет живой интерес к методикам оптимизации производительности программного обеспечения, теории графов и параллельным вычислениям. Арпан является аспирантов в области программных систем.



14.06.2013

Лет десять назад, когда разработка программного обеспечения стала моей профессией, мы с моей командой писали код, выполняли несколько локальных тестов и затем принимали окончательную рабочую версию. Поскольку количество необходимых тестов было очень большим, каждый разработчик выполнял только свой определенный набор тестов. Каждый вечер в 20:00 выполнялось задание cron, которое компилировало исходный код и запускало процесс интеграционного тестирования. Периодически, раз в несколько дней, выяснялось, что фрагменты кода двух разных разработчиков отлично работают в изолированной среде, но не работают при интеграционном тестировании.

Быстрый темп разработки программного обеспечения на протяжении последних десяти лет привел к пониманию, что нам необходимы новые, более совершенные инструменты для интеграционного тестирования. Здесь и приходит на помощь ПО CruiseControl (см. ссылку в разделе Ресурсы), которое аналогично Makefile и отличается лишь тем, что основано на Python. Таким образом, вам не придется изучать еще один специфичный для определенной системы язык сборки, наподобие make или jam.

Непрерывная интеграция

Процесс непрерывной интеграции предполагает, что система сборки проверяет исходный код, компилирует его и запускает интеграционные тесты через заданные интервалы времени (например, через каждые 2 часа). При возникновении ошибок компиляции или ошибок в интеграционных тестах разработчики получают соответствующие уведомления. При желании каждый разработчик может создать свою Web-страницу, содержащую статистику сборки, информацию об источниках возникновения ошибок и т. д.

Работа с CruiseControl

CruiseControl прозрачно интегрируется с довольно большим количеством инструментов, включая Concurrent Versions System (CVS), Subversion, Git, Perforce, Microsoft® Visual SourceSafe и IBM® Rational® ClearCase®. В этой статье используется система управления версиями Subversion, поскольку она является наиболее популярной.

Вы можете загрузить CruiseControl с домашней страницы проекта (см. ссылку в разделе Ресурсы). На момент написания этой статьи последней была версия 2.8.4; она и используется во всех примерах.

В основе системы CruiseControl лежит файл config.xml, который выполняет сборку и тестирование в ответ на изменение кода, а также уведомляет о результатах. В CruiseControl определены несколько тегов, которые обычно используются в XML-файлах: <listeners>, <bootstrappers>, <modificationset>, <schedule> и <publishers>.

Создание конфигурационного файла

Корневой элемент конфигурации называется cruisecontrol и не содержит атрибутов. Обычно корневой элемент содержит один или несколько элементов property. Элементы property содержат пары "имя-значение" и очевидным образом задают параметры директорий, log-файлов и т. д. Пример простой конфигурации приведен в листинге 1.

Листинг 1. Добавление свойств в конфигурацию
<?xml version="1.0"?>
<cruisecontrol>
  <property name="builddir" value=""/opt/logs/project1" />
  <property name="buildlog" value="${builddir}/build.log" />
</cruisecontrol>

Следующий и, вероятно, наиболее важный тег конфигурации – это тег <project>. Конфигурационный файл может содержать несколько тегов <project>, каждый из которых определяет отдельный проект. Если у вас имеется несколько проектов, которые вы хотите запускать параллельно, то необходимо использовать тег <thread>, присвоив атрибуту count этого тега значение, превышающее 1. Если сборка выполняется на сервере с многоядерным процессором, обязательно попробуйте использовать этот прием. Обратите внимание на то, что тег <thread> является частью иерархии тегов <system>. Использование тега <thread> продемонстрировано в листинге 2.

Листинг 2. Конфигурация для серверов с многоядерными процессорами
<?xml version="1.0"?>
<cruisecontrol>
  <property name="builddir" value="/opt/logs/project1" />
  <property name="buildlog" value="${builddir}/build.log" />
  <system>
     <configuration>
         <threads count="8" />
     </configuration>
  </system>
</cruisecontrol>

Для каждого тега <project> необходимо задать атрибут name. В листинге 3 продемонстрировано использование нескольких тегов <project>.

Листинг 3. Добавление нескольких проектов в CruiseControl
<?xml version="1.0"?>
<cruisecontrol>
  <property name="builddir" value=""/opt/logs/project1" />
  <property name="buildlog" value="${builddir}/build.log" />
  <system>
     <configuration>
         <threads count="8" />
     </configuration>
  </system>
  <project name="project1">
    <! need to configure project child tags>
  </project>
  <project name="project2">
     <! need to configure project child tags>
  </project>
</cruisecontrol>

У тега <project> имеется несколько интересных атрибутов. Наиболее интересными для нас являются атрибуты buildafterfailed и requireModification. Если атрибут buildafterfailed имеет значение True, то CruiseControl будет продолжать попытки выполнения сборки проекта даже если последняя сборка завершилась с ошибкой. Это может быть полезно в случаях, когда в системе имеется много зависимостей: например, если исходные файлы хранятся в удаленном хранилище, доступ к которому может быть нарушен, или если какая-то информация извлекается из базы данных, которая может не вернуть ответ на запрос вовремя. По умолчанию атрибут buildafterfailed имеет значение False, а атрибут requireModification – значение True. Последний атрибут указывает CruiseControl на то, что повторная сборка должна выполняться только в том случае, если в базе исходного кода появились отметки об изменении. Если необходимо выполнять сборку с заданной периодичностью вне зависимости от того, был ли изменен исходный код или нет, установите значение этого атрибута в False. В следующем примере продемонстрировано использование этих двух атрибутов.

  <project name="project1" buildafterfailed=true requireModification=no>
    <! need to configure project child tags>
  </project>

Далее необходимо сконфигурировать иерархию дочерних тегов для каждого отдельного проекта. Прежде всего необходимо понять, как связать исходный код с тегом <project>. Есть ли для этого специальный тег? Конечно, да. Именно эту задачу выполняет тег <modificationset>. В листинге 4 продемонстрировано, как получить доступ к репозиторию исходных кодов Subversion из CruiseControl.

Листинг 4. Конфигурирование репозитория исходных кодов в CruiseControl
<?xml version="1.0"?>
<cruisecontrol>
  <property name="builddir" value="/opt/logs/project1" />
  <property name="buildlog" value="${builddir}/build.log" />
  <system>
     <configuration>
         <threads count="8" />
     </configuration>
  </system>
  <project name="project1">
    <modificationset>
       <svn RepositoryLocation="svn+ssh://dev/svn/project/trunk"
          username="integration" password="integration" >
    </modificationset>
  </project>
</cruisecontrol>

Если вы предпочитаете выполнять сборку из локальной копии репозитория, то можно использовать атрибут LocalWorkingCopy, как показано в листинге 5.

Листинг 5. Запуск сборки из проверенной локальной копии исходного кода
  <project name="project1">
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
  </project>

Обратите внимание на то, что в теге <svn> необходимо использовать либо атрибут RepositoryLocation, либо атрибут LocalWorkingCopy. Атрибуты username и password являются необязательными и их необходимо использовать только если этого требует система аутентификации. Для других элементов управления исходным кодом используются аналогичные теги, например, <cvs> и <vss>. Код из листинга 5 наводит на следующий интересный вопрос: если указана только локальная рабочая копия, то кто отвечает за то, чтобы она всегда была актуальной? Ответ на этот вопрос приводит нас к тегу <bootstrappers>. В зависимости от используемого репозитория тег <bootstrappers> может иметь дочерние теги, например, <svnbootstrapper> или <clearcasebootstrapper>. В листинге 6 продемонстрирована автоматическая установка системы в заданное состояние для конфигурации из листинга 5.

Листинг 6. Проверка исходного кода с помощью bootstrapper
  <project name="project1">
    <bootstrappers>
       <svnbootstrappers localWorkingCopy="/build/checkout/project1" 
          username="integration" password="integration" >
    </bootstrappers>
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
  </project>

Тег <svnbootstrapper> указывает CruiseControl на локальную копию исходного кода, к которой необходимо применить команду обновления перед запуском процесса сборки.

Итак, исходный код настроен. Теперь нужно определиться с тем, когда должен запускаться процесс сборки и какую команду следует запускать для этого. Как вы уже можете предположить, нам потребуется новый тег. Итак, встречайте тег <schedule>, использование которого демонстрируется в листинге 7.

Листинг 7. Выполнение сборки с помощью тега schedule
  <project name="project1">
    <bootstrappers>
       <svnbootstrappers localWorkingCopy="/build/checkout/project1" 
          username="integration" password="integration" >
    </bootstrappers>
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
    <schedule>
       <exec command="/opt/build-tools/mybuildscript.sh"
                  workingdir="/build/checkout/project1"
                  args="link=shared variant=debug j4"
                  errorstr="build failed" >
    </schedule>
  </project>

Тег <exec> (дочерний тег <schedule>) выполняет указанную команду или сценарий как часть процесса сборки для указанной в атрибуте workingdir директории. На случай, если сборка завершится с ошибкой, предусмотрен атрибут errorstr, с помощью которого можно задать пользовательскую строку, содержащую сообщение об ошибке. Если необходимо выполнять сборку с заданной периодичностью независимо от того, был ли изменен исходный код в репозитории, или нет, можно использовать атрибут interval тега <schedule>, в котором указывается временной интервал (в секундах) между сборками, как показано в листинге 8.

Листинг 8. Задание временного интервала (1 час) между сборками
  <project name="project1" requireModifications=no>
    <bootstrappers>
       <svnbootstrappers localWorkingCopy="/build/checkout/project1" 
          username="integration" password="integration" >
    </bootstrappers>
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
    <schedule interval=3600>
       <exec command="/opt/build-tools/mybuildscript.sh"
                  workingdir="/build/checkout/project1"
                  args="link=shared variant=debug j4"
                  errorstr="build failed" >
    </schedule>
  </project>

После того как процесс сборки будет запущен, может возникнуть необходимость в отслеживании ее статуса. Вероятно, проще всего использовать для этого Web-интерфейс. Так мы подошли к рассмотрению дочернего элемента тега <project> под названием listeners. Особый интерес для нас представляет тег <currentbuildstatuslistener>, который записывает статус сборки в файл. Если требуется явно указать директорию, в которой должны создаваться log-файлы сборки, используйте для этого тег <log> и его необязательный атрибут dir. Тег <log> содержит дочерний тег <merge>, который используется для объединения различной информации интеграционного тестирования в виде файла журнала CruiseControl. Команда

<merge dir=dir-name pattern="*.xml">

объединяет все XML-файлы из директории dir-name в один log-файл CruiseControl. Это полезная функция, если вам необходимо видеть общую картину двух процессов – сборки и тестирования. Такая конфигурация приведена в листинге 9.

Листинг 9. Обновленная конфигурация
  <project name="project1" requireModifications=no>
    <bootstrappers>
       <svnbootstrappers localWorkingCopy="/build/checkout/project1" 
          username="integration" password="integration" >
    </bootstrappers>
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
    <schedule interval=3600>
       <exec command="/opt/build-tools/mybuildscript.sh"
                  workingdir="/build/checkout/project1"
                  args="link=shared variant=debug j4"
                  errorstr="build failed" >
    </schedule>
    <log dir="/build/logs/project1">
       <merge dir="/build/tests/project1" pattern="*.output" />
    </log>
    <listeners>
       <currentbuildstatuslistener file="/build/staus/latest" />
    </listeners>
  </project>

Мы почти закончили, если не считать нескольких действий, выполняемых по окончании сборки. Например, может потребоваться отправлять сообщения разработчикам или поместить весь исходный код в zip-файл. Здесь нам может помочь тег <publishers>. Этот тег имеет множество дочерних тегов, позволяющих выполнять различные действия по окончании процесса сборки. Для наших целей можно использовать самый распространенный подход – отправку уведомлений по электронной почте. В этом случае тег <publishers можно использовать с его дочерним тегом <email> или <htmlemail>, как показано в листинге 10.

Листинг 10. Добавление функции уведомления по электронной почте
  <project name="project1" requireModifications=no>
    <bootstrappers>
       <svnbootstrappers localWorkingCopy="/build/checkout/project1" 
          username="integration" password="integration" >
    </bootstrappers>
    <modificationset>
       <svn LocalWorkingCopy="/build/checkout/project1">
    </modificationset>
    <schedule interval=3600>
       <exec command="/opt/build-tools/mybuildscript.sh"
                  workingdir="/build/checkout/project1"
                  args="link=shared variant=debug j4"
                  errorstr="build failed" >
    </schedule>
    <log dir="/build/logs/project1">
       <merge dir="/build/tests/project1" pattern="*.output" />
    </log>
    <listeners>
       <currentbuildstatuslistener file="/build/staus/latest" />
    </listeners>
    <publishers>
        <email mailhost="smtp.myjob.com" subject="Build/Test Stats">
            <failure address="all@myjob.com" />
            <success address="developers@myjob.com" />
        </email> 
    </publishers>
  </project>

SCons

Еще одним отличным инструментом автоматизации сборки программного обеспечения является SCons. SCons означает software construction (конструирование программного обеспечения) и с легкостью заменяет собой make. Зачем же использовать SCons, если у нас уже есть make? Прежде всего make, automake, autoconf и прочие инструменты имеют специальный синтаксис, тогда как SCons основан на Python. Кроме того, синтаксис SCons намного проще запомнить, а с файлами SConstruct (аналог Makefile) проще работать. Также SCons обладает специализированной поддержкой файлов C/C++ и Java™ и обладает всей функциональностью make, используя намного меньше кода.

SCons и Python

В этой статье используется версия SCons 2.1, которая не поддерживает версии Python старше 2.3.5.

Приложение "Hello, World" на SCons

В листинге 11 представлен код простой программы "Hello, World", написанный на C++.

Листинг 11. Программа "Hello, World" на C++
#include <iostream>
using namespace std;

int main( )
{
  cout << "Hello World" >> endl;
}

Ниже приводится содержимое SConstruct-файла для создания исполняемого файла:

env = Environment()
env.Program(hello.cpp)

Если вы сравните это с вариантом использования make, то сразу же ощутите все преимущества SCons. Функция Program носит название builder_method и является API-интерфейсом Python, который говорит SCons о том, что нужно выполнить сборку исполняемого файла. Можно также одновременно выполнять сборку нескольких исполняемых файлов и выводить в процессе сборки различные сообщения, как показано в листинге 12.

Листинг 12. Сборка нескольких исполняемых файлов с выводом дополнительных сообщений
env = Environment()
env.Program('hello.cpp')

print "Done with building hello, on to hello2"       
env.Program('hello2.cpp')
print "Finished building hello2"

Для чего здесь используется env? В терминах SCons мы создаем среду конструирования, а объект env содержит различную информацию о компиляторе, флагах компоновщика и т. д.

Полезные приемы использования SCons

Ниже приводится несколько типовых задач, которые можно выполнять при помощи SCons:

  • Присвоение исполняемому файлу произвольного имени. Например, при сборке исходного файла hello2.cpp необходимо получить исполняемый файл с названием newworld2. Для этого укажите требуемое имя в качестве первого аргумента Program:

    env = Environment()
    print "Beginning building newworld2"
    env.Program('newworld2',  'hello2.cpp')
    print "Finished building newworld2"
  • Сборка исполняемого файла из нескольких исходных файлов. Например, файл newworld2 зависит от файлов hello2.cpp и world3.cpp. В этом случае SConstruct-файл должен выглядеть следующим образом:

    env = Environment()
    print "Beginning building newworld2"
    env.Program('newworld2',  ['hello2.cpp', 'world3.cpp'])
    print "Finished building newworld2"
  • Чтобы очистить сборку, просто наберите в командной строке scons c.
  • Сборка двух исполняемых файлов, имеющих несколько общих исходных файлов. Логично выполнять компиляцию этих файлов только один раз. Вот, как можно это сделать:

    env = Environment()
    
    common = ['common1.cpp', 'common2.cpp']
    hello1_sources = ['hello1.cpp'] + common
    hello2_sources = ['hello2.cpp', 'world2.cpp'] + common
           
    env.Program('hello1', hello1_sources)
    env.Program('hello2', hello2_sources)
  • Часто бывает необходимо выполнить компоновку с определенными библиотеками, например, pthread или math. Функция Program может работать с дополнительными аргументами, содержащими переменные LIBS и LIBPATH. Ниже показано, как можно выполнить компоновку с библиотекой pthread:

    env = Environment()
    env.Program('new-exe',
                   'concurrent.cpp', 
                   LIBS = 'pthread',
            LIBPATH = ['/usr/lib', '/usr/local/lib'])
  • Что если для создания исполняемого файла требуются все cpp-файлы, хранящиеся в директории? К счастью, нет необходимости перечислять их все. Эту задачу упрощает функция Glob:

    env = Environment()
    Program('new-exe',
                   Glob('*.cpp'),
                   LIBS = 'pthread',
            LIBPATH = ['/usr/lib', '/usr/local/lib'])
  • С помощью SCons очень просто собрать статическую библиотеку. Для этого просто вызовите функцию Library:

    env = Environment()
    env.Library('arith', ['math1.cpp', 'math2.cpp', 'math3.cpp'])

    Обратите внимание на то, что внутренние механизмы SCons создают файлы math1.o, math2.o и math3.o, затем ar rc libarith.a math1.o math2.o math3.o и, наконец, ranlib libarith.a.

  • Создать совместно используемую библиотеку в SCons так же просто, как и статическую:

    env = Environment()
    env.SharedLibrary('arith', ['math1.cpp', 'math2.cpp', 'math3.cpp'])
  • Может возникнуть необходимость скомпилировать разные исходные файлы с разными опциями. Например, если необходимо передать в исходный код опцию DX86_64, означающую оптимизацию для 64-разрядных платформ, то SConstruct-файл будет выглядеть следующим образом:

    env = Environment()
    env.Object('hello.cpp', CCFLAGS='-DX86_64')
    env.Object('world.cpp', CCFLAGS='-DX86_64')
    env.Program('new-exe', ['hello.o', 'world.o']

    Обратите внимание на новую функцию компоновщика Object. Функция Program может выполнять сборку исполняемых модулей из объектных файлов, поэтому нет необходимости указывать исходные файлы, как это делалось раньше.

  • В этой статье я упоминал о том, что имя целевого файла должно являться первым аргументом. Если вы хотите сделать все наоборот или поиграть с порядком аргументов, то можно поступить так:

    common = ['common1.cpp', 'common2.cpp']
    hello1_sources = ['hello1.cpp'] + common
    hello2_sources = ['hello2.cpp', 'world2.cpp'] + common
           
    Program(source = hello1_sources, target = 'hello1')
    Program(target = 'hello2', source = hello2_sources)

    Обратите внимание на то, что source и target – это специальные ключевые слова SCons.

  • Хотя Program, SharedLibrary и остальные функции являются членами объекта env, их можно использовать без указания префикса env. Таким образом, допустимо использовать следующий SConstruct-файл:

    common = ['common1.cpp', 'common2.cpp']
    hello1_sources = ['hello1.cpp'] + common
    hello2_sources = ['hello2.cpp', 'world2.cpp'] + common
           
    Program('hello1', hello1_sources)
    Program('hello2', hello2_sources)

Заключение

Из этой статьи вы узнали о CruiseControl и непрерывной интеграции, а также о наиболее важных тегах (<bootstrappers>, <modificationset>, <schedule> и <listeners>). Также мы применили систему SCons для сборки небольшого проекта на C++ и узнали, как выполнять сборку статических и совместно используемых библиотек. Оба этих инструмента обладают гораздо большими возможностями, чем рассматривалось в этой статье, поэтому обязательно обратитесь за дополнительной информацией к разделу Ресурсы.

Ресурсы

Научиться

Получить продукты и технологии

  • Загрузите последнюю версию CruiseControl (EN) с Web-сайта проекта.
  • Загрузите SCons версии 2.1 (EN) с Web-сайта SourceForge.
  • Получите дополнительную информацию и загрузите систему Subversion (EN).

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=AIX и UNIX
ArticleID=934072
ArticleTitle=Изучаем инструменты автоматизации сборки программного обеспечения нового поколения
publish-date=06142013