Pull to refresh

Построение «правильного» процесса разработки на платформе Mono

Reading time20 min
Views13K
Элементарный пример цикла разработка примитивного ASP.NET (Mono) приложения с использованием Jenkins CI, по мотивам Построение «правильного» процесса разработки на платформе .NET.

Представленный пример может быть интересен широкой аудитории, т.к. легко может быть адаптирован для разработки под любую другую платформу.


Вступление

Эта статья никогда бы не была бы написана, если бы не статья-оригинал, которая в своё время сыграла ключевую роль в формировании моего представления о процессе разработки. И, как говориться, «пользуясь случаем хочу». Хочу поблагодарить Евгения за его замечательную статью, которая является просто замечательным стартом для начинающих разработчиков! Огромное тебе спасибо!

Данная статья, по большому счету, не является чем-нибудь принципиально новым. Тот же C#-проект, те же тесты на NUnit и та же автоматизация на NAnt. Но есть нюансы. Во-первых: в качестве CI-сервера используется Jenkins CI, а во-вторых: уделено значительное внимание анализу и представлению различных метрик проекта в процессе его сборки.

В статье последовательно будет описан процесс настройки рабочей среды и создания сборочно проекта, который будет автоматически отслеживать изменения в исходном коде, производить компиляцию проекта, выполнять юнит и функциональные тесты, а так же собирать метрики, такие как количество строк кода, наличие его дубликатов, наличие в исходном коде технических долгов (TODO, FIXME и т.п.) Стержнем проекта будет являтся NAnt-скрипт, который будет наращиваться по мере рассмотрения материала. На любом этапе проект является рабочим и может быть выполнен, что может оказаться очень удобно для тех кто не сумеет создать весь проект за раз либо не нуждается во всем представленном функционале (а он, откровенно говоря, в некоторых вопросах избыточен).


Workspace

Для настройки рабочего пространства потребуются:
OS GNU/Linux (openSUSE 12.1, LXDE) Операционная система на базе которой будет развернут сервер непрерывной интеграции.
CIS Jenkins CI 1.450 Сервер непрерывной интеграции.
VCS Subversion 1.6.17 Централизованная система контроля версий (вместо Subversion может быть использована любая другая VCS)
CLR Mono 2.6.10 Платформа под которой производится разработка.
Testing NUnit 2.4.8 Инструмент для создания и выполнения тестов (юнит, неюнит — это уже зависит от того, кто будет создавать тесты).
Selenium RC 2.18.0 Инструмент для выполнения функциональных тестов.
Static code analyze Gendarme 2.10.0.0 Статический анализатор кода.
Cloneanalyzer 2005-05-30 Утилита для поиска дубликатов кода.
StyleCopCmd 0.2.1 Статический анализатор кода.
Other tools NAnt 0.90 Утилита для автоматической сборки проекта.
Firefox 7.0.1 Web-браузер с помощью которого будут создаваться и выполняться функциональные тесты (выполнять тесты можно и при помощи других браузеров)
Selenium IDE 1.6.0 IDE для создания функциональных тестов.
Для настройки рабочей среды необходимо выполнить следующие действия:
1. Скачать DVD образ и установить ОС (при выборе окружения рабочего стола указать LXDE). Строго говоря, может быть использовано и другое Desktop Environment, либо не использоваться вообще. В примере используется LXDE, что бы сделать процесс настройки проще и не более.
2. Настроить резпозитории:
sudo zypper ar http://download.opensuse.org/repositories/Mono/openSUSE_11.4/ Mono
sudo zypper ar http://pkg.jenkins-ci.org/opensuse/ Jenkins
3. Установить необходимые пакеты:
sudo zypper in jenkins mono-complete mono-nunit mono-tools nant subversion-server apache2 http://www.dwheeler.com/sloccount/sloccount-2.26-1.i386.rpm
4. Скачать и установить необходимые утилиты, которые не поставляются в виде rpm-пакетов (StyleCopCmd и CloneAnalyzer). Тут есть несколько нюансов.
Во-первых: использовать бинарные файлы под Linux из коробки нет возможности. Приложение в целом работает, но из-за жестко установленного разделителя пути в строке 460 файла ReportBuilder.cs файл с отчетами оказывается не совсем там, где это ожидается:
        private static string GetViolationsFile(string outputXmlFile)
        {
            var offp = Path.GetFullPath(outputXmlFile);
            var f = string.Format(
                CultureInfo.CurrentCulture,
                "{0}\\{1}.violations.xml", // String No 460, wrong separator here!
                Path.GetFullPath(Path.GetDirectoryName(offp)),
                Path.GetFileNameWithoutExtension(outputXmlFile));
            return f;
        }
