Continuous Integration (CI) is a process where software under development is continuously compiled and tested as changes are submitted to a revision control system. The goal is to identify issues such as code changes that break the build, broken unit tests, and broken functional tests, as soon as possible after such a change is committed. By identifying these issues earlier, they are resolved sooner, reducing the impact to the entire team. In an Agile Software Development methodology, this type of immediate feedback is critical for a team to meet their iterative goals.
Setting up a CI environment consists of several parts:
- Dedicated build machine
- Continuous Integration software
- Automated and Repeatable Builds
- Automated Unit and Functional Tests
CSS uses a Continuous Integration process for all of our software development efforts.
A dedicated and standalone build machine serves as a “clean room” environment for building software products. This machine is a controlled environment, with new software and upgrades taking place only when our development schedule allows. Build machines are often set up in a virtualized environment, making it easy to expand as well as being “futureproof” if more capacity is needed down the road.
The second part is the CI server itself. There are several commercial and open-source CI systems, including Jenkins, Microsoft TFS, Atlassian Bamboo, Cruise Control, Cruise Control.NET, and many others. The CI server is responsible for managing the workflow of your CI process. It can be triggered to build a software product manually, on a time-basis (Nightly Builds), or on a change-basis by continually monitoring your Revision Control System looking for changes from developers. CI servers execute your unit tests and analyze the results to help determine the health of a build. Most CI servers can be configured to archive the build artifacts (binaries, installs, and symbols) after a successful build.
A third component is automated builds. A fully automated build benefits both the CI process as well as the development team. Ideally the same automated build system is used by both developers and the CI server. A developer should only need to install a few pre-requisites onto their machine (such as Visual Studio). The build script should take care of downloading necessary dependencies (both internal and external) before the source code build begins.
The last part of a CI setup (and a good development practice even without CI) is automated unit and functional tests. While it’s great to know that a set of changes to the product will compile, it’s even better to know that they didn’t break the functionality. Even better than that is having this verification take place without any actions by a developer, freeing up developers to remain focused on build product features.
CI Process in Action at CSS
Our continuous integration process kicks in whenever a developer checks in changes to our Revision Control System. We’re currently using Jenkins as our CI Server. Jenkins has its roots in the Java realm, but now contains hundreds of plugins to expand its functionality. So far, it has worked very well for building our .NET-based, as well as native code-based products. We’re also big fans of the BruceSchneier plug–in for Jenkins.
One of the many bits of Bruce Schneier facts that appear on our build system.
Our existing MSBuild-based build process made it easy to configure Jenkins. All we had to do was select the Subversion URL for the project, and select our MSBuild script, passing the target name that represents the complete CI build. We invested a lot of effort into our MSBuild-based build system. Our goal is to have a single MSBuild template that we re-use for each product, which allows our builds to be consistent and reliable. Our MSBuild script performs the following tasks:
- Download dependencies
- Compilation of the code for the product
- Unit test projects are built and executed
- Obfuscation is applied to the build outputs
- Authenticode Signing of the build outputs
- Build the Install
- Authenticode Signing of the Install
- Symbol files posted to our internal symbol server
- Archival to our internal file server
Once a check-in is detected, the CI server automatically downloads the latest product source. It initiates a source build, and if successful, continuous on to execute our tests, build the installers, digitally sign the components, and post the build artifacts to our internal file server. 10-15 minutes later, we have a full report about the build, including test results. Lastly, the CI server notifies us (either through a system tray application or email) whether the build was successful or not.
Continuous Integration process in action.
Setting up a good CI process is no easy task, and requires more than just a nominal amount of work to get it just right. While there is an up-front cost to this, organizations will undoubtedly see payback from their CI process. The CI process will immediately discover source code compilation failures and unit test failures, instead of letting them accumulate and hampering productivity across the entire team. The build history, including unit test execution history, is archived and accessible through the CI server’s portal. A solid CI process helps keep developers writing code, instead of working on repetitive tasks such as running unit tests or manually building the product.
- Jenkins CI – https://jenkins-ci.org
- Jenkins Plugins – https://wiki.jenkins-ci.org/display/JENKINS/Plugins
- Martin Fowler on Continuous Integration – https://martinfowler.com/articles/continuousIntegration.html
- Wikipedia: Continuous Integration – https://en.wikipedia.org/wiki/Continuous_integration