Vous êtes sur la page 1sur 27

Continuous Integration for .

Net Development

Craig Berntson 3925 South 700 West #22 Murray, UT 84123 Voice: 801-699-8782 www.craigberntson.com Email: craig@craigberntson.com

Contents
Continuous Integration is a method where source code is continually built and tested, resulting in better quality applications in less time. While Visual Studio Team System gives you the tools you need to do continuous integration, the cost is prohibitive to most shops. This whitepaper discusses free tools that allow you to integrate continuous integration into your .Net development.

A Case for Continuous Integration


Software is increasingly becoming more complex, requiring more parts to make it all work. This complexity comes at a cost to the development team, in terms of time needed to test and integrate the different components, ensure the code meets good coding standards, create the install set, and more. How many of the following problems have you heard: The software doesnt work on the customers machine and the developer cant reproduce the error You dont know when the software will be ready to ship because integration is taking longer than expected. You are unable to recreate your database quickly during development or it is difficult to make changes Defects are discovered late in the development process, delaying release of the application Members of the development team dont know the current state of the software Quality of the software is low because coding or architectural standards werent followed or because code has been duplicated

Wouldnt it be great if you could reduce the possibilities of these and more issues? Wouldnt it be even better if you could automate the process that does that? You can reduce quality issues in an automated fashion. Its called Continuous Integration.