Поправленный вариант, а так же файл конфигурации, можно скачать здесь.
Во-вторых: DRY plug-in не распознает отчетов утилиты CloneAnalyze и поэтому необходимо самостоятельно преобразовать отчет CloneAnalyze в один из понятных Jenkins'y (я выбрал CPD). Примеры отчетов, а так же написанный на скорую руку конвертер с исходным кодом можно взять здесь.
Создаем директория для дополнительных утилит:
sudo mkdir -p /var/lib/jenkins/tools/{StyleCop,CloneAnalyzer,SeleniumRC}
# 1. В папке SlyleCop размещаем файлы приложения StyleCopCmd.
# ...

# 2. Устанавливаем CloneAnalyzer
cd /var/lib/jenkins/tools/CloneAnalyzer
sudo wget http://sourceforge.net/projects/cloneanalyzer/files/latest/download?source=files
sudo unzip CloneAnalyzerPluginInstall_20050530.zip
sudo mv eclipse/plugins/CloneAnalyzer .
rm -rf eclipse

# 3. В одной директории с CloneAnalyzer размещаем конвертер отчетов.
# ...

# 4. Устанавливаем Selenium Remoute Control.
cd /var/lib/jenkins/tools/SeleniumRC
sudo wget http://selenium.googlecode.com/files/selenium-server-standalone-2.19.0.jar
5. Установить plug-in'ы для Jenkins'a (Jenkins -> Manage Jenkins -> Manage Plugins -> Available):
Обязательно
Subversion Позволяет автоматизировать операции получения исходного кода из svn-репозитория (установлен по умолчанию)
NUnit Позволяет строить отчеты по результатам работы NUnit.
NAnt Позволяет задавать NAnt-скрипты в качестве сборочных целей Jenkins-проекта.
Static Code Analysis Необходим для DRY плагина.
Task Scanner Позволяет строить отчеты о найденных в коде меток (TODO и другие).
SLOCCount Позволяет строить отчеты по результатам работы утилиты SLOCCount (отображает скромные метрики кода).
DocLinks Позволяет размещать на главной странице проекта ссылку на документацию к проекту.
Violations Позволяет строить отчеты по результатам работы различных утилит. В данном примере используется для отображения результатов работы Gendarme и StyleCopCmd.
DRY Позволяет строить отчеты о найденных дубликатах кода.
Seleniumhq Размещает ссылки на отчет Selenium.
Warnings Позволяет строить отчеты, отображая предупреждения компилятора.
Рекомендовано:
JobConfigHistory Хранит историю изменений настроек проекта.
Backup Позволяет упростить процесс создания резервной копии сервера.
Опционально:
Green Balls Заменяет синий шарик на зеленый.
Next Build Number Позволяет устанавливать произвольный номер сборки.
Sidebar-Link Позволяет создавать ссылки на главной странице сервера и на страницах проектов.


Описание демонстрационного проекта

К сожалению придумать простой, но в то же время не перегруженный логикой предметной области и удобный для тестирования проект я не сумел. Представленный проект прост до неприличия — два поля ввода, две кнопки («Сумма» и «Конкатенация») и результат.
Для создания приложения создано решение (solution), включающее в себя три проекта:
  • ExampleCore — в котором сосредоточена вся логика приложения (а это на секундочку, аж целых два метода)!
  • ExampleGUI — интерфейс приложения (одна единственная, но от этого ещё более важная, aspx-страница).
  • ExampleUTest — проект с тестами (NUnit).
  • ExampleFTest — так же в корневом каталоге расположена папка ExampleFTest с функциональными тестами (Selenium), которая не входит в решение.




Создание сборочного скрипта и настройка инструментов

