Does TDD Harm Architecture?
Bob Martin, also known as Uncle Bob and co-author of the Agile Manifesto, has recently published an evaluation on whether TDD harms architecture. Most of the discussion centres around whether following a test driven approach has a negative impact on both the high level design and general maintainability of implementation code. Martin concludes that TDD is an important discipline, but it's the principles such as decoupling, separation and isolation which lead to good design.
One notion that Martin explores is Test Induced Design Damage, originally from David Heinemeier Hansson, creator of Ruby on Rails. Essentially it is a comparison between a design which Hansson prefers, and a more testable design which is produced by Jim Weirich, a Ruby contributor and speaker. Martin encourages the reader to conclude which design they prefer, and writes:
The argument, boils down to separation and indirection. DHH’s concept of good design minimizes these attributes, whereas Weirich’s maximizes them.
Martin also writes about the Fragile Testing Problem. This is where a minor refactoring of implementation code could break hundreds of dependant tests, forcing them to all be updated.
As Martin starts to convey his opinion, he first points out that just like production code, tests need to be designed. This is regardless of whether TDD has been followed or not:
Principles of design apply to tests just as much as they apply to regular code. Tests are part of the system; and they must be maintained to the same standards as any other part of the system.
Martin also explains that a common mistake in TDD, is to end up with a one-to-one correspondence between tests and implementation. This could mean there is a single test class per implementation class, and a single test method per implementation method. The main negative result of this is a tight coupling between tests and implementation, leading to code that is difficult to refactor and reason about.
In terms of higher level architecture and design, Martin believes that it does not emerge from TDD:
The idea that the high level design and architecture of a system emerge from TDD is, frankly, absurd. Before you begin to code any software project, you need to have some architectural vision in place. TDD will not, and can not, provide this vision.
However, Martin does believe that lower level design, closer to the code, does emerge from practicing TDD. In other words, whilst tests remain the same, the implementation code can be refactored and be structured into something that is more maintainable. Martin believes this leads up to two streams of evolution: "As the tests get more specific, the production code gets more generic."
Despite these differences, Martin's main thought is that TDD is a discipline. He concludes that it's up to the developer to produce a good design, regardless of whether they are doing TDD or not:
You see, it is not TDD that creates bad designs. It is not TDD that creates good designs. It’s you. TDD is a discipline. It’s a way to organize your work. It’s a way to ensure test coverage. It is a way to ensure appropriate generality in response to specificity.
By giving this summary, it means that in his view, the design produced by TDD is in reality a design produced by a developer. Whilst the main benefits of TDD are the test coverage and reliability of the application - it's the principles such as decoupling, separation and isolation that produce good design.
Testing is not trivial
Even if we put aside how tests and the production systems work together and look at tests in themselves, we will find complexity.
So far research has shown that creating tests might be an endeavour very similar to product development in scale/complexity/evolution.
Maybe it is not "just" TDD that is harming the architecture, but the way we think on testing.
Bursting the bubble of TDD and emergent design
Hopefully we are beginning to put TDD in it's true place as a useful technique some of the time but not the primary driver of development.
Re: Testing is not trivial
Re: Bursting the bubble of TDD and emergent design
Another I've run into is over-mocking. This leads to situations where the test ends up testing the implementation and not the behaviour. It makes refactoring a nightmare, and of course leads to badly designed production code.
I can relate to Martin's blog - design principles need to be thought about properly, and don't come for free with TDD.