The DevOps world has matured dramatically in the past few years, enabling us to reduce development release cycles and iterate much more quickly, which has led to more rapid feature delivery and innovation. Over a decade ago we were introduced to a development practice called Continuous Integration, in which a server application automated the task of checking source code out from a source code repository, building it, and testing it, when developers check in code. Continuous Integration served us well and established the foundation for the next step in automating our build and deploy process: Continuous Delivery.
This three part article series presents an overview of Continuous Integration, Continuous Delivery, and Continuous Deployment, and introduces Jenkins as a build tool that enables all three.
Introduction to Continuous Integration
Continuous Integration was popularized by an article that Martin Fowler wrote in September 2000 and subsequently updated in May 2006. Continuous integration introduces a new server application called a Continuous Integration Server (CI Server) that monitors a source code repository and, when a developer checks in code, the CI Server checks out that code, builds it, and executes its unit tests. This is summarized in figure 1.
Continuous Integration builds can be divided into a primary build and a set of secondary builds. The purpose of the primary build is to quickly test and ensure that the check-in does not break anything. In order to ensure that the build is quick, we expect the primary build is to run in less than 10 minutes so that it does not impact other developers’ ability to check in code. We want to run a build every time that a developer checks in code so that we can isolate any breaking code to a small subset (the checked in code) and keeping the build under 10 minutes helps us achieve this goal.
But, as you might surmise, building and testing a large application in under 10 minutes means that your test suite cannot be very robust. In order to keep builds fast, you might opt to run tests against stubs instead of testing your application running against a real database or a real set of services. In other words, the primary build tests the application in isolation from its external dependencies. When the primary build completes successfully then other developers are free to check in code and run another primary build.
Because we need to keep the primary build under 10 minutes, we can spawn additional secondary builds that run after the primary build completes successfully that test the application more substantially. Secondary builds can test the application running against real external dependencies, such as a live database and live services. Or stated another way, one of the more popular secondary builds tests the application as an integrated whole. If we find an error in the integration test, then we can add additional tests to the primary build to try to detect that error in the future, before the application makes its way to the integration test.
As soon as we saw that we could run integration tests under the CI banner, we then extended the paradigm to run performance and load tests. Load tests will run much less frequently, but still on the order of once or twice a day. By doing this we can identify performance issues sooner in our release cycle and avoid major headaches that commonly occur right before we’re ready to push our application into production.
For me personally, the value of continuous integration was realized when I looked back on painful releases of day old. To illustrate this, in 1999 I was working for a factory automation company that adopted the Rational Unified Process (RUP) to move away from waterfall development and to iterative development. In iterative development, we break a project into a set of iterations, each with a clear set of deliverables. We work for a period of time, three months in my example, and deliver a set of working and testable code. In this example, I vividly remember spending three months building code in isolation from other teams before bringing all of that code, written by a dozen other teams, together and performing integration tests. Needless to say, it was a disaster! We spent the next two to three months integrating our components and making the product work holistically. Thus, when I learned about continuous integration and the goal of testing the integrated application on each build, I realized that this painful process could be avoided. In essence, we make integration a non-issue in our product development lifecycle - when we’re ready for a release, we take the latest fully integrated and fully tested application from our CI server, label it, and move on..
In my next post, I'll dive into Continuous Delivery and Continuous Deployment.