• No results found

Continuous Integration for.net Development

N/A
N/A
Protected

Academic year: 2021

Share "Continuous Integration for.net Development"

Copied!
27
0
0

Loading.... (view fulltext now)

Full text

(1)

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.

(2)

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 doesn’t work on the customer’s machine and the developer can’t reproduce the error

 You don’t 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 don’t know the current state of the software

 Quality of the software is low because coding or architectural standards weren’t followed or because code has been duplicated

Wouldn’t it be great if you could reduce the possibilities of these and more issues? Wouldn’t it be even better if you could automate the process that does that? You can reduce quality issues in an automated fashion. It’s 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, we’ll 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 isn’t just a practice adopted by the build manager. Simply put, Continuous Integration (CI) is a process that helps ensure quality software. Notice I didn’t say guarantee because as we know, there is no such thing as bug-free software.

A typical CI process works like this:

1. The developer builds the software on his local PC and then runs unit tests on it.

2. After the code passes all the unit tests, the source is checked in to a source code repository.

3. 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.

4. The CI Server builds the software and then runs unit tests.

5. 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.

(3)

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.

(4)

Too much change Don’t try to implement everything at once. When you first setup your CI system, you may start with doing daily builds. Technically, this isn’t 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

 Don’t 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 (don’t 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.

(5)

Source Control

For years, you’ve heard the importance of using source control for your code. I won’t 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. Don’t 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 Download URL

Subversion http://subversion.tigris.org/

Tortoise http://tortoisesvn.tigris.org/

SourceGear Vault 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 doesn’t 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

(6)

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 we’re 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 won’t always want to create install bits so you may only do parts of the build. Here’s 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 aren’t any more tasks to perform than what I’ve listed. However, we anticipate in the future that we’ll 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 Fowler’s company). While CCNet doesn’t 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 it’s easy to see what’s 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.

(7)

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 won’t 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 it’s 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 didn’t start the CCNet console application.

(8)

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.

(9)

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.

(10)

Now click on View Build Log. This gives more detail of what happened in the build. It’s shown here as XML because we haven’t 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.

(11)

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 Download URL

CruiseControl.Net 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.

(12)

You will also need to change CCNet’s dashboard.config file. All this is documented on the download page. MSBuild uses your .Net project and solution files to determine what to build. It’s easier to start with using the solution file directly, but doesn’t 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, we’ll 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.

(13)

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>

(14)

</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>

(15)

<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.. there’s 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.

(16)

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 isn’t 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 Download URL

MSBuild Installs with the .Net runtime

NAnt http://nant.sourceforge.net Rodemeyer.MsBuildToC CNet.dll msbuild2ccnet.xsl cruisecontrol.css http://confluence.public.thoughtworks.org/display/CCNETCOMM/Improve d+MSBuild+Integration

MSBuild Sidekick http://www.attrice.info/msbuild/index.htm

MSBuild Community Tasks

http://msbuildtasks.tigris.org/

Unit Testing

If the build was successful, it’s 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.

(17)

The primary tool used for unit testing in .NET is NUnit. Visual Studio 2005 Team Suite has its own unit testing, but most shops can’t 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" />

(18)

<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.

(19)

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 you’ve added that, the information is available in the CCNet

(20)

Drilling into NUnit Details gives the following result.

(21)

Tools discussed in this section

Tool Download URL

NUnit 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.

(22)

To run FXCopy interactively, set it up in your MSBuild file. Here’s 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 -->

(23)

<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');">

(24)

<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 don’t 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 I’ve 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>

(25)

Tools discussed in this section

Tool Download URL

FxCop FxCop is distributed as part of the Windows SDK .

http://www.microsoft.com/downloads/details.aspx?FamilyID=c2b1e300-f358-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? I’m 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 don’t 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 Download URL

Sandcastle http://www.codeplex.com/Sandcastle

HTML Help Workshop 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 browse to the site to see what’s 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.

(26)

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 Information URL

Ambient Orb http://www.ambientdevices.com

What’s next?

We’re 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. I’m 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 Developer’s 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

(27)

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

References

Related documents

This volume contains the revised accepted papers selected from among those presented at the 8th Italian Research Conference on Digital Libraries (IRCDL 2012), which was held at

Recurrent programmes (excl. Reruns, sports events, programmes aired less than 3 times, programmes shorter than 3 minutes)... guests in DVR- or IPTV-households,

Almost all potential customers would prefer not to do business with someone who deceives them, no matter how good a product or service they offer.. As a marketer you

Between 1 GB and 2 GB 1.5 times the size of RAM Between 2 GB and 8 GB Equal to the size of RAM More than 8 GB .75 times the size of RAM 500MB free space in /tmp directory.

vertices of the standard lattice tiling; if we choose the point set data as the vertices or the symmetry centres, both cases will give us Delone set as a stan- dard lattice

Free Compulsory Universal Basic Education (FCUBE) policy recommends the formation of School Management Committees (SMCs), governing bodies and Parent-Teacher

[r]

foundation; heat of hydration; mass concrete; modulus of elasticity; placing; portland cement; pozzolan; restraint; stress; temperature; tensile strength; thermal expansion;