What is Continuous Integration?


Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily - leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible. Many teams find that this approach leads to significantly reduced integration problems and allows a team to develop cohesive software more rapidly. Martin Fowler (http://www.martinfowler.com/articles/continuousIntegration.html) In his book, Continuous Integration, Paul Duvall states, CI is the embodiment of tactics that gives us, as software developers, the ability to make changes in our code, knowing that if we break software, well receive immediate feedback...[It is] the centerpiece of software development, as it ensures the health of software through running a build with every change. Continuous Integration affects every member of the development team. It isnt just a practice adopted by the build manager. Simply put, Continuous Integration (CI) is a process that helps ensure quality software. Notice I didnt say guarantee because as we know, there is no such thing as bug-free software. A typical CI process works like this: 1. 2. 3. 4. 5. The developer builds the software on his local PC and then runs unit tests on it. After the code passes all the unit tests, the source is checked in to a source code repository. An automated system on the CI Server detects that changes have been made in the repository and gets a copy of the code to its local system. The CI Server builds the software and then runs unit tests. The results of the build and unit tests are automatically posted to a web site where all team members can see the current state of the software.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 2

The above steps are the minimum that you do under your CI system. Other things that can be done include: Produce developer documentation of the system. This can be in the form of help files, UML diagrams, and more. Run code metrics on the source that indicate code coverage, adherence to standards, amount of duplicated code, and more. Produce an install set by calling programs such as InstallShield Call external, sophisticated testing applications to do functional testing Burn a CD or DVD that contains the release bits of the application

This list is not exhaustive as CI is really all about the process and team notification. Just about any part of the development process that can be automated can be included in continuous integration. CI adds value to your process by: Reducing risks When you integrate several times a day, defects are detected and fixed sooner. If you wait a few days for a QA person to test your software, you may forget what you were doing when you wrote it, increasing the amount of time to fix the issue while also increasing the chances of introducing additional bugs. Reduce Repetitive Processes Continuous Integration ensures that processes are run the same way every time. This also increases the quality of the application as you know that manual steps are skipped. Generate Deployable Software Because the software is always tested, you know that you can build and deploy at anytime. If there are problems with a build, the team is notified immediately and the fix can be made right then. Enable Better Project Visibility By providing testing and metrics, you can make more effective decisions and see trends in the development process Establish Greater Product Confidence Because the software is always tested and kept in a deployable state, you know that there is a high probability that the user will not find bugs.

When you first begin to setup a CI system in your company, some team members will fight it. After all, it is a change to the way they are doing things and people do not like change. Some arguments against CI include: Increased overhead to maintain the CI system At the beginning, there will be some increased overhead to do this, but over a short period of time, the CI system will actually save considerable time. Boise Code Camp 2008 3

Continuous Integraton 2007-2008 Craig Berntson

Too much change Dont try to implement everything at once. When you first setup your CI system, you may start with doing daily builds. Technically, this isnt continuous integration. To do CI effectively, you need to concentrate on the continuous part, meaning that builds are made often. But by gradually adding pieces to the process, team members are more likely to adopt it. Additional hardware/software costs Our first CI system used free software running on six year old laptop. It worked fine to get us started. Developers should be performing these activities Yes, many of these activities can be done by the developers, but by automating parts of the process, the developers become more effective in writing solid code while the automated system can perform more tasks in a shorter time than a person.

Paul Duvall identifies seven practices that teams should follow when using CI: Commit code frequently Dont commit broken code Fix broken builds immediately Write automated developer tests All tests and inspections must pass Run private builds (each developer runs tests on their own code) Avoid getting broken code (dont check out broken code)

At a minimum, a CI system consists of five software components: source control, software build tools (ie compiler), unit testing tools, CI server software, and web-based reporting mechanisms. A dedicated PC should be setup as the build box or integration server. These parts and other optional components are discussed in the following sections.

Project Organization
One of the keys to a successful CI process is organizing your project directory structure so that the source, tools, assemblies, artifacts, and other files can be found. Each developer will need to organize their development the same way. The files will also be laid down the same way on the build box when checked out of source control. The following figure is a representation of how we setup our project.

The Visual Studio solution file (.sln) is placed in the Source folder. Each CSharp project (.csproj) file goes in its respective project folder. Lib is used to store third party assemblies. Assemblies and other files that will be distributed to end users are copied into the Install folder by the build process. I will discuss code metrics and documentation later in this document.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 4

Source Control
For years, youve heard the importance of using source control for your code. I wont say much about source control except that even in a one-man shop, source control should be at the top of your list of must have tools. To get there, you have many options. One very popular free source control system is Subversion. Dont forget the Tortoise client tools that integrate with Windows Explorer. My team at work uses Telelogic Synergy CM for older projects, but have moved new development to Subversion. We also have a team in Maryland that is using PerForce, so we may have to integrate that too. For my personal work, I use SourceGear Vault, which is free for one user. I will use Vault for examples in this session.

Tools discussed in this section


Tool Subversion Tortoise SourceGear Vault Download URL http://subversion.tigris.org/ http://tortoisesvn.tigris.org/ http://www.sourcegear.com/vault/downloads.html

The Build Box


All the work of automating the build happens on the build box. Some people call this the Continuous Integration (CI) Server. The build box is a PC dedicated just for doing the build. It doesnt have to be the latest hardware, just something that can get the job done. When I started working with CI, my first build box was a six year old Gateway laptop with 512M RAM and a 30 Gig hard drive running Windows XP Pro. It also ran IIS to serve as our build Continuous Integraton 2007-2008 Craig Berntson Boise Code Camp 2008 5

reporting server, but you can user a different server box for reporting if you want. We currently have plans to use a virtual partition on one of our development servers as a build box and expect to implement it next year. The first things to install on the build box are the client tools for your source code control software. As a best practice you should not use your source control server as the build box. This is because the source files will be checked out and copied to a work area on the build box. Should the source control server fail, you can always get the latest version from the build box. You will need to install your source control client software, the .NET Framework, and all third party controls, assemblies that you are using in your project, and anything else needed to produce a build beginning to end. Once all this is installed, you should use your source control client software to check out the source code to make sure you can access the source control server and database. While were on the subject of a build box, this is a good time to talk about what makes up a build. Under CI, a build is really more than a simple compile of the source code. A build is everything that needs to be done to get the application out the door from compiling and testing the code to creating the install bits. You wont always want to create install bits so you may only do parts of the build. Heres what we currently do: Incremental Build Compile code, run unit tests Daily Build Clean, compile code, run unit tests, generate code metrics, email results to the development team Weekly Build Clean, compile code, run unit tests, generate code metrics, produce developer documentation, email the results to the development team

The incremental build should run as quickly as possible, preferably in less than 10 minutes. Other builds become more complex and do things such as delete assemblies from the previous build, a process called Clean, to generating code metrics, documentation, and more sophisticated tests. As we are currently in the early stages of developing the foundation framework, there arent any more tasks to perform than what Ive listed. However, we anticipate in the future that well add automated functional testing, InstallShield integration, additional code metrics, and more.

Cruise Control
Key to getting CI working is a tool that can automate checkouts, handle the build, testing, statistics, reporting, and other needs. The primary tool used is CruiseControl.Net (CCNet), created by ThoughtWorks (Martin Fowlers company). While CCNet doesnt handle everything in CI, it does have the ability to call other tools to do the work. You will install CCNet on the build box. CCNet consists of four main components: CCNet.exe The CruiseControl.Net console application. Use this tool to get CCNet up and running and to trouble shoot issues. Results are displayed in a console window so its easy to see whats going on. CCService.exe The CCNet Windows service. This is the executable that you will normally run in production. It has the same functionality as CCNet.exe, but does not have any display capabilities. WebDashBoard A web-based tool that provides reporting of build status and allows particular projects to be built on-demand. We run IIS on our build box for the web server, but you can use another server if desired. CCTray A small application that installs in the system tray of the development PCs and allows the team to easily monitor the status of any build.

I had one problem when installing CCNet. The install program setup the WebDashBoard to use ASP.Net 1.1. I had to change the web site to use ASP.Net 2.0 in the IIS Admin tool. Continuous Integraton 2007-2008 Craig Berntson Boise Code Camp 2008 6

CruiseControl.Net supports over a dozen source control products out of the box. I had no problems getting it to work with Subversion and Telelogic Synergy CM, nor SourceGear Vault. My examples in this document all use Vault. CCNet does its magic through an XML configuration file named CCNet.config. Start your configuration with something simple. Just get CCNet running. A bare-bones config file should work for you:
<cruisecontrol> <project name="Math"> <webURL>http://localhost/ccnet/</webURL> <artifactDirectory>C:\CI\Artifacts\Math\Artifacts</artifactDirectory> </project> </cruisecontrol>

The project name is required. This uniquely identifies each project running under CCNet and is the name displayed on the web server build report. The location of the web server is specified by the webUrl tag. It does not have to be local, so you can put the build results on another server if you already have one running. The artifact directory is where CCNet output is placed. I found it convenient to store CCNet output separate from the actual source code. Complete documentation is installed with CCNet and is also available on the CCNet web site. Now test that you have things working. Save the CCNet.config file. From a command window, navigate to the C:\Program Files\CruiseControl.Net\Server folder, then run CCNet. If all is working, you wont get an error. In actual production, you will want to run the CCNet service. The advantage of running the console application is that you get feedback on errors. The next thing you want to do is hook up source control to CCNet. The specific tags you need are listed in the CCNet documentation and vary from one source control provider to another. However, for the supported providers, CCNet knows how to call the local client to check out the source.
<cruisecontrol> <project name="Math"> <webURL>http://localhost/ccnet/</webURL> <artifactDirectory>C:\CI\Artifacts\Math\Artifacts</artifactDirectory> <sourcecontrol type="vault" autoGetSource="true"> <executable>c:\program files\sourcegear\vault client\vault.exe</executable> <host>localhost</host> <username>Admin</username> <password>MyPass</password> <repository>Math</repository> <folder>$</folder> <workingDirectory>C:\ci\math</workingDirectory> </sourcecontrol> </project> </cruisecontrol>

The sourcecontrol tag specifies the provider, in this case Vault. Executable tells CCNet where the client executable is located. Host is the Vault host server. Repository and folder specify where the source is saved in Vault. WorkingDirectory is where the source should be placed when it is checked out. Note that the code is placed in its own folder to separate out each project. To test that source control checkout is working, again save the CCNet.config file and run the CCNet Console application. Launch your browser and navigate to http://localhost/CCNet. The CCNet Dashboard is displayed. Note that if you get an error, you probably didnt start the CCNet console application.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 7

Each project setup in CCNet will be displayed. Click the Force button next to the Math project to force the CI process to start. Switch over to the CCNet console window to see if there are any errors. If all goes well, you can navigate back to the Dashboard and click Refresh Status. Now click on the project name, in this case Math. A Project Report is displayed.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 8

Now click on either Latest Build or here link to display the results of the latest build. This report lists all the changes made to the source code since the last build. On the left is a series of links to get reports from additional tools that can be added to the CI process. You should be getting some idea now that the Dashboard is a powerful tool to get a quick idea of what happened not only in the latest build, but CCNet also keeps track of all previous builds. It does this by storing the XML output of each build in its own folder, then displaying those files in the dashboard.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 9

Now click on View Build Log. This gives more detail of what happened in the build. Its shown here as XML because we havent applied an XSLT style sheet to the output yet. This is another thing on the to do list that we will be getting to soon.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 10

CCNet also has the ability to send other types of notification based on the results of the build. For example, you can have and email sent if an incremental build fails and a text message if the daily or weekly build fails. While you can send an email with every build, it is recommended that you only send an email on failure. Here are the changes to the CCNet.config file.
<cruisecontrol> <project name="Math"> <webURL>http://localhost/ccnet/</webURL> <artifactDirectory>C:\CI\Artifacts\Math\Artifacts</artifactDirectory> <sourcecontrol type="vault" autoGetSource="true"> <executable>c:\program files\sourcegear\vault client\vault.exe</executable> <host>localhost</host> <username>Admin</username> <password>MyPass</password> <repository>Math</repository> <folder>$</folder> <workingDirectory>C:\ci\math</workingDirectory> </sourcecontrol> <publishers> <statistics /> <xmllogger /> <email from="CIServer@craigberntson.com" mailhost="local" includeDetails="true"> <users> <user name="Craig" group="cimanagers" address="craig@craigberntson.com" /> </users> <groups> <group name="cimanagers" notification="failed" /> </groups> </email> </publishers> </project> </cruisecontrol>

In this example, an email is sent to each member of the cimanagers group whenever the build fails. Tools discussed in this section
Tool CruiseControl.Net Download URL http://ccnet.thoughtworks.com

MSBuild
There are two primary tools you can use to do the actual build of the solution, MSBuild and NAnt. Many people are using NAnt, but my informal research showed MSBuild is gaining supporters. It is also the tool used by Visual Studio when you click Build. Finally, MSBuild is installed with the .Net runtime, which NAnt needs to do the build anyway. By just calling MSBuild directly and not using NAnt, I found I had one less piece of software and one less possible failure point. The CSharp and VB compilers are also part of the free .Net runtime. You will not need to install Visual Studio on the build box. In fact, if you do, you may need to purchase an additional Visual Studio license. In order to use MSBuild effectively with CCNet, additional add-ons are required. The first, an XML logger, is a .Net assembly that takes the XML file generated by MSBuild and formats it for display on the CCNet web server. Thoughtworks provides an assembly for this, but an improved logger by Christian Rodemeyer is available. The web page where you download this plugin also tells you to get new .xsl and .css files that are compatible with the plugin.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 11

You will also need to change CCNets dashboard.config file. All this is documented on the download p age. MSBuild uses your .Net project and solution files to determine what to build. Its easier to start with using the solution file directly, but doesnt give you as much control over the build process .
<cruisecontrol> <project name="Math"> <webURL>http://localhost/ccnet/</webURL> <artifactDirectory>c:\ci\artifacts\Math</artifactDirectory> <sourcecontrol type="vault" autoGetSource="true"> <executable>c:\program files\sourcegear\vault client\vault.exe</executable> <host>localhost</host> <username>Admin</username> <password>MyPass</password> <repository>Math</repository> <folder>$</folder> <workingDirectory>C:\ci\math</workingDirectory> </sourcecontrol> <tasks> <msbuild> <executable>C:\windows\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe</executable> <workingDirectory>C:\CI\Math\Math</workingDirectory> <projectFile>C:\CI\Math\Math\Source\Math.sln</projectFile> <buildArgs>/noconsolelogger /p:Configuration=Debug</buildArgs> <targets></targets> <timeout>15</timeout> <logger>C:\Program Files\CruiseControl.NET\server\Rodemeyer.MSBuildToCCNet.dll</logger> </msbuild> </tasks> <publishers> <statistics /> <xmllogger /> <email from="CIServer@craigberntson.com" mailhost="local" includeDetails="true"> <users> <user name="Craig" group="cimanagers" address="craig@craigberntson.com" /> </users> <groups> <group name="cimanagers" notification="failed" /> </groups> </email> </publishers> </project> </cruisecontrol>

This version of the Ccnet.config file has added a tasks section. Tasks are action elements. Anything you want CCNet to do is put inside a task. You can have multiple tasks per project. Each will run in sequential order until either there are no more tasks to run or one fails. This task has one thing to do, run MSBuild. A path MSBuild is provided along with the directory where the source code is located and the name of the project file to run. Here, I run the Solution file directly instead of the project files. Buildargs are passed on the command line to MSBuild to tell it what to do. The timeout tag tells CCNet the number of seconds to wait before assuming MSBuild has hung and it should be terminated. Finally, the logger tag tells CCNet what logging assembly to use for the build results. We can gain more control over MSBuild through the use of an MSBuild project file. This is an XML file, typically with a .proj extension. For our example project, well use Math.proj. The build project file should go in the same folder as the Visual Studio Solution file for your application. This means that it becomes part of the checked in source code. Ideally, each developer will use this same project file to do their own builds.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 12

The third party tool, MSBuild Sidekick may make it easier for you to edit the build project file. However, it requires you know something about what you want to do and the namespaces needed. I found it easier to just write the XML as I got started, but can see the usefulness of the tool.

First, a couple of changes are needed to the ccnet.config file.


<cruisecontrol> <project name="Math"> <webURL>http://localhost/ccnet/</webURL> <artifactDirectory>c:\ci\artifacts\Math</artifactDirectory> <sourcecontrol type="vault" autoGetSource="true"> <executable>c:\program files\sourcegear\vault client\vault.exe</executable> <host>localhost</host> <username>Admin</username> <password>MyPass</password> <repository>Math</repository> <folder>$</folder> <workingDirectory>C:\ci\math</workingDirectory> </sourcecontrol> <tasks> <msbuild> <executable>C:\windows\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe</executable> <workingDirectory>C:\CI\Math\Math</workingDirectory> <projectFile>C:\CI\Math\Math\Source\Math.proj</projectFile> <buildArgs>/noconsolelogger /p:Configuration=Debug</buildArgs> <targets>DailyBuild</targets> <timeout>15</timeout> <logger>C:\Program Files\CruiseControl.NET\server\Rodemeyer.MSBuildToCCNet.dll</logger>

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 13

</msbuild> </tasks> <publishers> <statistics /> <xmllogger /> <email from="CIServer@craigberntson.com" mailhost="local" includeDetails="true"> <users> <user name="Craig" group="cimanagers" address="craig@craigberntson.com" /> </users> <groups> <group name="cimanagers" notification="failed" /> </groups> </email> </publishers> </project> </cruisecontrol>

First, the projectFile name is changed to use Math.proj. A target is also added. Targets are names of sections in the build file. By specifying the target here, MSBuild is directed to perform the actions specified in the DailyBuild target. Here is the Math.proj file.
<Project DefaultTargets="BuildCode" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath) \MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" /> <!-- ASCII Constants used for Message output --> <PropertyGroup> <NEW_LINE>%0D%0A</NEW_LINE> <TAB>%09</TAB> <DOUBLE_QUOTES>%22</DOUBLE_QUOTES> <SPACE>%20</SPACE> </PropertyGroup> <!-- Solution Files--> <PropertyGroup> <SolutionName>math.sln</SolutionName> </PropertyGroup> <Target Name="GetProjects"> <!-- Get all projects in the solution --> <GetSolutionProjects Solution="$(SolutionName)"> <Output TaskParameter="Output" ItemName = "SolutionProjects" /> </GetSolutionProjects> <!-- Filter out solution folders and non .csproj items --> <RegExMatch Input="@(SolutionProjects)" Expression=".[\.]csproj$"> <Output TaskParameter="Output" ItemName = "Projects" /> </RegExMatch> <!-- Add Code folder to all solution project paths --> <RegexReplace Input="@(Projects)" Expression="(math.*)\\" Replacement="Code\$1\\" Count="-1"> <Output TaskParameter="Output" ItemName="CSProjects" /> </RegexReplace> <!-- Resolve test projects --> <RegexMatch Input="@(CSProjects)" Expression=".[\.](Test|Testing|UnitTest|IntegrationTest).*[\.]csproj$"> <Output TaskParameter="Output" ItemName="TestProjects" /> </RegexMatch> <!-- Resolve the code projects --> <CreateItem Include="@(CSProjects)" Exclude="@(TestProjects)"> <Output TaskParameter="Include" ItemName="CodeProjects"/> </CreateItem>

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 14