В первую очередь необходимо создать Jenkins-проект, для чего необходимо через web-интерфейс, доступный по адресу: http://localhost:8080 (если Jenkins установлен на локальном компьютере), перейти по ссылке New Job, выбрать тип проекта Build a free-style software project, ввести имя проекта и создать его. Проект создан.
Как и упоминалось ранее стержнем сборочного проекта будет NAnt-скрипт, для этого необходимо создать в секции Build, цель, которая будет вызывать скрипт:Вообще, может быть несколько подходов к организации проекта, которые имеют свои преимущества и недостатки. В примере все действия помещены в один NAnt-скрипт, который вызывается одной командой в Jenkins-проекте. Это удобно тем, что сборку очень легко произвести минуя Jenkins, достаточно просто выполнить NAnt-скрипт. Но не всегда удобно изменять процесс сборки (сначала нужно внести изменение в NAnt-скрипт, затем выполнить коммит и только тогда процесс сборки обновиться). В противовес этому подходу можно создавать в Jenkins-проекте много целей по выполнению bash-скриптов и всю логику сборки разместить в них. В таком случае удобно редактировать процесс сборки, но выполнить сборку вне Jenkins будет нельзя.
Приступим к созданию Nant-скрипта, который имеет следующий вид:
<?xml version="1.0"?>
    <project name="Example" default="cis" basedir=".">

        <!-- Property -->

        <!--Targets -->

        <target name="cis" description="Execute all targets in CIS.">
            <call target="clean" />
            <call target="build" />
            <call target="documentation" />
            <call target="utest" />
            <call target="gendarme" />
            <call target="stylecop" />
            <call target="sloccount" />
            <call target="cloneanalyze" />
            <call target="ftest" />
        </target>
</project>
Т.е. сначала объявляются свойства (Property), затем объявляются цели (Target) и в заключении объявляется главная цель (её имя указывается в свойстве default скрипта), которая поочередно вызывает объявленные ранее. Описанный способ не единственный, вместо создания цели вызывающей другие, можно просто прописывать зависимость одних целей от других и тогда вызов целей будет производится автоматически.
Далее будут реализованы все цели, которые вызывает цель cis. На любом этапе скрипт может быть выполнен, для чего в главной цели (cis) необходимо закомментировать еще нереализованные цели и неиспользуемые свойства.

Для простоты объявим свойства, которые в дальнейшем сократят нам код.
Переменная окружения устанавливаемая Jenkins'ом:
        <property name="work.d"      value="${environment::get-variable('WORKSPACE')}" />
Директория в которой собраны инструменты:
        <property name="tools.d"     value="/var/lib/jenkins/tools" />
Алиасы различных директорий, назначение которых очевидно из названия:
        <property name="bin.d"       value="${build.conf}/bin"/>
        <property name="deploy.d"    value="/home/vm/public_html" />
        <property name="test.res.d"  value="test-results" />
        <property name="report.d"    value="${work.d}/reports" />
        <property name="doc.d"       value="${work.d}/doc" />
        <property name="core.d"      value="${work.d}/ExampleCore" />
        <property name="gui.d"       value="${work.d}/ExampleGUI" />
        <property name="utest.d"     value="${work.d}/ExampleUTest" />
        <property name="ftest.d"     value="${work.d}/ExampleFTest" />
Алиасы бинарного файла и файла настроек StyleCopCmd:
        <property name="style.exe"   value="${tools.d}/StyleCop/Net.SF.StyleCopCmd.Console.exe" />
        <property name="style.conf"  value="${tools.d}/StyleCop/Settings.StyleCop" />
Алиасы исполняемого файла, конвертера и файла конфигурации CloneAnalyzer:
        <property name="clone.jar"   value="${tools.d}/CloneAnalyzer/CloneAnalyzer.jar" />
        <property name="clone.conf"  value="${tools.d}/CloneAnalyzer/comments.conf" />
        <property name="clone.conv"  value="${tools.d}/CloneAnalyzer/ca2cpd.exe" />
Алиасы исполняемого файла, обёрточного скрипта, а так же файла с набором тестов и именем хоста на котором развернуто приложение для SeleniumRC:
        <property name="selen.jar"   value="${tools.d}/SeleniumRC/selenium-server-standalone-2.18.0.jar" />
        <property name="selen.sh"    value="${tools.d}/SeleniumRC/selenium.sh" />
        <property name="selen.host"  value="http://192.168.56.210" />
        <property name="selen.suite" value="${ftest.d}/Main.html" />

