Построение «правильного» процесса разработки на платформе.NET
Содержание
Введение
Достаточно часто программисты игнорируют (а возможно и не знают) некоторые приемы, позволяющие значительно облегчить весь процесс разработки.
Предлагаю вам взглянуть на «альтернативный», но не единственный правильный процесс разработки в команде, позволяющий значительно сократить усилия, чтобы еще осталось время попить пивка с коллегами.
Рассматриваемый в статье пример может показаться весьма наивным, и простым. Пусть это не вводит вас в заблуждение, я пытался сделать все достаточно просто, чтобы охватить как можно большую аудиторию.
Прежде чем начать предлагаю вам ответить на ряд вопросов, которые будут раскрыты в конце статьи:
- сколько действий вам нужно сделать, чтобы собрать проект, включая установку Windows Services, дейплоймент веб сайта, запуск тестов (вы же пишете тесты?)?
- что вам нужно сделать, чтобы настроить всю инфраструктуру на машине нового члена команды?
- как часто у вас собирается проект на Тест-сервере?
- насколько отличается процесс деплоймента на машине девелопера, qa сервере и production сервере?
- сколько человек в команде сумеют развернуть проект на production сервере?
Итак, приступим...
Windows Server 2008
При написании статьи использовалась именно эта операционная система и этому есть ряд причин:
- прежде всего это не Windows Vista
- в Windows Server 2008 может быть установлен IIS 7, который очень легко конфигурируется из командной строки
- эта операционная система стоит у автора статьи (что является основной причиной:)).
Т. к. мы собираемся писать скрипты для IIS 7, хотелось бы сделать ряд замечаний по поводу других операционных систем.
Windows Vista — никаких трудностей, скорее всего, не будет, весь код из данной статьи без проблем будет работать.
Windows Server 2003 — могут возникнут затруднения при переносимости скриптов, т. к. в Server 2003 установлен IIS 6. Но все примеры можно применить, приложив некоторые усилия и бубен.
Windows XP SP2/3 — возникнут проблемы с переносимостью скриптов для настройки IIS т. к. в XP возможно установить только IIS 5.1 (Фанаты Windows Script Host могут справяться и с этим заданием).
Более ранние версии Windows — к этой аудитории у меня есть резонный вопрос — вы в каком веке живете?
Итак, в Windows Server 2008 должен быть установлен IIS 7. Чтобы съэкономить место, предлагаю вам зайти на этот ресурс, там подробно описано как это сделать (это первая ссылка из гугла на запрос «как установить IIS 7»).
Visual Studio 2008
Пример в статье будет разработан именно в этой среде, т. к. Visual Studio — это основное средство любого. NET разработчика.
На протяжении всей статьи будет рассматриваться процесс разработки веб приложения со слоем бизнес логики, unit тестами и windows сервисом.
В качестве фрэймворка для тестов будет использоваться Nunit, поэтому прежде чем приступить к разработке какркаса приложения идем по этой ссылке и качаем Nunit (у меня установлена версия 2.4.7, но вполне возможно установить и более свежую версию).
Запускаем Visual Studio 2008
File→New→Project
Выбираем Other Projetc Types → Blank Solution
Name: TestApp, Location = «C: \», папка «C: \TestApp» будет создана автоматически.
Рис 1. Создание пустого проекта.
Добавляем новый веб сайт
Рис 2. Новый Веб сайт, шаг 1
Меняем Language на «C#» и Location на «C: \TestApp\WebApp»
Рис 3. Новый веб сайт, шаг 2
Создаем проект «BusinessObjects», содержащий набор бизнес объектов
Рис 4. Новый проект — BusinessObjects, шаг 1.
Тип проекта — «Class Library», имя — «BusinessObjects».
Рис 5. Новый проект — BusinessObjects, шаг 2.
После создания проекта удаляем автоматически созданный класс с именем «Class1» за ненадобностью.
Создаем проект для тестов.
Рис 6. Новый проект — Tests.
Удаляем «Class1»...
Далее, создаем проект для Windows сервиса, который ничего не будет делать, но необходим для полноты примера деплоймента проекта.
Рис 7. Новый проект TestService.
После всех манипуляций должен получиться каркас проекта аналогичный представленному на Рис 8:
Рис 8. Каркас проекта.
На данном этапе было бы правильным создать репозиторий и залить туда проект, но мы это отложим, т. к. проект по образу и подобию «Hello World» и не хотелось бы разрывать логически связанные части статьи.
Итак, следуя принципам TDD, начнем создание логики с написания тестов. Задача предельно проста — необходимо создать класс, содержащий один единственный метод, принимающий string, и возвращающий «Hello » + переданный параметр, если передать null должен быть выкинут ArgumentNullException.
В тестовой сборке добавляем референс на Nunit.framework
Рис 9. Добавление ссылки на сборку nunit.framework, шаг 1.
Рис 10. Добавление ссылки на сборку nunit.framework, шаг 2.
Создаем класс с именем «TestClass», код с пояснениями представлен ниже:
using System; using NUnit.Framework; namespace Tests { [TestFixture] //все тестовые классы должны быть с аттрибутом TestFixture public class TestClass //обязательно указывать public, иначе класс не будет запущен { [Test] //каждый тест должен быть с аттрибутом Test public void GetHelloTest() { Entity entity = new Entity(); //Провряем на идентичность полученных результатов с ожидаемыми Assert.AreEqual("Hello Eugene", entity.GetHello("Eugene")); } [Test] [ExpectedException(typeof(ArgumentNullException))] //ожидаем ArgumentNullException, если Exception-a не будет, тест не пройдет public void GetHelloExceptionTest() { Entity entity = new Entity(); entity.GetHello(null); } } }
Если сейчас попробовать собрать проект, ничего не выйдет, т. к. не существует класса с именем Entity. Создадим его.
Добавляем класс к сборке BusinessObjects, называем его Entity:
Рис 11. Новый класс в сборке BusinessObjects.
Добавляем референс в проекте Tests на проект BusinessObjects для того, чтобы тесты имели возможность использовать только что созданный класс:
Рис 12. Добавление ссылки на сборку BusinessObjects, шаг 1
Рис 13. Добавление ссылки на сборку BusinessObjects, шаг 2
Добавляем using
в тестовый класс:
using BusinessObjects;
После этих шагов пробуем собрать проект, и опять ничего не получится, т. к. мы не добавили метод с именем GetHello. Добавим его. Весь код класса Entity приведен ниже.
using System;
namespace BusinessObjects
{
public class Entity
{
public string GetHello(string name)
{
if(name == null)
{
throw new ArgumentNullException();
}
return "Hello " + name;
}
}
}
После всех проделанных шагов проект успешно собирается.
Пришло время проверить, правильно ли проходят тесты (хотя это нужно было делать немного раньше — до добавления логики к методу GetHello, и только после этого имплементировать логику в методе, но об этом я предлагаю почитать в литературе посвященной TDD и DDD).
Я предпочитаю использовать Resharper для запуска тестов, т. к. этот плагин очень юзабильный и позволяет запускать тесты из студии (а также содержит большое количество фичей для рефакторинка и поиска), единственная проблема — платность продукта (триал версию можно скачать тут). Также существует бесплатный плагин для студии — Test Driven. NET который нужнен только для тестов (можно скачать тут).
В конкретном случая, я предлагаю воспользоваться отдельным приложением Nunit GUI, т. к. данное приложение устанавливается вместе с nunit.framework.
Идем в Пуск -> All Programs -> NUnit 2.4.7 запускаем NUnit GUI
Жмем File -> Open Project -> «C: \TestApp\Tests\bin\Debug\Tests.dll»
Получаем приблизительно следующее:
Рис 14. Nunit GUI, запуск тестов
Жмем «Run», должны получить такую же картинку как на рис 15. Если видите красный цвет, тогда где-то ошибка и нужно вернуться на пару шагов назад и проверить правильность кода.
Рис 15. Nunit GUI, «послезапуск» тестов
Итак, логика написана, тесты пройдены, пришло время написать пару строк кода на веб странице.
Добавляем ссылку в веб сайте на сборку BusinessObjects.
Рис 16. Добавление ссылки на сборку BusinessObjects, шаг 1
Рис 17. Добавление ссылки на сборку BusinessObjects, шаг 2
Открываем файл «Default.aspx.cs» в веб сайте. Добавляем using
для сборки бизнес объектов, а также для сборки System.Web.Configuration
чтобы использовать класс WebConfigurationManager
(для чего это нужно будет описано в разделе посвященном Nant-у). Также добавляем несколько строк кода.
Весь код приведен ниже:
using System;
using System.Web.Configuration;
using BusinessObjects;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Entity entity = new Entity();
Response.Write(entity.GetHello("Eugene"));
Response.Write("<br/>");
Response.Write("Environment: " + WebConfigurationManager.AppSettings["env"]);
}
}
Код достаточно прозрачный, думаю объяснения излишние.
Открываем файл web.config, и изменяем блок appSettings
следующим образом:
<appSettings>
<add key="env" value="DEV"/>
</appSettings>
После чего можно нажать F5 и посмотреть, что получится в браузере
SVN
Каркас проекта создан, пора его залить в репозиторий. Существует несколько наиболее популярных видов репозитория, к которым относятся CVS, SVN, VSS и др. В конкретном случае будет использоваться SVN, т. к. его невероятно просто настроить, а также он тесно интергирован с Cruise Control. NET, но обо всем по порядку.
Итак, идем по этой ссылке качаем «Visual SVN Server» (на момент написания статьи актуальной была версия 1.7.2). После чего запускаем только что скачанный инсталлер:
После принятия лицензионного соглашения, есть возможность настроить пути установки сервера и репозитория (Рис 18). Для простоты оставим все как есть.
Рис 18. Установка Visual SVN Server.
После установки SVN сервера нужно сделать 2 очень важных шага.
1. Добавить путь к папке с бинарными файлами SVN Server (в моем случае это «C: \Program Files\VisualSVN Server\bin») в PATH (Рис 19).
Рис 19. Добавление пути к Visual SVN Server bin к PATH
2. Добавить новый элемент в User Variables с именем «SVN_EDITOR» и значением «notepad» (можете выбрать любой редактор):
Рис 20. Добавление нового элемента в User Variables
После этого идем в Start -> All Programs -> Visual SVN -> VisualSVN Server Manager и создаем нового юзера с именем «Eugene» и паролем «1» (Рис 21, 22):
Рис 21. Создание нового пользователя, шаг 1
Рис 22. Создание нового пользователя, шаг 2
Далее создаем новый репозиторий (Рис 23):
Рис 23. Создание нового репозитория, шаг 1
Имя репозитория будет «TestApp». Также предлагается создать стандартную структуру папок (trunk, branches, tags), данная структура подразумевает разработу с версионированием, т. е. вся разработка ведется из папки trunk, когда выходит новая версия продукта, она перекладывается в папку branches, а в папке trunk продолжается разработка над следующей версией продукта. Таким образом папка trunk будет содержать актуальную и еще не выпущенную версию продукта, а папка branches будет содержать папки v1, v1.1, v2, и т. е. версии, которые уже в релизе.
Для нашего примера никакой структуры в репозитории не нужно, т. к. мы собираемся выпустить только одну версию продукта.
Рис 24. Создание репозитория, шаг 2
Итак репозиторий создан, пора залить в него проект. Для того, чтобы проводить манипуляции с svn существует несколько достаточно удобных клиент-интерфейсов, к которым относятся TortoiseSVN, VisualSVN и т. д. Но мы будем пользоваться командной строкой, чтобы наиболее четко ощутить все прелести примера.
Хотелось бы еще пару слов сказать о менеджерах командной строки (или файловых менеджерах). Существует много вариантов — NC, VC, Total Commander, и т. д, но как сказал один очень сильный программист (Ден привет:)): «Все нормальные поцаны используют Far». Поэтому в этом примере будем использовать именно этот менеджер.
Заходим в VisualSVN Server Manager и копируем url к репозиторию (Рис 25):
Рис 25. Копирование url к репозиторию в clipboard
После чего открываем Far, переходим в корень диска C:, создаем папку «Projects», переходим в «C: \Projects» и выполняем команду «svn co» + вставляем только что скопированный url. Получится приблизительно то же что и на Рис 26:
Рис 26. Checkout из репозитория
Жмем «Enter», будет предложено отклонить®, принять временно (t) или принять (p) сертификат сервера. Принимаем сертификат (p). Важно выбрать именно (p), почему — будет описано в разделе посвященному CruiseControl. NET. Также возможно потребуется ввести User Name и Password для юзера репозитория — вводим их для юзера, который был создан в VisualSVN Server Manager.
После проделанных шагов в консоль будет выведено сообщение «Checked out revision 0.» и будет создана папка «TestApp».
Если зайти в только что созданную папку «C: \Projects\TestApp» можно обнаружить скрытую папку с именем «.svn» — эту папку нельзя ни в коем случае удалять, т. к. именно в ней хранятся все локальные изменения, и системные файлы SVN.
Рис 27. Папка TestApp после svn checkout.
Наконец то мы дошли до залития всего проекта в репозиторий. Переходим в C: \TestApp, удаляем все ненужные файлы и папки, такие как _ReSharper. TestApp, TestApp.4.5.resharper.user, bin, obj. Перемещаем все содержимое из C: \TestApp в C: \Projects\TestApp.
Переходим в C: \Projects\TestApp и выполняем команду
«svn add TestService Tests BusinessObjects TestApp.sln WebApp». Результат должен быть приблизительно таким же как показано на рис. 28:
Рис 28. Добавление проекта в репозиторий.
Далее, нужно залить все в репозиторий. Выполняем команду «svn ci». Будет открыт блокнот с перечнем файлов для коммита (именно для этого нужно было добавлять новую переменную SVN_EDITOR) в переменные среды. Сохраняем и закрываем файл (если не сохранить и попробовать закрыть, вам будет предложено решить — нужно ли делать коммит или нет).
С этого момента вы можете использовать репозиторий с проектом. В данной статье не будет рассматриваться как осуществляется работа с репозиторием svn. Чтобы получить более подробную информацию, советую обратиться к специализированным источникам.
Nant
Следующим шагом в построении процесса разработки должна стать разработка автоматизированного процесса деплоймента и запуска тестов. Для этих целей, как правило, используются 2 подхода.
Первый подход — создание *.bat файлов с кодом для развертывания проекта. У данного подхода существует ряд ограничений, основным из которых является, неудобство использования.
Второй подход — использование специализированных средств, к которым относятся Nant (для. NET) или Ant, Continuum (для Java) и др.
В данной статье будет использоваться open source консольное приложение Nant. Загрузить это тул можно здесь — nant.sourceforge.net. Советую загружать не ниже версии 0.86-beta1, т. к. только она на момент написания статьи поддерживает тип проекта Visual Studio 2008.
После загрузки, распаковываем архив, и помещаем его, скажем, в папку «C: \Program Files\nant-0.86».
Добавляем путь к «bin» в PATH (Рис 29):
Рис 29. Добавление пути к бинарным файлам Nant в PATH
Открываем Far, пишем в консоли «nant» и жмем «Enter». Если видим вывод, показанный на рис. 30, значит все впорядке — путь к nant работает правильно (если фар был открыт до добавления значения в PATH, его нужно закрыть и открыть заново).
Рис 30. Проверка пути к nant
Далее предлагается краткий обзор основ конфигурирования используя nant.
Для того чтобы конфигурировать билд, необходимо иметь
Чтобы не отходить далеко в теорию, предлагаю рассмотреть короткий пример, после которого мы вернемся к основному примеру — приложению TestApp.
Итак, создаем файл с именем default.build (В какой папке создать файл абсолютно без разницы). Добавляем в него следующее содержимое:
<?xml version="1.0"?>
<project name="TestApp">
<target name="test">
<echo message="Test message" />
</target>
</project>
После чего в консоли пишем «nant test
». Вывод должен быть следующим:
test:
[echo] Test message
BUILD SUCCEEDED
Мы только что создали target — основной элемент, на основании которого построена вся работа в nant, после чего передали имя target-а в качестве параметра приложению.
Немного усложним пример. Изменим файл default.build как показано ниже.
<?xml version="1.0"?>
<project name="TestApp">
<property name="app.author" value="Eugene" />
<target name="test">
<echo message="${app.author}" />
</target>
</project>
После чего опять запустим «nant test
». Вывод будет следующим:
test:
[echo] Eugene
BUILD SUCCEEDED
В *.build файлах могут содержаться как таргеты (targets), так и проперти (property). Как показано в примере для того, чтобы использовать property, необходимо поместить его имя в фигурные скобки после знака $.
Еще усложним пример.
<?xml version="1.0"?>
<project name="TestApp">
<property name="app.author" value="Eugene" />
<property name="app.path" value="${directory::get-current-directory()}" />
<target name="test">
<echo message="${app.author}" />
<echo message="${app.path}" />
</target>
</project>
Вывод будет зависеть от папки, в которой находится файл default.build, но он должен быть приблизительно следующим:
test: [echo] Eugene [echo] C:\Projects\TestApp BUILD SUCCEEDED
Как вы видите, существует возможность использования как констант, так и встроенных функций. Весь перечень функций можно найти здесь: nant.sourceforge.net/...help/functions/index.html
Итак, пора бы написать что-нибудь полезное, поэтому закончим с основами, и перейдем непосредственно к автоматизации проекта TestApp. Для тех кто хочет подробно изучить возможности nant настоятельно рекомендую прочитать книгу «Expert. NET Delivery Using NAnt and CruiseControl. NET».
Прежде чем приступать непосредственно к процессу написания скриптов для автоматизации процесса деплоймента, необходимо продумать стратегию (последовательность шагов) сборки.
Предлагаемая стратегия сборки:
- Подменить *.config файл в веб приложении
- Собрать проект
- Запустить тесты
- Создать *.bat файл для деплоймента веб приложения в IIS, а также установки сервиса.
- Если environment==’QA’, будем архивировать бинарники сервиса и копировать их в папку «C:\build\» (это может показаться бессмысленным, но для примера сойдет)
Итак, поехали...
Создаем файл default.build в папке «C: \Projects\TestApp».
Копируем файл C: \Projects\TestApp\WebApp\web.config в C: \Projects\TestApp\WebApp\web.config.standard. В *.standard файле подменяем значение раздела appSettings:
<appSettings> <add key="env" value="[[[config.environment]]]"/> </appSettings>
После чего модифицируем файл default.build как показано ниже (самые нетерпеливые читатели могут сразу перейти в конец раздела, где представлен полный код файла default.build):
<?xml version="1.0"?>
<project name="TestApp">
<property name="solution.dir" value="${directory::get-current-directory()}" />
<property name="config.file.path" value="${solution.dir}\WebApp\web.config" />
<property name="config.environment" value="QA" />
<target name="build.recreateConfig">
<echo message="build.recreateConfig starded at: ${datetime::now()}" />
<copy verbose="true"
file="${config.file.path}.standard"
tofile="${config.file.path}"
overwrite="true">
<filterchain>
<replacestring from="[[[config.environment]]]" to="${config.environment}" />
</filterchain>
</copy>
<echo message="build.recreateConfig completed at: ${datetime::now()}" />
</target>
</project>
В таргете «build.recreateConfig» выводится на экран дата и время запуска таргета, после чего копируется файл «web.config.standard» в файл «web.config» с заменой значения [[[config.environment]]] на значение «QA».
Идем дальше, следующим шагом необходимо создать таргет для сборки проекта. Сделаем это...
Добавляем проперти:
<property name="solution.file.name" value="TestApp.sln" />
<property name="build.framework.dir"
value="C:\WINDOWS\Microsoft.NET\Framework\v3.5\" />
<property name="build.configuration" value="Release" />
<property name="build.platform" value="Any CPU" />
<property name="build.msbuildTargetList" value="Clean;Rebuild" />
<property name="build.verbosity" value="quiet" />
Создаем таргет для билда:
<target name="build">
<echo message="build starded at: ${datetime::now()}" />
<exec program="msbuild.exe"
basedir="${build.framework.dir}"
workingdir="${solution.dir}"
resultproperty="build.result" failonerror="true">
<arg value="${solution.file.name}" />
<arg value="/property:Configuration=${build.configuration};Platform=${build.platform}" />
<arg value="/target:${build.msbuildTargetList}" />
<arg value="/verbosity:${build.verbosity}" />
</exec>
<echo message="build completed at: ${datetime::now()}" />
</target>
После этого следует сохранить файл default.build, перейти в консоль, и выполнить команду «nant build». Если вы увидите вывод как показано далее, значит все в порядке, проект успешно собрался.
build:
[echo] build starded at: 06/06/2009 23:27:52
[exec] Microsoft (R) Build Engine Version 3.5.30729.1
[exec] [Microsoft .NET Framework, Version 2.0.50727.3053]
[exec] Copyright (C) Microsoft Corporation 2007. All rights reserved.
[exec]
[echo] build completed at: 06/06/2009 23:27:56
BUILD SUCCEEDED
Если в выводе появится «BUILD FAILED» значит нужно вернуться на несколько шагов назад и перепроверить правильность кода.
Следующий пункт — запуск тестов. Создаем папку «C: \Projects\TestApp\Tests\Results» для файла c результатами тестирования.
Добавляем проперти в файлу default.build:
<property name="tests.dir" value="${solution.dir}\Tests" />
<property name="tests.results.dir" value="${solution.dir}\Tests\Results" />
<property name="tests.assembly.name" value="Tests.dll" />
Добавляем таргет для запуска тестов:
<target name="tests.build">
<echo message="tests.build starded at: ${datetime::now()}" />
<nunit2 haltonfailure="false" failonerror="true">
<formatter type="Xml"
usefile="true"
extension=".xml"
outputdir="${tests.results.dir}" />
<formatter type="Plain" usefile="false" />
<test assemblyname="${tests.dir}\bin\${build.configuration}\${tests.assembly.name}" />
</nunit2>
<echo message="tests.build completed at: ${datetime::now()}" />
</target>
Выполняем команду «nant tests.build
» в консоли. Вывод должен быть таким, как показано ниже:
tests.build:
[echo] tests.build starded at: 06/06/2009 23:46:16
[nunit2] Tests run: 2, Failures: 0, Not run: 0, Time: 0.114 seconds
[nunit2]
[nunit2]
[nunit2]
[echo] tests.build completed at: 06/06/2009 23:46:18
BUILD SUCCEEDED
Мы закончили первые 3 пункта, переходим к самому интересному — созданию скрипта для развертывания веб сайта и установки сервиса. Чтобы немного усложнить задачу допустим для сайта должно быть настроено несколько биндингов (Bindings):
Создаем папку «C: \Projects\TestApp\Scripts», в которой создаем файл «deployWebApp.bat.standard». Этот файл будет копироваться в файл «C: \Projects\TestApp\Scripts\deployWebApp.bat», причем будут подменяться некоторые параметры.
Код файла «C: \Projects\TestApp\Scripts\deployWebApp.bat.standard»:
REM create standard app pool for the application
%windir%\system32\inetsrv\AppCmd DELETE APPPOOL /apppool.name:"[[[webapp.appPoolName]]]"
%windir%\system32\inetsrv\AppCmd ADD APPPOOL /apppool.name:"[[[webapp.appPoolName]]]"
REM create web site
%windir%\system32\inetsrv\AppCmd delete site "[[[webapp.siteName]]]"
%windir%\system32\inetsrv\AppCmd add site /site.name:"[[[webapp.siteName]]]" /bindings:http/*:80:[[[webapp.appPoolName]]]
REM create web app in the root of the web site
%windir%\system32\inetsrv\AppCmd ADD APP /site.name:"[[[webapp.siteName]]]" /path:/ /applicationPool:[[[webapp.appPoolName]]] /physicalPath:"[[[webapp.physicalPath]]]"
REM add bindings to the site
%windir%\system32\inetsrv\AppCmd set site /site.name:[[[webapp.siteName]]] /+bindings.[protocol='http',bindingInformation='*:80:admin.[[[webapp.siteName]]]']
В этой статье написано достаточно подробно как в IIS 7 создавать сайты, приложения, пулы и т. д. используя AppCmd, поэтому здесь я не буду тратить место для объяснения что делает каждая команда в этом файле.
Далее добавляем новые проперти в файл default.build:
<property name="webapp.siteName" value="testapp.ua" />
<property name="webapp.appPoolName" value="testapp.ua" />
<property name="webapp.physicalPath" value="${solution.dir}\WebApp" />
<property name="webapp.deploy.file" value="${solution.dir}\Scripts\deployWebApp.bat" />
А также новый таргет для создания *.bat файла:
<target name="deploy.createIisBat">
<copy verbose="true"
file="${webapp.deploy.file}.standard"
tofile="${webapp.deploy.file}"
overwrite="true">
<filterchain>
<replacestring from="[[[webapp.siteName]]]" to="${webapp.siteName}" />
<replacestring from="[[[webapp.appPoolName]]]" to="${webapp.appPoolName}" />
<replacestring from="[[[webapp.physicalPath]]]" to="${webapp.physicalPath}" />
</filterchain>
</copy>
</target>
После чего можно попробовать выполнить команду «nant deploy.createIisBat», и запустить файл «Scripts\deployWebApp.bat». Если у вас выключен UAC, тогда вам необходимо запустить файл от имени администратора (Рис 31).
Рис 31. Запуск *.bat файла от имени администратора.
После запуска файла запускаем «inetmgr», и видим приблизительно следующее:
Рис 32. IIS Manager после запуска скрипта.
На данном этапе необходимо сделать один «кастомный» шаг — внести изменения в файл hosts, для того, чтобы можно было запускать сайт.
Открываем notepad от имени администратора, далее File-> Open-> «C: \Windows\System32\drivers\etc\hosts» и добавляем строку «127.0.0.1 testapp.ua», как показано на рис. 33:
Рис 33. Правка файла hosts.
Пришло время протестировать веб приложение. Открываем браузер и вводим в строку url: «testapp.ua/
Рис 34. Тест веб сайта.
Как видим, все работает.
Следующим шагом будет написание скрипта для установки Windows Service
Создаем файл «C: \Projects\TestApp\Scripts\deployService.bat.standard» с следующим содержимым:
sc delete [[[service.name]]]
sc create [[[service.name]]] binPath= "[[[service.path]]] -d runservice " start= demand DisplayName= "[[[service.name]]] Rocks"
Добавляем проперти в файл default.build:
<property name="service.deploy.file" value="${solution.dir}\Scripts\deployService.bat" />
<property name="service.name" value="TestAppService" />
<property name="service.path" value="${solution.dir}\TestService\bin\${build.configuration}\TestService.exe" />
А также добавляем таргет:
<target name="deploy.createServiceBat">
<copy verbose="true"
file="${service.deploy.file}.standard"
tofile="${service.deploy.file}"
overwrite="true">
<filterchain>
<replacestring from="[[[service.name]]]" to="${service.name}" />
<replacestring from="[[[service.path]]]" to="${service.path}" />
</filterchain>
</copy>
</target>
Выполняем команду «nant deploy.createServiceBat
» (проект должен быть собран, чтобы была возможность проинсталировать TestService.exe).
Запускаем «Scripts\deployService.bat» от имени администратора.
Далее открываем «services.msc» и ищем добавленный сервис:
Рис 35. Установленный сервис
Последним шагом является «странное» требование — архивирование бинарников TestService и копирование архива в папку «C: \builds\»
Добавляем проперти в файл default.build:
<property name="service.zip.folder" value="C:\builds" />
Добавляем новый таргет:
<target name="deploy.zipService">
<if test="${config.environment=='QA'}">
<if test="${not directory::exists(service.zip.folder)}">
<echo message="${service.zip.folder} doesn't exist, creating the folder..." />
<mkdir dir="${service.zip.folder}"/>
</if>
<zip zipfile="${service.zip.folder}\build_arc.zip">
<fileset basedir="${solution.dir}\TestService\bin\${build.configuration}">
<include name="**/*" />
</fileset>
</zip>
</if>
</target>
Выполняем команду «nant deploy.zipService
» и если проперти с именем «config.environment» было установлено в «QA», тогда можно открыть папку «C: \builds» и убедиться, что *.zip файл создается:
Рис 36. Созданный *.zip файл
Остался последний шаг — создать таргет, который будет собирать в себе все перечисленные ранее. Код таргета представлен ниже:
<target name="deploy">
<call target="build.recreateConfig" />
<call target="build" />
<call target="tests.build" />
<call target="deploy.createIisBat" />
<exec program="${solution.dir}\Scripts\deployWebApp.bat" />
<call target="deploy.createServiceBat" />
<exec program="${solution.dir}\Scripts\deployService.bat" />
<call target="deploy.zipService" />
</target>
Тут нужно сделать одно замечание: если на машине выключен UAC, тогда проект не будет собираться, необходимо будет закоментировать вызовы «*.bat» файлов, и запускать их вручную. Если UAC выключен — тогда все что нужно сделать это выполнить команду «nant deploy
».
Весь код файла default.build представлен ниже:
<?xml version="1.0"?>
<project name="TestApp">
<property name="solution.dir" value="${directory::get-current-directory()}" />
<property name="solution.file.name" value="TestApp.sln" />
<property name="config.file.path" value="${solution.dir}\WebApp\web.config" />
<property name="config.environment" value="QA" />
<property name="build.framework.dir"
value="C:\WINDOWS\Microsoft.NET\Framework\v3.5\" />
<property name="build.configuration" value="Release" />
<property name="build.platform" value="Any CPU" />
<property name="build.msbuildTargetList" value="Clean;Rebuild" />
<property name="build.verbosity" value="quiet" />
<property name="tests.dir" value="${solution.dir}\Tests" />
<property name="tests.results.dir" value="${solution.dir}\Tests\Results" />
<property name="tests.assembly.name" value="Tests.dll" />
<property name="webapp.siteName" value="testapp.ua" />
<property name="webapp.appPoolName" value="testapp.ua" />
<property name="webapp.physicalPath" value="${solution.dir}\WebApp" />
<property name="webapp.deploy.file" value="${solution.dir}\Scripts\deployWebApp.bat" />
<property name="service.deploy.file" value="${solution.dir}\Scripts\deployService.bat" />
<property name="service.name" value="TestAppService" />
<property name="service.path" value="${solution.dir}\TestService\bin\${build.configuration}\TestService.exe" />
<property name="service.zip.folder" value="C:\builds" />
<target name="build.recreateConfig">
<echo message="build.recreateConfig starded at: ${datetime::now()}" />
<copy verbose="true"
file="${config.file.path}.standard"
tofile="${config.file.path}"
overwrite="true">
<filterchain>
<replacestring from="[[[config.environment]]]" to="${config.environment}" />
</filterchain>
</copy>
<echo message="build.recreateConfig completed at: ${datetime::now()}" />
</target>
<target name="build">
<echo message="build starded at: ${datetime::now()}" />
<exec program="msbuild.exe"
basedir="${build.framework.dir}"
workingdir="${solution.dir}"
resultproperty="build.result" failonerror="true">
<arg value="${solution.file.name}" />
<arg value="/property:Configuration=${build.configuration};Platform=${build.platform}" />
<arg value="/target:${build.msbuildTargetList}" />
<arg value="/verbosity:${build.verbosity}" />
</exec>
<echo message="build completed at: ${datetime::now()}" />
</target>
<target name="tests.build">
<echo message="tests.build starded at: ${datetime::now()}" />
<nunit2 haltonfailure="false" failonerror="true">
<formatter type="Xml"
usefile="true"
extension=".xml"
outputdir="${tests.results.dir}" />
<formatter type="Plain" usefile="false" />
<test assemblyname="${tests.dir}\bin\${build.configuration}\${tests.assembly.name}" />
</nunit2>
<echo message="tests.build completed at: ${datetime::now()}" />
</target>
<target name="deploy.createIisBat">
<copy verbose="true"
file="${webapp.deploy.file}.standard"
tofile="${webapp.deploy.file}"
overwrite="true">
<filterchain>
<replacestring from="[[[webapp.siteName]]]" to="${webapp.siteName}" />
<replacestring from="[[[webapp.appPoolName]]]" to="${webapp.appPoolName}" />
<replacestring from="[[[webapp.physicalPath]]]" to="${webapp.physicalPath}" />
</filterchain>
</copy>
</target>
<target name="deploy.createServiceBat">
<copy verbose="true"
file="${service.deploy.file}.standard"
tofile="${service.deploy.file}"
overwrite="true">
<filterchain>
<replacestring from="[[[service.name]]]" to="${service.name}" />
<replacestring from="[[[service.path]]]" to="${service.path}" />
</filterchain>
</copy>
</target>
<target name="deploy.zipService">
<if test="${config.environment=='QA'}">
<if test="${not directory::exists(service.zip.folder)}">
<echo message="${service.zip.folder} doesn't exist, creating the folder..." />
<mkdir dir="${service.zip.folder}"/>
</if>
<zip zipfile="${service.zip.folder}\build_arc.zip">
<fileset basedir="${solution.dir}\TestService\bin\${build.configuration}">
<include name="**/*" />
</fileset>
</zip>
</if>
</target>
<target name="deploy">
<call target="build.recreateConfig" />
<call target="build" />
<call target="tests.build" />
<call target="deploy.createIisBat" />
<exec program="${solution.dir}\Scripts\deployWebApp.bat" />
<call target="deploy.createServiceBat" />
<exec program="${solution.dir}\Scripts\deployService.bat" />
<call target="deploy.zipService" />
</target>
</project>
Cruise Control.NET
Заключительным шагом будет установка и настройка «Continuous Integration» сервера для автоматического запуска сборки проекта и запуска тестов.
Идем на сайт ccnet.thoughtworks.com и качаем последнюю версию Cruise Control. NET (на момент написания статьи актуальной была версия 1.4.4).
Запускаем установщик...
Рис 37. Установка CruiseControl.NET, шаг 1
Рис 38. Установка CruiseControl.NET, шаг 2
Рис 39. Установка CruiseControl.NET, шаг 3
Рис 40. Установка CruiseControl.NET, шаг 4
Рис 41. Установка CruiseControl.NET, шаг 5
Рис 42. Установка CruiseControl.NET, шаг 6
Необходимо сделать одно замечание. На шаге 4, была отмечена опция «Create virtual directory in IIS for Web Dashboard», но, несмотря на это, виртуальная папка в IIS 7 не будет создана (можете называть это багом). Будем надеяться, что в следующих версиях такой проблемы не будет.
Необходимо сделать несколько дополнительных шагов для того, чтобы можно было запускать «Dashboard» в браузере, если этого не нужно (например, если вы будете использовать CCTray), можете смело пропустить эту часть раздела.
Открываем «inetmgr» и добавляем приложение к «Default web site»
Рис 43. Добавление приложения для «Web Dashboard», шаг 1
Далее заполняем форму как показано ниже. Очень важно поменять значение «Application pool», т. к. по умолчанию оно устанавливается в «DefaultAppPool», если не поменять этот параметр на «Classic», приложение запускаться не будет.
Рис 44. Добавление приложения для «Web Dashboard», шаг 2
Web Dashboard установлен, теперь нужно проверить это. Открываем браузер, в строке url пишем «http://localhost/ccnet/». Если вы видите такую же картинку как на рис. 45, значит все установлено правильно, если нет — вернитесь на пару шагов назад и проверте правильность своих действий.
Рис 45. CruiseControl.NET Web Dashboard
Пришло время приступить к настройке ccnet на работу с тестовым приложением TestApp.
Модифицируем файл «C: \Program Files\CruiseControl. NET\server\ccnet.config» как показано ниже:
<!DOCTYPE cruisecontrol [
<!ENTITY PROJECT_NAME "TestApp_v1">
<!ENTITY ROOT_FOLDER "C:\Projects\TestApp">
<!ENTITY ARTIFACTS_FOLDER "C:\Projects\TestApp\Tests\Results">
<!ENTITY REPOSITORY_FOLDER "https://JonyW2008:8443/svn/TestApp">
<!ENTITY TEST_USERNAME "Eugene">
<!ENTITY TEST_PASSWORD "1">
<!ENTITY NANT_EXECUTABLE "nant.exe">
]>
<cruisecontrol>
<project>
<name>&PROJECT_NAME;</name>
<sourcecontrol type="svn">
<trunkUrl>&REPOSITORY_FOLDER;</trunkUrl>
<workingDirectory>&ROOT_FOLDER;</workingDirectory>
<username>&TEST_USERNAME;</username>
<password>&TEST_PASSWORD;</password>
<autoGetSource>true</autoGetSource>
</sourcecontrol>
<tasks>
<nant>
<executable>&NANT_EXECUTABLE;</executable>
<baseDirectory>&ROOT_FOLDER;</baseDirectory>
<buildFile>default.build</buildFile>
<targetList>
<target>build</target>
<target>tests.build</target>
</targetList>
<buildTimeoutSeconds>600</buildTimeoutSeconds>
</nant>
</tasks>
<publishers>
<merge>
<files>
<file>&ARTIFACTS_FOLDER;\*-results.xml</file>
</files>
</merge>
<xmllogger />
</publishers>
</project>
</cruisecontrol>
Несколько важных замечаний:
- Элемент «PROJECT_NAME» не должен содержать пробелов, иначе вы будете получать exception в IIS, когда попытаетесь посмотреть результаты билда
- не забудьте подменить значение елемента «REPOSITORY_FOLDER» — самый простой способ сделать это — открыть VisualSVN Server Manager и скопировать url к вашему репозиторию
Далее открываем «services.msc», находим сервис с именем «CruiseControl. NET Server», заходим в настройки сервиса.
Рис 46. Настройка сервиса CruiseControl.NET Server, шаг 1
По умолчанию сервис будет запускаться как «Local System account», необходимо поменять это поведение, сервис должен запускаться с правами пользователя, у которого есть сертификат для работы с SVN. Грубо говоря, необходимо, чтобы сервис логинился как юзер, который выполнял команду «svn co», т. к. именно после этой команды пользователь должен принять или отклонить сертификат, как говорилось в разделе посвященном SVN.
Меняем настройки аутентификации сервиса:
Рис 47. Настройка сервиса CruiseControl.NET Server, шаг 2
Сохраняем изменения и запускаем сервис.
Открываем браузер, переходим по следующей ссылке «http://localhost/ccnet/».
Рис 48. Cruise Control.NET Dashboard
С этого момента проект можно собирать нажатием
На самом деле, еще много чего можно сделать, например можно настроить автоматический билд после каждого коммита, вместо билда по нажатию кнопки. Можно настроить рассылку писем всем разработчикам с результатами билда (достаточно юзабильная вещь), в письме можно будет увидеть последние изменения и определить кто завалил билд.
Заключение
Существует методика, которая называется «12 шагов Джоэла Спольски». Это набор из
Приведенный в статье метод разработки не нужно считать панацеей, т. к. существует много способов все усложнить на ровном месте, как говорится: «Кадры решают все». Так или иначе, принимать информацию к сведению или игнорировать, решать вам.
Теперь давайте ответим на вопросы, которые были заданы в начале статьи:
— сколько действий вам нужно сделать, чтобы собрать проект, включая установку Windows Services, дейплоймент веб сайта, запуск тестов (вы же пишете тесты?)?
Если выключен UAC, тогда 1 — запустить правильный таргет
— что вам нужно сделать, чтобы настроить всю инфраструктуру на машине нового члена команды?
Сделать checkout из репозитория, запустить таргет, модифицировать файл hosts
— как часто у вас собирается проект на Тест-сервере?
Несколько раз в день (возможно настроить на сборку после каждого коммита)
— насколько отличается процесс деплоймента на машине девелопера, qa сервере и production сервере?
Абсолютно идентичен, все сводится к запуску правильного таргета
— сколько человек в команде сумеют развернуть проект на production сервере?
Кто угодно сумеет это сделать, т. к. для этого нужно всего лишь запустить таргет для деплоймента
60 коментарів
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.