<Message Text="$(NEW_LINE)Resolved the following solution projects:" Importance="high" /> <Message Text="CodeProjects:$(NEW_LINE)$(TAB)@(CodeProjects>'%(RelativeDir)%(FileName)%(Extension)', '$(NEW_LINE)$(TAB)')" Importance="high"/> <Message Text="TestProjects:$(NEW_LINE)$(TAB)@(TestProjects>'%(RelativeDir)%(FileName)%(Extension)', '$(NEW_LINE)$(TAB)')" Importance="high"/> </Target> <Target Name="BuildCode" DependsOnTargets="GetProjects"> <Message Text="Target BuildCode" /> <MSBuild Projects="@(CodeProjects)"> <Output TaskParameter="TargetOutputs" ItemName="CodeAssemblies"/> </MSBuild> </Target> <Target Name="CleanCode" DependsOnTargets="GetProjects"> <Message Text="Target CleanCode" /> <CreateItem Include= "@(CodeProjects->'%(RelativeDir)bin\*\*.obj'); @(CodeProjects->'%(RelativeDir)bin\*\*.dll'); @(CodeProjects->'%(RelativeDir)bin\*\*.exe'); @(CodeProjects->'%(RelativeDir)bin\*\*.pdb'); @(CodeProjects->'%(RelativeDir)obj\*\*.obj'); @(CodeProjects->'%(RelativeDir)obj\*\*.dll'); @(CodeProjects->'%(RelativeDir)obj\*\*.exe'); @(CodeProjects->'%(RelativeDir)obj\*\*.pdb');"> <Output TaskParameter="Include" ItemName="SolutionOutput" /> </CreateItem> <Delete Files="@(SolutionOutput)" /> </Target> <Target Name="CleanTests" DependsOnTargets="CleanCode"> <Message Text="Target CleanTests" /> <CreateItem Include= "@(TestProjects->'%(RelativeDir)bin\*\*.obj'); @(TestProjects->'%(RelativeDir)bin\*\*.dll'); @(TestProjects->'%(RelativeDir)bin\*\*.exe'); @(TestProjects->'%(RelativeDir)bin\*\*.pdb'); @(TestProjects->'%(RelativeDir)obj\*\*.obj'); @(TestProjects->'%(RelativeDir)obj\*\*.dll'); @(TestProjects->'%(RelativeDir)obj\*\*.exe'); @(TestProjects->'%(RelativeDir)obj\*\*.pdb');"> <Output TaskParameter="Include" ItemName="SolutionOutput" /> </CreateItem> <Delete Files="@(SolutionOutput)" /> </Target> <Target Name="IncrementalBuild" DependsOnTargets="BuildCode"> <Message Text="Target Incremental" /> </Target> <Target Name="DailyBuild" DependsOnTargets="CleanTests;BuildCode"> <Message Text="Target DailyFramework" /> </Target> <Target Name="WeeklyBuild" DependsOnTargets="CleanTests;BuildCode"> <Message Text="Target Incremental" /> </Target> </Project>