Приступим к созданию целей.
1. Первым делом создадим цель по очистке сборочной директории от старых артефактов и созданию необходимых директорий:
        <target name="clean" description="Remove binary files, recreate report directory.">
            <echo message="Target starded at: ${datetime::now()}."/>
            <delete failonerror="false" dir= "${core.d}/${bin.d}"/>
            <delete failonerror="false" file="${core.d}/ExampleCore.pidb"/>
            <delete failonerror="false" dir= "${utest.d}/${bin.d}"/>
            <delete failonerror="false" dir= "${utest.d}/${test.res.d}"/>
            <delete failonerror="false" file="${utest.d}/ExampleUTest.pidb"/>
            <delete failonerror="false" dir= "${gui.d}/${bin.d}"/>
            <delete failonerror="false" dir= "${gui.d}/${test.res.d}"/>
            <delete failonerror="false" file="${gui.d}/ExampleGUI.pidb"/>
            <delete failonerror="false" dir= "${report.d}"/>
            <delete failonerror="false" dir= "${doc.d}"/>
            <delete failonerror="false" file="${work.d}/stylecop.report"/>
            <delete failonerror="false" file="${work.d}/stylecop.violations.xml"/>
            <mkdir dir="${report.d}"/>
            <mkdir dir="${report.d}/gendarme"/>
            <mkdir dir="${report.d}/sloccount"/>
            <mkdir dir="${report.d}/cloneanalyzer"/>
            <mkdir dir="${report.d}/selenium"/>
            <mkdir dir="${doc.d}/xml"/>
            <mkdir dir="${doc.d}/html"/>
            <echo message="Target completed at: ${datetime::now()}."/>
        </target>

2. Вторым шагом могло бы быть получения обновлений из репозитория. Поскольку эта операция выполняется Jenkins'ом, то в данном примере NAnt-скрипт её не содержит, но если бы в ней была необходимость, то её место здесь.
Создадим и настроим репозиторий Subversion:
su
a2enmod dav
a2enmod dav_svn
a2enmod mod_authz_svn
cd /srv/www/htdocs
wget http://tortoisesvn.googlecode.com/svn/trunk/contrib/svnindex/menucheckout.ico
wget http://tortoisesvn.googlecode.com/svn/trunk/contrib/svnindex/svnindex.css
wget http://tortoisesvn.googlecode.com/svn/trunk/contrib/svnindex/svnindex.xsl
mkdir -p /srv/svn/{repos,user_access,html}

cat > /etc/apache2/conf.d/subversion.conf << EOF
<IfModule mod_alias.c>
    Alias /repos "/srv/svn/html"
</IfModule>
<Directory /srv/svn/html>
    Options        +Indexes +Multiviews -FollowSymLinks
    IndexOptions   FancyIndexing \
                   ScanHTMLTitles \
                   NameWidth=* \
                   DescriptionWidth=* \
                   SuppressLastModified \
                   SuppressSize
    order allow,deny
    allow from all
</Directory>

<Location /repos/Example>
    DAV svn
    SVNListParentPath on
    SVNPath /srv/svn/repos/Example
    SVNIndexXSLT "/svnindex.xsl"
    AuthType Basic
    AuthName "Subversion"
    AuthUserFile /srv/svn/user_access/passwdfile
    AuthGroupFile /srv/svn/user_access/groupfile
    AuthzSVNAccessFile /srv/svn/user_access/accessfile
    Require valid-user
</Location> 
EOF

cd /srv/svn/repos
svnadmin create --fs-type fsfs Example
mkdir -p /srv/svn/repos/Example/dav
chown -R wwwrun:www Example/{dav,db,locks}
touch /srv/svn/user_access/passwdfile
chown root:www /srv/svn/user_access/passwdfile
chmod 640 /srv/svn/user_access/passwdfile

touch /srv/svn/user_access/groupfile
cat > /srv/svn/user_access/groupfile << EOF
Example_commiters: Admin User
Example_readers: Admin User CIS

touch /srv/svn/user_access/accessfile
cat > /srv/svn/user_access/accessfile << EOF
[groups]
admin = Admin
user = User
cis = CIS
    
[/]
* = 
@admin = rw

[Example:/]
@user = rw
@cis = r

