It seems to be a popular misconception that the motivation for writing unit tests is the short term reduction of bugs in new code. This limited view makes it easy for developers to justify to themselves laziness in not writing properly tested code. What it ignores are the primary benefits and justifications for unit testing, what I call the "Why" of unit testing.

In my experience there are two primary and significant benefits to writing comprehensive unit tests: Design and Maintainability. These attributes are closely related in that good design supports maintainability and maintainability allows the design to be kept effective over time.

The Test Driven Development (TDD) practice of leading development with unit testing is fundamentally a design process. Writing unit tests closely in conjunction with the code under development provides immediate feedback which goes into improving the design of the code. Developers are working with their code in an immediate fashion that other styles of development do not provide. Issues with the code from the perspective of its clients are detected more quickly and therefore can be resolved faster and with significantly less effort (and hence cost)

Code developed using TDD will also tend to be better factored and easier to work with. The need to be able to effectively test the code drives important design principals such as separation of concerns that support the ability of code elements to be tested in isolation. Testability here provides incentives to producing good design, a rare win-win in a world of software engineering tradeoffs.

Maintainability, or the ability to support change in the codebase, is the key determinant of the cost of a software system over its lifetime. Systems that are highly maintainable can have defects resolved more quickly and cheaply and may be more effectively extended to include new functionality or adapt to new conditions. A comprehensive unit test suite is a critical element of ensuring the maintainability of a system.

Every change to a system is a risk. Even resolving an obvious defect may actually break other code that relies upon the defective behaviour. Adding new functionality will almost always require alteration or extension of at least some existing code, introducing the risk of defects being introduced into existing code that may not seem at all related to the new features.

Unit testing provides the first line of defence against the introduction of defects into existing code. It is therefore a risk mitigation strategy to make it safer to work with an existing codebase. A developer working on a codebase with solid unit tests can have significantly higher confidence that their changes are benign than if no tests are available (the problem of overconfident developers who underestimate their potential to do damage is left as an exercise for the reader).

Introducing new features often requires altering the design of the application to support the new functionality. This involves refactoring the codebase to meet the new design. The presence of solid unit tests are a necessity to be able to make the design changes with confidence (see Refactoring: Improving the Design of Existing Code by Martin Fowler).

Good design derived from TDD also supports maintainability through reduced coupling and improved cohesion. This isolates the codebase as a whole from the effects of modifications in specific areas. Additionally good design will tend to produce less code overall. Good design will remove duplication which is a leading cause of maintenance costs and introduced defects. A good design will also fit the problem domain more closely resulting in more natural and hence more succinct expression of the solution.

In a future post I shall examine some of the arguments I've heard against writing unit tests and discuss why they are fundamentally flawed.