Wow.. theres a lot going on here. It might help to walk through the file in the order things are processed. First, a default Target is specified. If no target is passed to MSBuild, this is the target that is executed. However, we used the DailyBuild target. Continuous Integraton 2007-2008 Craig Berntson Boise Code Camp 2008 15

Next, another project is imported. MSBuild contains a number of targets that do specific things in the build. The specified assembly adds additional targets that we need in the example. You can download the assembly from links at the end of this section. The PropertyGroup sections are processed next. Think of these as setting up variables. Now MSBuild looks for the target that CCNet passed to it on the command line, that is DailyBuild. The DailyBuild target depends on two other targets, CleanTests and BuildCode. It will run each of these in order before running any tasks specific to the target. So, MSBuild looks first for the CleanTests target. CleanTests depends on CleanCode, which depends on GetProjects. The GetProjects target opens the Solution file and iterates through each project listed and stores the name of each project in one of two collections, the first is a list of code projects, the second a list of test projects. Control is then returned to CleanCode, which gets a list of all generated code files from the previous build, then deletes them. Then, CleanTests is run, which deletes the generated test files. Control now returns to the DailyBuild target to find the next DependsOnTargets entry, which is BuildCode. BuildCode depends on GetProjects, but because it was already run, it isnt run again and processing continues on the current target, BuildCode. Here, a list of projects to build is created and the actual build occurs. Finally, control is return to DailyBuild and a message is added to the output file, or displayed on screen, depending on how it is run. Other targets, IncrementalBuild and WeeklyBuild are also supplied. As you are setting up the build project file, I suggest you run MSBuild from the command line in the Visual Studio Command Prompt. This will help you track down errors because they will be displayed on the screen. Once the build project is working, start calling it from CCNet.
Tools discussed in this section Tool MSBuild NAnt Rodemeyer.MsBuildToC CNet.dll msbuild2ccnet.xsl cruisecontrol.css MSBuild Sidekick MSBuild Community Tasks http://www.attrice.info/msbuild/index.htm http://msbuildtasks.tigris.org/ Download URL Installs with the .Net runtime http://nant.sourceforge.net http://confluence.public.thoughtworks.org/display/CCNETCOMM/Improve d+MSBuild+Integration