/sbin/service apache2 restart
exit

После этого настроим Subversion плагин:

А так же зададим периодичность сборки (производить сборку при наличии обновлений, наличие которых проверять каждый час в рабочие дни недели):

3. Создадим цель которая будет осуществлять компиляцию с генерацию документации в формате xml подпроектов ExampleCore и ExampleGUI:
        <target name="build" description="Compiles the source code.">
            <echo message="Target starded at: ${datetime::now()}."/>
            <echo message="Building ExampleCore."/>
            <csc codepage="utf8" target="library"
                output="${core.d}/${bin.d}/${build.conf}/ExampleCore.dll"
                doc="${doc.d}/xml/ExampleCore.xml">
                <sources>
                    <include name="${core.d}/**.cs"/>
                </sources>
            </csc>
            <copy
                file="${core.d}/${bin.d}/${build.conf}/ExampleCore.dll"
                tofile="${gui.d}/${bin.d}/${build.conf}/ExampleCore.dll" />
            <copy
                file="${core.d}/${bin.d}/${build.conf}/ExampleCore.dll"
                tofile="${utest.d}/${bin.d}/${build.conf}/ExampleCore.dll" />

            <echo message="Building ExampleGUI."/>
            <csc codepage="utf8" target="library"
                output="${gui.d}/${bin.d}/${build.conf}/ExampleGUI.dll"
                doc="${doc.d}/xml/ExampleGUI.xml">
                <sources>
                    <include name="${gui.d}/**.cs" />
                </sources>
                <references>
                    <include name="System.Web.dll" />
                    <include name="${gui.d}/${bin.d}/${build.conf}/ExampleCore.dll" />
                </references>
            </csc>
            <echo message="Target completed at: ${datetime::now()}." />
        </target>

Настроим плагин Warnings таким образом, что бы сообщения компилятора обрабатывались Jenkins'ом:

В дальнейшем это позволит получать вот такие отчеты по Warning'ам компилятора:

4. Сгенерируем документацию в формате html:
        <target name="documentation" description="Generation documentation.">
            <echo message="Target starded at: ${datetime::now()}."/>
            <exec
                program="monodocer"
                commandline="
                    -pretty
                    -i:${doc.d}/xml/ExampleCore.xml
                    -assembly:${core.d}/${bin.d}/${build.conf}/ExampleCore.dll
                    -path:${doc.d}/xml"/>
            <exec
                program="monodocer"
                commandline="
                    -pretty
                    -i:${doc.d}/xml/ExampleGUI.xml
                    -assembly:${gui.d}/${bin.d}/${build.conf}/ExampleGUI.dll
                    -path:${doc.d}/xml"/>
            <exec
                program="mdoc"
                commandline="export-html ${doc.d}/xml -o=${doc.d}/html"/>
            <echo message="Target completed at: ${datetime::now()}." />
        </target>

Настроим плагин DocLinks:В результате на главной странице проекта будет создана ссылка на документацию.

5. Соберём и запустим юнит-тесты:
        <target name="utest" description="Test the source code.">
            <echo message="Target starded at: ${datetime::now()}."/>
            <echo message="Building ExampleUTest."/>
            <csc codepage="utf8" target="library"
                output="${utest.d}/${bin.d}/${build.conf}/ExampleUTest.dll">
                <sources>
                    <include name="${utest.d}/**.cs" />
                </sources>
                <references>
                    <include name="System.Web.dll" />
                    <include name="${gui.d}/${bin.d}/${build.conf}/ExampleCore.dll" />
                    <include name="nunit.core.dll" />
                    <include name="nunit.framework.dll" />
                </references>
            </csc>

            <echo message="Launch NUnit." />
            <nunit2 haltonfailure="false">
                <formatter type="Xml"
                    usefile="true"
                    extension=".xml"
                    outputdir="${utest.d}/${test.res.d}" />
                <formatter type="Plain" usefile="false" />
                <test assemblyname="${utest.d}/${bin.d}/${build.conf}/ExampleUTest.dll" />
            </nunit2>
            <echo message="Target completed at: ${datetime::now()}." />
        </target>

Настроим плагин NUnit:

Отчеты плагина NUnit выглядят следующим образом:

6. Выполним статический анализ кода при помощи Gendarme:
        <target name="gendarme">
            <echo message="Target starded at: ${datetime::now()}."/>
            <echo message="Check code by Gendarme."/>
            <exec program="gendarme" failonerror="false"
                commandline="
                    --config GendarmeRules.xml ${core.d}/${bin.d}/${build.conf}/ExampleCore.dll
                    --xml ${report.d}/gendarme/ExampleCore.gendarme.xml
                    --severity medium+
                    --confidence total"/>
            <exec program="gendarme" failonerror="false"
                commandline="
                    --config GendarmeRules.xml ${gui.d}/${bin.d}/${build.conf}/ExampleGUI.dll
                    --xml ${report.d}/gendarme/ExampleGUI.gendarme.xml
                    --severity medium+
                    --confidence total"/>
            <echo message="Target completed at: ${datetime::now()}." />
        </target>
… и StyleCopCmd:
        <target name="stylecop">
            <echo message="Target starded at: ${datetime::now()}."/>
            <exec program="mono"
                commandline="
                    ${style.exe}
                    -r
                    -sc ${style.conf}
                    -d ${work.d}
                    -of ${work.d}/stylecop/stylecop.report"/>
            <echo message="Target completed at: ${datetime::now()}." />
        </target>
Что бы избавиться ошибки, вызываемой StyleCopCmd:
While saving registry data at /etc/mono/2.0/../registry/last-btime: System.UnauthorizedAccessException: Access to the path "/etc/mono/registry/last-btime" is denied.
 at System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options) [0x00000] in <filename unknown>:0 
 at System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share) [0x00000] in <filename unknown>:0 
 at (wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare)
 at System.IO.StreamWriter..ctor (System.String path, Boolean append, System.Text.Encoding encoding, Int32 bufferSize) [0x00000] in <filename unknown>:0 
 at System.IO.StreamWriter..ctor (System.String path, Boolean append, System.Text.Encoding encoding) [0x00000] in <filename unknown>:0 
 at (wrapper remoting-invoke-with-check) System.IO.StreamWriter:.ctor (string,bool,System.Text.Encoding)
 at Microsoft.Win32.KeyHandler.SaveRegisteredBootTime (System.String path, Int64 btime) [0x00000] in <filename unknown>:0
Создадим вожделенный файл с правами на запись всем желающим:
sudo touch /etc/mono/registry/last-btime
sudo chmod 666 /etc/mono/registry/last-btime

Настроим плагин Violations:

Так будут выглядеть отчеты:

Тут есть несколько неприятных моментов: во-первых: для того, что бы успешно отображался детализированный отчет по StyleCop отчет должен лежать в корне проекта (что нарушает общую тенденцию), а во-вторых: детализированный отчет по Gendarme мне так и не удалось настроить (кто сталкивался — прошу помощи).

7. Собираем метрики:
        <target name="sloccount">
            <echo message="Target starded at: ${datetime::now()}."/>
            <echo message="Analyze code by SLOCCount."/>
            <exec program="sloccount"
                output="${report.d}/sloccount/sloccount.report"
                commandline="
                    --duplicates
                    --wide
                    --details ${work.d}"/>
            <echo message="Target completed at: ${datetime::now()}." />
        </target>

Настроим плагин SLOCCount:

Пример отчета:

8. Выполняем поиск дубликатов в два этапа: сначала запустим приложение CloneAnalyzer, а потом выполним конвертацию полученного отчета:
        <target name="cloneanalyze">
            <echo message="Target starded at: ${datetime::now()}."/>
            <echo message="Find code duplicates by CloneAnalyze."/>
            <exec program="java" failonerror="false"
                output="${report.d}/cloneanalyzer/cloneanalyzer.report.txt"
                commandline="
                    -jar ${clone.jar}
                    -c ${clone.conf}
                    -f .*\.\(cs\|aspx\)
                    -d ${work.d}"/>
            <echo message="Convert CloneAnalyze report in CPD report."/>
            <exec program="mono" failonerror="false"
                commandline="
                    ${clone.conv}
                    ${report.d}/cloneanalyzer/cloneanalyzer.report.txt
                    ${report.d}/cloneanalyzer/cloneanalyzer.report.xml"/>
            <echo message="Target completed at: ${datetime::now()}." />
        </target>

Настроим плагин DRY:

