Kirk Knoernschild discusses the subtle though significant ways that continuous integration can be leveraged—from helping to align IT with the business to enforcing architectural constraints—and shows that this fundamental aspect of agility is the defining and necessary element of a truly agile development experience.
Agile has grown and evolved from a very simple developer centric process defined by Extreme Programming to a complex product brand that enterprises are using to bring more resiliency to governance programs, enterprise architecture initiatives, and application portfolio management efforts. But at its roots, there remains a key fundamental aspect that defines the essence of agility on the software development project. Continuous Integration is a strategy where software is integrated and built continuously, or at least as frequently as is feasibly possible. Many teams have adopted a continuous integration strategy, yet do not fully realize all the benefits that continuous integration might bring to the development effort. This article discusses the subtle though significant ways that continuous integration can be leveraged—from helping to align IT with the business to enforcing architectural constraints—and shows that this fundamental aspect of agility is the defining and necessary element of a truly agile development experience.
Closing the Loop
At the center of a continuous integration strategy is an automated and repeatable build. In "Activating the Lifecycle", I talked about the aspects defining a robust build process, and pointed out some of the benefits of an automated and repeatable build. But an automated and repeatable build does not guarantee that a software development team will realize the advantages of continuous integration. Having worked with numerous software teams who have setup an automated and repeatable build, I've discovered that the most successful teams use continuous integration to close the feedback loop.
For a moment, visualize the macro-software lifecycle. Traditional teams begin by analyzing the problem and gathering requirements, designing a solution, constructing the software, executing tests, and finally, deploying the completed software. Of course, iterative development aims to perform each of these steps within iterations, possibly a four to six week period. Certainly it's logical that if we perform a slice of the entire software lifecycle in less time, we'll generate important feedback that can be used to plan future iterations. This is the greatest benefit of iterative development, and most experienced development teams recognize its value. Like a traditional software lifecycle, iterations tend to go well the first few days. But as the iteration deadline approaches, the team frequently falls behind schedule, often creating a backlog of items to be addressed in subsequent iterations. Now take this notion one step further.
Continuous integration aims to perform the entire software lifecycle each time the automated and repeatable build is performed. In other words, each build represents a complete pass through the software development lifecycle. Is this possible? Is it entirely realistic to believe that a team can perform the entire lifecycle each time the software is built? Hourly? Possibly even more frequently? Doubtful. However, a sound continuous integration strategy creates the ability for a software team to perform any part of the lifecycle any time they desire or need. This very notion, the ability to perform any part of the software lifecycle at any time, is the single greatest benefit that teams practicing a successful approach to continuous integration understand. It is no surprise, then, that teams successful with continuous integration have mastered knowing when to perform software lifecycle activities to maximize the necessary feedback at the appropriate moment in time. These teams have closed the feedback loop, and left little room open for high risk, long-term, unsuspecting problems to surface.
The Right Stuff
Of course, if software development was as simple as implementing a continuous integration strategy, we'd all have done it long ago. Unfortunately, while some teams find great success with continuous integration, others do not. Knowing when to perform various lifecycle activities can be amazingly difficult. As a general rule, any activity performed later in the software lifecycle that can be moved toward the beginning of a project helps you gauge your progress. Essentially, you can view continuous integration as a process pattern
Like all other patterns, there is a common underpinning that forms the foundation of continuous integration (that being the build), but the overall strategy must be tailored to fit your context (i.e., your team, your project, and your technology). Young developers learn early that simply because the application works on their machine doesn't mean that it works on other machines. Experienced continuous integration practitioners recognize that a successful build only proves that the software passes the compiler and the automated test suite, but does not mean that the application does what clients need. One oft-cited benefit of continuous integration is that teams avoid significant problems, such as compile errors, when attempting to integrate code. While true, this is only the beginning, as there are other critical activities that can dramatically improve the advantages of a successful continuous integration strategy.
When a build fails, whether due to a compiler error or test failure, all team members should be notified. Upon failure, it must become the highest priority of all team members to fix the problem. Other significant events should also be communicated, such as tests that execute slowly and successful deployments to various tiers.
I've long held the belief that the source code is the most important artifact created by the software development team, as the source code is the only artifact used to create the executable version of the product. In "The Value of executable Artifacts," I discussed a number of ways that teams can extend the build to provide feedback to the development team. For many teams, however, their continuous integration strategy stops after the source is compiled and tests are executed. Except for iteration milestones where a specific build is tagged for deployment, most times the compiled settles into the dust bin and is never used for anything useful. While compiling the application and executing all unit tests provides some advantages to the software developer crafting the application, teams that fail to deploy the build are missing out on important feedback. Bundling the application and deploying it to a simulated production environment completes the development lifecycle and opens the possibility to perform other important lifecycle activities such as the following:
Making the Application Accessible
The ability to build and deploy the software system means the team always has a functional product, and teams should take advantage of this fact. If you're developing a rich client application, be sure it is bundled in the same manner as if it were a true production release and allows team members to download and install the application. If it is a web application, publish the URL so all project team members have access. Encourage folks to experience the application. Along the way, always encourage customer feedback.
Creating a Dashboard
With each build, update a project dashboard. Typically, the dashboard is a web application containing information on the most recent build. Such information might include test coverage reports, static analysis reports such as JavaNCSS, auto-generated documentation such as JavaDoc, and visual representations such as those produced by JDepend and JarAnalyzer. Use this information as part of corporate architecture, design, and code reviews to somewhat objectively analyze the quality of your source code.
Testing the Application
From the moment you have a functional product that is accessible, you can develop and execute test suites that include load tests, performance tests, usability tests, failover tests, and acceptance tests. Getting quality assurance involved early in the project lifecycle ensures that continuous testing takes place and allows the team to grow a very robust suite of tests. Continuous regression testing, beginning very early in the project, ensures that any problem encountered is only as old as the most recent successful run of the test suite. As the test strategy evolves, project management can begin tracking project status through the growth of successful features that have been tested over time.
By structuring your build scripts so that lower level modules with fewer dependencies are built first, followed by those modules with dependencies on perviously compiled components, you can enforce architectural integrity. Through automation, you can track and manage component versions. If undesirable architectural qualities begin to surface, you will be able to identify them.
A great way to garner feedback from your customers is to show them the system, and allow them to experiment by simulating their work. Frequent system demos involving customers and developers are a great supplement to traditional requirements elicitation sessions. Showing the system to both developers and clients helps facilitate discussion and iron out any challenges associated with especially tricky or complex areas of the application. Especially as some folks might drown themselves in the details of a specific use case or subsystem, system demonstrations ensures that everyone maintains a consistent perspective on application growth.
The consistency and frequency of your approach to continuous integration allows you to identify and manage trends throughout the development lifecycle. Maintaining a history of past builds on a project dashboard allows project managers and developers to track the ration of successful to unsuccessful builds, identifying potential quality issues. Trends in coverage reports and analysis tools helps developers manage growth successfully. Recognizing growth in total features tested can help establish more reliable development velocity.
Sticking with What Works
Teams with a successful continuous integration strategy need not change their process after their first production deliverable. Application maintenance efforts that are of short duration with limited scope fit well into the existing approach. Because teams have been focusing on managing change throughout the software lifecycle, change requests are an inherent part of their work.
I've found that any activity delayed until late in the software lifecycle is a breeding ground for risk. Fail to execute performance tests frequently and it's likely that the team will experience performance problems with the application. Fail to deploy the application to an environment simulating production and it's likely that the team will experience deployment issues. Worse yet, failing to integrate the system frequently often results in integration issues.
Teams leveraging continuous integration prove the full lifecycle as frequently as possible. They develop, test, integrate, and deploy every time they build. The more frequently you perform all software lifecycle activities, the less likely it is that unforeseen high risk issues will surface unexpectedly. A sound continuous integration strategy can be leverage to help deal with many pain points throughout the software lifecycle because the project team always has a functional product at a given moment in time. Successful teams use that product to close the feedback loop to ensure that unexpected issues don't surface that can kill a project.