Unit Testing
If the build was successful, its time to run unit tests. Unit tests should be run on each build to help ensure the code functions properly. Hopefully, each developer ran their own targeted tests before checking in their code. But in Continuous Integration, all unit tests should be run. I will not discuss how to write unit tests or Test Driven Development as these are topics for an entire session and there is lots of information available that discusses them. Continuous Integraton 2007-2008 Craig Berntson Boise Code Camp 2008 16

The primary tool used for unit testing in .NET is NUnit. Visual Studio 2005 Team Suite has its own unit testing, but most shops cant afford the increased price of Team Suite. In Visual Studio 2008, Microsoft has pushed unit testing down into the Professional edition, but I think people who have been using NUnit will continue to do so rather than convert their tests. I use NUnit in this session. You should install NUnit on the CI Server. MSBuild will run the unit tests. Here is the updated Math.proj file.
<Project DefaultTargets="BuildCode" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath) \MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" /> <!-- ASCII Constants used for Message output --> <PropertyGroup> <NEW_LINE>%0D%0A</NEW_LINE> <TAB>%09</TAB> <DOUBLE_QUOTES>%22</DOUBLE_QUOTES> <SPACE>%20</SPACE> </PropertyGroup> <!-- Solution Files--> <PropertyGroup> <SolutionName>math.sln</SolutionName> </PropertyGroup> <!-- 3rd Party Program Settings --> <PropertyGroup> <NUnitExe>"C:\Program Files\NUnit 2.4.3\bin\nunit-console.exe"</NUnitExe> <NUnitArgs>/nologo /xml:c:/ci/artifacts/math/UnitTest-Results.xml</NUnitArgs> <NUnitFile>TestResult.xml</NUnitFile> </PropertyGroup> <Target Name="GetProjects"> <!-- Get all projects in the solution --> <GetSolutionProjects Solution="$(SolutionName)"> <Output TaskParameter="Output" ItemName = "SolutionProjects" /> </GetSolutionProjects> <!-- Filter out solution folders and non .csproj items --> <RegExMatch Input="@(SolutionProjects)" Expression=".[\.]csproj$"> <Output TaskParameter="Output" ItemName = "Projects" /> </RegExMatch> <!-- Add Code folder to all solution project paths --> <RegexReplace Input="@(Projects)" Expression="(math.*)\\" Replacement="Code\$1\\" Count="-1"> <Output TaskParameter="Output" ItemName="CSProjects" /> </RegexReplace> <!-- Resolve test projects --> <RegexMatch Input="@(Projects)" Expression="(MathTest).*[\.]csproj$"> <Output TaskParameter="Output" ItemName="TestProjects" /> </RegexMatch> <!-- Resolve the code projects --> <CreateItem Include="@(CSProjects)" Exclude="@(TestProjects)"> <Output TaskParameter="Include" ItemName="CodeProjects"/> </CreateItem> <Message Text="$(NEW_LINE)Resolved the following solution projects:" Importance="high" /> <Message Text="CodeProjects:$(NEW_LINE)$(TAB)@(CodeProjects->'%(RelativeDir)%(FileName)%(Extension)', '$(NEW_LINE)$(TAB)')" Importance="high"/> <Message Text="TestProjects:$(NEW_LINE)$(TAB)@(TestProjects->'%(RelativeDir)%(FileName)%(Extension)', '$(NEW_LINE)$(TAB)')" Importance="high"/> </Target> <Target Name="BuildCode" DependsOnTargets="GetProjects"> <Message Text="Target BuildCode" />

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 17

