Getting Started with Continuous Integration
Imagine that you've just fixed a bug in your application's code and checked the fix into the project's source code repository. How much do you know now? If all you know is that the fix worked on your machine, you're playing with fire. If you've run the fix through a suite of integration tests and are confident that it didn't break anything else in the project's code, you're doing much better: your process is under control. But would you believe that you can do better than that? There are some teams that know that the new checkin passes the integration tests, that the tests cover the entire codebase, that the project's coding standards are being followed, and that there's no excessive duplication in code. And they know this after every checkin. Best of all, they know this without human intervention: a little icon in their system tray turns red if anything goes wrong, and stays happily green otherwise. At any time, they can visit a Web page on their local intranet to check up on the current state of the code.
The technique behind this seeming magic is called continuous integration, and it's not as difficult to set up as you might think. In this article I'll show you how the pieces fit together for a simple .NET continuous integration server that will help you monitor the quality of your own coding efforts in real time.
Setting Up the Server
The first step in continuous integration is to set up a build server. Ideally, this will be a dedicated computer that isn't used for anything else. This is the computer that will be the master source for final builds of your software: if all goes well, at any time you should be able to burn a working CD from this machine's hard drive and be ready to install it on customer boxes. You'll want a computer that is powerful enough to run all of the tools that you need to build your software. If you're doing .NET development, that means that it at least needs to be able to manage command-line builds using the csc or vbc compilers, though you shouldn't skimp here. The faster the build machine can turn out a fresh build of your software, the faster you get feedback on the code quality of new checkins.
The key piece of software you'll need is a continuous integration server. This is a master controller for the build process: it monitors your source code control repository for checkins, and every time that it spots one, it gets the latest version of the code. Then it kicks off a build by calling other tools, such as compilers and unit test tools. Finally, the continuous integration server notifies team members of the build status by publishing information in a variety of ways, from updated Web pages to e-mailed status messages, and goes back to watching for new checkins. There are several continuous integration servers for .NET available; I happen to use CruiseControl.NET, which is currently at the 1.0RC2 release level. Installing CruiseControl.NET is straightforward; you'll want to have the .NET Framework and IIS with ASP.NET installed first. Then just download and run the installers for CruiseControl.NET and the separate Web Dashboard (the applications are free) and you're off and running. CruiseControl.NET is open source, so if you prefer you can start with the source code and build it yourself.
Installing Your Tools
The next thing the server needs is everything required to actually build your software, starting from the source code. It's impossible to give a complete list here, because every project is different. What you need to do is observe your own manual practices, starting from source code checkin and ending with the deliverable. Certainly you'll need to install a source code control client, so the continuous integration server can get at the source code, and either the .NET SDK or Visual Studio .NET to build the software. From there, you may need a unit-testing tool, an obfuscator, a code-quality checker, a setup builder, and other tools.
Depending on your own particular set of tools, you'll also want to install a general-purpose build-scripting utility such as NAnt or MSBuild. While CruiseControl.NET can call many tools directly, it can't know about every development tool in the universe. Sometimes you may find that you need to have CruiseControl.NET call a NAnt task that in turn calls your development tool of choice.
It's important that you get the same versions of all your tools on the build server that you're using on your development computers. Otherwise, some day you'll spend far too much time trying to track down a subtle error caused by, say, the testing tool being at a different revision level on different machines. One way to handle this is to actually check the tools into your source code control repository and install them from there. Another is to use a central software distribution system such as Microsoft Systems Management Server to install the build tools.
A Simple Example
To make this all a bit more concrete, let's look at one of the projects on my own continuous integration server. My build tools stack for this project currently includes these tools:
- Subversion for source code control.
- Visual Studio .NET to handle the actual builds.
- NUnit for unit testing.
- NAnt for build automation.
- FxCop for conformance checking.
- NCover for code coverage analysis.
- Simian for similarity analysis.
- Vil for collecting code metrics.
One key to making the process work smoothly is the proper use of NAnt. I want the build process on the continuous integration server to be as close as possible to the build process on my local development computer. That way, if anything goes wrong, it's easy for me to replicate what happened. So rather than have CruiseControl.NET call out to the individual tools directly, I've set it up to call a NAnt build file that in turn calls the individual build tools. With this indirection in place, I can replicate what CruiseControl.NET is doing simply by invoking NAnt by had any time I need to check what's going on. The NAnt configuration file looks like this:
<?xml version="1.0" ?> <project name="nant" default="compile" xmlns="http://nant.sf.net/schemas/nant.xsd"> <target name="compile"> <solution solutionfile="RssDrop.sln" configuration="AutomatedDebug" /> </target> <target name="test" depends="compile, run-unit-tests" description="Compile and Run Tests" /> <target name="ccnet" depends="compile, fxcop, ncover, simian, vil" description="Cruise Control.NET target" /> <target name="run-unit-tests"> <nunit2> <formatter type="Xml" usefile="false" /> <test assemblyname="RssDropLibUnitTests.dll" /> </nunit2> </target> <!-- Run FxCop compliance checker --> <target name="fxcop"> <exec program="toolsFxCopfxcopcmd.exe" ... /> </target> <!-- NOTE: ncover runs nunit tests --> <target name="ncover"> <exec program="toolsncoverNCover.Console.exe" ... /> </target> <!-- Find duplicate code --> <target name="simian" > <exec program="toolssimiansimian-2.2.7.exe" ... /> </target> <!-- Code metrics --> <target name="vil"> <exec program="toolsvilvil.exe" ... /> </target> </project>
I've omitted the details of the command lines and arguments for the various tools so as to make the structure of this file more clear. Even if you're not familiar with NAnt, you should be able to tell what's going on here. A NAnt configuration file is a collection of targets, each of which tells NAnt what to do in a particular circumstance; when you invoke NAnt, you tell it which target to carry out. The
ccnet target is the composite target that CruiseControl.NET will execute; it in turn calls the targets that make up the various steps involved in building and testing my project.