Пример отчета:

9. Выполним функциональные тесты, для чего сначала развернем приложение, перезапустим web-сервер и собственно запустим тесты:
        <target name="ftest">
            <echo message="Target starded at: ${datetime::now()}."/>
            <echo message="Prepare to launch tests."/>
            <copy todir="${deploy.d}/${bin.d}/">
                <fileset basedir="${gui.d}/${bin.d}/${build.conf}">
                    <include name="*.dll"/>
                </fileset>
            </copy>
            <copy todir="${deploy.d}/">
                <fileset basedir="${gui.d}/">
                    <include name="*.aspx"/>
                    <include name="*.asax"/>
                    <include name="*.config"/>
                </fileset>
            </copy>

            <echo message="Restart apache2 server."/>
            <exec program="/bin/bash" commandline="-c 'sudo /etc/init.d/apache2 restart'"/>

            <echo message="Testing project by Selenium."/>
            <exec program="${selen.sh}" failonerror="false"
                commandline="
                    -htmlSuite
                    *firefox
                    ${selen.host}
                    ${selen.suite}
                    ${report.d}/selenium/selenium.html"/>
            <echo message="Target completed at: ${datetime::now()}." />
        </target>

Для того, что бы пользователь jenkins мог выполнять перезапуск web-сервера, ему необходимо дать соответствующие права:
su
cat > /etc/sudoers << EOF
jenkins  ALL=(ALL) NOPASSWD: /etc/init.d/apache2
EOF
exit

Для запуска тестов создадим скрип-обёртку, который будет устанавливать необходимые переменные окружения и транслировать через себя параметры командной строки Selenium-серверу:
cd /var/lib/jenkins/tools/SeleniumRC
touch selenium.sh
chmod +x selenium.sh
cat > selenium.sh << EOF
#!/bin/bash

export $(dbas-launch)
export NSS_USE_SHARED_DB=ENABLE
export DISPLAY=:0

java -jar /var/lib/jenkins/tools/SeleniumRC/selenium-server-standalone-2.18.0.jar $@
EOF

Настроим Apache. Отредактируем файл /etc/apache/conf.d/mod_mono и укажем расположение приложения:
<IfModule !mod_mono.c>
    LoadModule mono_module /usr/lib/apache2/mod_mono.so
</IfModule>

MonoAutoApplication disabled
AddHandler mono .aspx .ascx .asax .ashx .config .cs .asmx .axd
MonoApplications "/:/home/vm/public_html"

AddType application/x-asp-net .aspx
AddType application/x-asp-net .asmx
AddType application/x-asp-net .ashx
AddType application/x-asp-net .asax
AddType application/x-asp-net .ascx
AddType application/x-asp-net .soap
AddType application/x-asp-net .rem
AddType application/x-asp-net .axd
AddType application/x-asp-net .cs
AddType application/x-asp-net .vb
AddType application/x-asp-net .master
AddType application/x-asp-net .sitemap
AddType application/x-asp-net .resources
AddType application/x-asp-net .skin
AddType application/x-asp-net .browser
AddType application/x-asp-net .webinfo
AddType application/x-asp-net .resx
AddType application/x-asp-net .licx
AddType application/x-asp-net .csproj
AddType application/x-asp-net .vbproj
AddType application/x-asp-net .config
AddType application/x-asp-net .Config
AddType application/x-asp-net .dll
DirectoryIndex index.aspx
DirectoryIndex Default.aspx
DirectoryIndex default.aspx

И создадим конфигурационный файл приложения /etc/apache2/conf.d/Example:
Alias / "home/vm/public_html"
MonoServerPath Example "/usr/bin/mod-mono-server2"
MonoSetEnv Example MONO_IOMAP=all
MonoApplications Example "/:/home/vm/public_html"
<Location "/">
    Allow from all
    Order allow,deny
    MonoSetServerAlias Example
    SetHandler mono
    SetOutputFilter DEFLATE
    SetEnvIfNoCase Request_URI "\.(?:gif|jpe?g|png)$" no-gzip dont-vary
</Location>
su
cat > /etc/sudoers << EOF
jenkins  ALL=(ALL) NOPASSWD: /etc/init.d/apache2
EOF
exit

Настроим плагин Selenium:

Пример отчета (Jenkins просто отображает отчет Selenium'a один к одному):На этом формирование NAnt-скрипта закончено.

10. Так же как и задача получения исходного кода из репозитория, так же и задача сканирования кода на предмет открытых задач вызывается непосредственно из Jenkins (минуя Nant-скрипт).
Настроим плагин Task Scanner:

Пример отчета:

Сборочный проект настроен и готов к запуску.

Тюнинг

Буквально в двух словах хотелось бы остановиться на некоторых других плагинах (коих есть огромное количество).

Backup — назначение плагина очевидно из его названия, а его настройка тривиальна. Его описание излишне, т.к. Backup — это самое первое о чем стоит побеспокоиться!

JobConfigHistory — бывает в процессе настройки внесенные изменение в конфигурацию проекта оказываются неудачными и для того, что бы легко вернуться к предыдущей версии необходимо самостоятельно принимать мероприятия по сохранению предыдущей версии. Данные плагин ведет историю изменений и позволяет без труда определить внесенные изменения.

Green Balls — по умолчанию для отображения статуса Jenkins использует три цвета: красный, желтый и синий. Данный плагин позволяет заменить синий цвет на зеленый. Практическая ценность этого плагина весьма сомнительна, а вот эстетическую переоценить тяжело!

Next Build Number — позволяет устанавливать номер следующей сборки. Удобно в случаях, когда при настройке выполняется несколько сборок, которые затем удаляются, а в нумерации образуется дыра. Либо в тех случаях, когда нужно форсировано задать номер версии (например для релиза).

Sidebar-Link — очень любопытный плагин. Позволяет размещать ссылки на главной странице или на страницах проектов. Когда это может быть полезно. Например на главной странице можно разместить ссылку на какой-нибудь корпоративный ресурс, базу знаний или ещё что-нибудь (не забываем, что контент размещенный в директории userContent отображается Jenkinso'ом автоматически).Для создания ссылки на главной странице необходимо выполнить настройку сервера (а не проекта):

В результате на главной странице появиться ссылка Important:

На странице проекта можно разместить ссылку на ресурс специфический для данного проекта (например на svn-репозиторий), или, что может оказаться более полезным, на отчет какой-нибудь утилиты, для которой нет соответствующего Jenkins-плагина. Для создания ссылки на странице проекта, необходимо выполнить его настройку:

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

Вот так в заключении будет выглядеть страница проекта:

А вот так страница отчета по билду:



Пространные рассуждения (вместо нормального заключения)

Конечно же приведенный пример идеализирован.
Во-первых: представленный пример не содержит БД, что крайне редко встречается в жизни и лишает сборочный проект занимательной задачи по поддержанию БД в корректном состоянии (то ли всегда ее собирать из скриптов лежащих под контролем, то ли держать под контролем непосредственно бинарный файл, то ли накатывать на бинарный файл подконтрольные скрипты).

Во-вторых: выполнять сборку на CI Servere очень часто может быть не достаточно, в большинстве случаев целесообразно создавать Matrix-проект поэтому операция развертывания приложения может оказаться несколько сложнее.

В-третьих: Юнит-тесты наверное правильнее выполнять в тестовом окружении, а не на билд-сервере.

В примере не рассмотрен ни один из нотификационных плагинов. На практике же их использование может оказаться необходимо.

В конце-концов на CI Servere может не оказаться X-сервера механизм запуска функциональных тестов в таком случае несколько изменится (на при мер).

Чего хотелось бы еще.
Больше всего хочется Pre-Tested Commit.
Хотелось бы анализатор покрытия кода тестами.
Очень хочется сборщик метрик с более широкими параметрами. Количество строк кода, написанного на C# это конечно же круто, но хотелось бы видеть информацию о цикломатической сложности и степени связности, а возможно и о чем-нибудь еще. Кстати дефолтная IDE с задачей сбора метрик справляется значительно лучше представленной в приложении утилиты. На вкладке Metrics Monodevelop можно увидеть следующую информацию:


P.S.

Ну и в самом конце, хотелось бы пригласить всех тех, кто предпочитает узнавать об ошибках не через месяц после коммита от заказчика, а на следующий день от билд-сервера, поделиться своим опытом и высказать замечания к представленном примеру.
Tags:
Hubs:
+15
Comments3

Articles

Change theme settings