<MSBuild Projects="@(CodeProjects)"> <Output TaskParameter="TargetOutputs" ItemName="CodeAssemblies"/> </MSBuild> </Target> <Target Name="TestCode" DependsOnTargets="BuildCode"> <Message Text = "Target TestCode" /> <MSBuild Projects="@(TestProjects)"> <Output TaskParameter="TargetOutputs" ItemName="TestAssemblies"/> </MSBuild> <Exec ContinueOnError='true' Command='$(NUnitEXE) $(NUnitArgs) @(TestAssemblies)'/> </Target> <Target Name="CleanCode" DependsOnTargets="GetProjects"> <Message Text="Target CleanCode" /> <CreateItem Include= "@(CodeProjects->'%(RelativeDir)bin\*\*.obj'); @(CodeProjects->'%(RelativeDir)bin\*\*.dll'); @(CodeProjects->'%(RelativeDir)bin\*\*.exe'); @(CodeProjects->'%(RelativeDir)bin\*\*.pdb'); @(CodeProjects->'%(RelativeDir)obj\*\*.obj'); @(CodeProjects->'%(RelativeDir)obj\*\*.dll'); @(CodeProjects->'%(RelativeDir)obj\*\*.exe'); @(CodeProjects->'%(RelativeDir)obj\*\*.pdb');"> <Output TaskParameter="Include" ItemName="SolutionOutput" /> </CreateItem> <Delete Files="@(SolutionOutput)" /> </Target> <Target Name="CleanTests" DependsOnTargets="CleanCode"> <Message Text="Target CleanTests" /> <CreateItem Include= "@(TestProjects->'%(RelativeDir)bin\*\*.obj'); @(TestProjects->'%(RelativeDir)bin\*\*.dll'); @(TestProjects->'%(RelativeDir)bin\*\*.exe'); @(TestProjects->'%(RelativeDir)bin\*\*.pdb'); @(TestProjects->'%(RelativeDir)obj\*\*.obj'); @(TestProjects->'%(RelativeDir)obj\*\*.dll'); @(TestProjects->'%(RelativeDir)obj\*\*.exe'); @(TestProjects->'%(RelativeDir)obj\*\*.pdb');"> <Output TaskParameter="Include" ItemName="SolutionOutput" /> </CreateItem> <Delete Files="@(SolutionOutput)" /> </Target> <Target Name="IncrementalBuild" DependsOnTargets="BuildCode;TestCode"> <Message Text="Target Incremental" /> </Target> <Target Name="DailyBuild" DependsOnTargets="CleanTests;BuildCode;TestCode"> <Message Text="Target DailyFramework" /> </Target> <Target Name="WeeklyBuild" DependsOnTargets="CleanTests;BuildCode;TestCode"> <Message Text="Target Incremental" /> </Target> </Project>

First, a PropertyGroup is setup to hold information about NUnit. Then in the TestCode task, all test projects are collected so each can be run. They are then passed to NUnit as command line parameters. Results are saved to an XML file. Finally, an additional task is added to each of the build targets. The TestCode task will not execute unless BuildCode completes successfully.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 18

We also need to change the ccnet.config file so that it incorporates the xml results file produced by NUnit. Here is the updated file.
<cruisecontrol> <project name="Math"> <webURL>http://localhost/ccnet/</webURL> <artifactDirectory>c:\ci\artifacts\Math</artifactDirectory> <sourcecontrol type="vault" autoGetSource="true"> <executable>c:\program files\sourcegear\vault client\vault.exe</executable> <host>localhost</host> <username>Admin</username> <password>MyPass</password> <repository>Math</repository> <folder>$</folder> <workingDirectory>C:\ci\math</workingDirectory> </sourcecontrol> <tasks> <msbuild> <executable>C:\windows\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe</executable> <workingDirectory>C:\CI\Math\Math</workingDirectory> <projectFile>C:\CI\Math\Math\Source\Math.proj</projectFile> <buildArgs>/noconsolelogger /p:Configuration=Debug</buildArgs> <targets>DailyBuild</targets> <timeout>15</timeout> <logger>C:\Program Files\CruiseControl.NET\server\Rodemeyer.MSBuildToCCNet.dll</logger> </msbuild> </tasks> <publishers> <merge> <files> <file>c:\ci\artifacts\math\unittest-results.xml</file> </files> </merge> <statistics /> <xmllogger /> <email from="CIServer@craigberntson.com" mailhost="local" includeDetails="true"> <users> <user name="Craig" group="cimanagers" address="craig@craigberntson.com" /> </users> <groups> <group name="cimanagers" notification="failed" /> </groups> </email> </publishers> </project> </cruisecontrol>

The change here is in addition of a merge files section. We only need to specify the fully qualified file name of the NUnit results file for it to be included. Once youve added that, the information is available in the CCNet Dashboard. The first figure shows a results summary when you drill down to the Build Report.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 19

Drilling into NUnit Details gives the following result.

And finally, NUnit Timings results in this screen in CCNet Dashboard.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 20

Tools discussed in this section Tool NUnit Download URL http://www.nunit.org

Code Metrics
There are many ways to get code metrics. You can run coverage tools, fitness tests, check for refactoring needs and code duplication. Even nUnit gives some code metrics as you saw in the previous section. One code metric you can also run is to check how your code conforms to known coding standards. Microsoft provides FxCop to do this. As with NUnit, you can run the tool interactively or via a command line.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 21

To run FXCopy interactively, set it up in your MSBuild file. Heres the new Math.proj file.
<Project DefaultTargets="BuildCode" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath) \MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" /> <!-- ASCII Constants used for Message output --> <PropertyGroup> <NEW_LINE>%0D%0A</NEW_LINE> <TAB>%09</TAB> <DOUBLE_QUOTES>%22</DOUBLE_QUOTES> <SPACE>%20</SPACE> </PropertyGroup> <!-- Solution Files--> <PropertyGroup> <SolutionName>math.sln</SolutionName> </PropertyGroup> <!-- 3rd Party Program Settings --> <PropertyGroup> <NUnitExe>"C:\Program Files\NUnit 2.4.3\bin\nunit-console.exe"</NUnitExe> <NUnitArgs>/nologo /xml:c:/ci/artifacts/math/UnitTest-Results.xml</NUnitArgs> <NUnitFile>TestResult.xml</NUnitFile> <FxCopExe>"c:\program files\microsoft fxcop 1.36\fxcopcmd.exe"</FxCopExe> <FxCopArgs>/d:"c:\ci\math\math\source\math\bin\debug\ /f:math.exe"</FxCopArgs> <FxCopFile>/o:"c:\ci\artifacts\math\FXCop.xml"</FxCopFile> </PropertyGroup> <Target Name="GetProjects"> <!-- Get all projects in the solution --> <GetSolutionProjects Solution="$(SolutionName)"> <Output TaskParameter="Output" ItemName = "SolutionProjects" /> </GetSolutionProjects> <!-- Filter out solution folders and non .csproj items --> <RegExMatch Input="@(SolutionProjects)" Expression=".[\.]csproj$">

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 22

<Output TaskParameter="Output" ItemName = "Projects" /> </RegExMatch> <!-- Add Code folder to all solution project paths --> <RegexReplace Input="@(Projects)" Expression="(math.*)\\" Replacement="Code\$1\\" Count="1"> <Output TaskParameter="Output" ItemName="CSProjects" /> </RegexReplace> <!-- Resolve test projects --> <RegexMatch Input="@(Projects)" Expression="(MathTest).*[\.]csproj$"> <Output TaskParameter="Output" ItemName="TestProjects" /> </RegexMatch> <!-- Resolve the code projects --> <CreateItem Include="@(CSProjects)" Exclude="@(TestProjects)"> <Output TaskParameter="Include" ItemName="CodeProjects"/> </CreateItem> <Message Text="$(NEW_LINE)Resolved the following solution projects:" Importance="high" /> <Message Text="CodeProjects:$(NEW_LINE)$(TAB)@(CodeProjects>'%(RelativeDir)%(FileName)%(Extension)', '$(NEW_LINE)$(TAB)')" Importance="high"/> <Message Text="TestProjects:$(NEW_LINE)$(TAB)@(TestProjects>'%(RelativeDir)%(FileName)%(Extension)', '$(NEW_LINE)$(TAB)')" Importance="high"/> </Target> <Target Name="BuildCode" DependsOnTargets="GetProjects"> <Message Text="Target BuildCode" /> <MSBuild Projects="@(CodeProjects)"> <Output TaskParameter="TargetOutputs" ItemName="CodeAssemblies"/> </MSBuild> </Target> <Target Name="TestCode" DependsOnTargets="BuildCode"> <Message Text = "Target TestCode" /> <MSBuild Projects="@(TestProjects)"> <Output TaskParameter="TargetOutputs" ItemName="TestAssemblies"/> </MSBuild> <Exec ContinueOnError='true' Command='$(NUnitEXE) $(NUnitArgs) @(TestAssemblies)'/> </Target> <Target Name="FxCop" DependsOnTargets="BuildCode"> <Message Text = "Target FxCop"/> <Exec ContinueOnError='true' Command='$(FxCopEXE) $(FXCopArgs) $(FxCopFile)'/> </Target> <Target Name="CleanCode" DependsOnTargets="GetProjects"> <Message Text="Target CleanCode" /> <CreateItem Include= "@(CodeProjects->'%(RelativeDir)bin\*\*.obj'); @(CodeProjects->'%(RelativeDir)bin\*\*.dll'); @(CodeProjects->'%(RelativeDir)bin\*\*.exe'); @(CodeProjects->'%(RelativeDir)bin\*\*.pdb'); @(CodeProjects->'%(RelativeDir)obj\*\*.obj'); @(CodeProjects->'%(RelativeDir)obj\*\*.dll'); @(CodeProjects->'%(RelativeDir)obj\*\*.exe'); @(CodeProjects->'%(RelativeDir)obj\*\*.pdb');">

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 23

<Output TaskParameter="Include" ItemName="SolutionOutput" /> </CreateItem> <Delete Files="@(SolutionOutput)" /> </Target> <Target Name="CleanTests" DependsOnTargets="CleanCode"> <Message Text="Target CleanTests" /> <CreateItem Include= "@(TestProjects->'%(RelativeDir)bin\*\*.obj'); @(TestProjects->'%(RelativeDir)bin\*\*.dll'); @(TestProjects->'%(RelativeDir)bin\*\*.exe'); @(TestProjects->'%(RelativeDir)bin\*\*.pdb'); @(TestProjects->'%(RelativeDir)obj\*\*.obj'); @(TestProjects->'%(RelativeDir)obj\*\*.dll'); @(TestProjects->'%(RelativeDir)obj\*\*.exe'); @(TestProjects->'%(RelativeDir)obj\*\*.pdb');"> <Output TaskParameter="Include" ItemName="SolutionOutput" /> </CreateItem> <Delete Files="@(SolutionOutput)" /> </Target> <Target Name="IncrementalBuild" DependsOnTargets="BuildCode;TestCode"> <Message Text="Target Incremental" /> </Target> <Target Name="DailyBuild" DependsOnTargets="CleanTests;BuildCode;TestCode;FXCop"> <Message Text="Target DailyFramework" /> </Target> <Target Name="WeeklyBuild" DependsOnTargets="CleanTests;BuildCode;TestCode;FXCop"> <Message Text="Target Incremental" /> </Target> </Project>

The changes start with the addition of new entries in the property group. The FxCop target specifies a specific assembly rather than incrementing through a list of assemblies. Changing this to more generic code is still on our todo list. Notice that we dont add a call to FxCop in the IncrementalBuild target. This is because the IncrementalBuild should run quickly and only do the unit tests. You also need to merge the results of FxCop into the CCNet dashboard report. Here Ive only included a portion of the CCNet.config file.
<publishers> <merge> <files> <file>c:\ci\artifacts\math\unittest-results.xml</file> <file>c:\ci\artifacts\math\fxcop.xml</file> </files> </merge> <statistics /> <xmllogger /> <email from="CIServer@craigberntson.com" mailhost="local" includeDetails="true"> <users> <user name="Craig" group="cimanagers" address="craig@craigberntson.com" /> </users> <groups> <group name="cimanagers" notification="failed" /> </groups> </email> </publishers>

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 24

Tools discussed in this section Tool FxCop Download URL FxCop is distributed as part of the Windows SDK . http://www.microsoft.com/downloads/details.aspx?FamilyID=c2b1e300f358-4523-b479-f53d234cdccf&DisplayLang=en

Documentation
Many people argue against documentation in this day and age when Agile processes are becoming the norm. However, this is a misguided understanding of Agile development. Agile does not say no documentation, it says that building software should take precedence over documentation. But what if documentation could be automated? Im talking here about developer documentation, not end user documentation. This is especially important if you are writing code to be used by other developers. In the .Net 1.0 and 1.1 days, nDoc was used to create documentation from source code. However, Microsoft is currently developing a tool, codenamed Sandcastle, that can be called from the build script to generate documentation. Sandcastle uses the /// comments in your code to generate CHM or HxS help files. If you want to create CHM files, you will also need to download and install HTML Help Workshop. In its current CTP version, Sandcastle is a bit awkward to use as it consists of several assemblies, xsl files, and steps to generate the help files. In the future, the Sandcastle team will provide Visual Studio integration and a GUI tool to make the process easier. As I am writing this, we are still working on Sandcastle and dont have it running yet in our environment. However, I wanted you to be aware of this tool to help create your developer documentation.
Tools discussed in this section Tool Sandcastle HTML Help Workshop Download URL http://www.codeplex.com/Sandcastle http://msdn2.microsoft.com/en-us/library/ms669985.aspx

Feedback
Getting results of a build back to the team in a timely manner is one of the most important aspects of Continuous Integration. There are several tools available for this, starting with CruiseControl itself. The most common way of getting information back to the team is via a web site where the latest build information is available. CruiseControl.Net uses IIS to host the web pages. The advantage is the information is complete and as detailed as you want it to be. The downside is that team members need to br owse to the site to see whats happening. CruiseControl.Net also includes an application, CCTray, that can be installed on your computer. It then sits in the system tray and monitors the build status. The color of the icon changes depending on the build status, thus providing more immediate information. Email is also one of the most used feedback features. CruiseControl.Net includes the ability to send emails via your SMTP server. But again, email suffers many of the same problems as the web site.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 25

Because email is available, it becomes easy to send SMS messages to a cell phone. Be careful however, because sending too many messages will cause the important ones to be ignored. CCNet can be configured to only send a message when a build fails and again when the issues have been fixed. You could also hook cause a sound to be played when a build fails. This would require a sound card and speakers hooked up to the build box. One drawback is that the sound is only played once so you have to be present to hear it. An interesting idea is to use an Ambient Orb. The Orb is basically a light that connects to wirelessly to your network. The color of the Orb can be controlled, so you can have green for a successful build and red for a failure. If failures continue in subsequent builds, the color of Orb can be darkened to indicate serious issues exist. However, the Orb requires an additional expense but the cool geek factor cannot be ignored.

Tools discussed in this section Tool Ambient Orb Information URL http://www.ambientdevices.com

Whats next?
Were just touching the tip of the iceberg in our CI implementation. More unit tests need to be added. Other code metrics tools such as coverage measurement, duplicated code, refactoring checking, and more are available. We would like to call our automated QA test tools at least on a weekly basis. We would also like to call InstallShield to create the install set. Im sure there is even more that can be done.

Summary
Continuous Integration can help ensure you create a better quality application in less time. By automating aspects of the build, testing, installation, code metrics, and other areas, your team and your customers will always know the status of the current project. Adding CI to your process should be a priority as are many best practices. Craig Berntson is a Microsoft Most Valuable Professional (MVP) for Visual FoxPro, a Microsoft Certified Solution Developer, and President of the Salt Lake City Fox User Group. He is the author of CrysDev: A Developers Guide to Integrating Crystal Reports, available from Hentzenwerke Publishing. He has also written for FoxRockx, FoxTalk and the Visual FoxPro User Group (VFUG) newsletter. He has spoken at Advisor DevCon, Essential Fox, DevEssentials, the Great Lakes Great Database Workshop, Southwest Fox, DevTeach, FoxCon, German FoxPro Continuous Integraton 2007-2008 Craig Berntson Boise Code Camp 2008 26

Devcon, Prague FoxPro DevCon, Microsoft DevDays and user groups around the US. Currently, Craig is a Senior Software Engineer at Fortune 100 company. in Salt Lake City, Utah, USA. You can reach him at craig@craigberntson.com,, www.craigberntson.com, or his blog at www.craigberntson.com/blog.

Continuous Integraton 2007-2008 Craig Berntson

Boise Code Camp 2008 27

Vous aimerez peut-être aussi