Have you ever worked on a system where the tests slow you down?
Do the tests break even with the smallest change?
Are the tests brittle?
Is it really hard to write a test? Lots of setup, mocks and expectations?
I’ve worked on these and I’m pretty sure I’m not alone.
So what’s the problem with TDD?
To understand the problem we have to understand feedback loops.
The use of feedback loops is crucial in engineering; the components in a system provide feedback that reinforces or reduces change.
There are two types of feedback loops; positive and negative.
Negative feedback loops move systems towards stability.
Negative feedback loops act as a damper to, or prohibits, change.
An example of a negative feedback loop in engineering is the centrifugal governor. This limits the speed of the engine by limiting fuel supply.
Positive feedback loops moves systems towards instability.
Positive feedback loops reinforce change. A change reinforces itself and creates more of the same change.
An oscillator is an example of a positive feedback loop.
Tests as a feedback loop
In much the same way, unit tests are an example of a negative feedback loop. When one breaks we stop and fix it. It is enforcing stability.
However, tests are also providing feedback when you are writing them. If it’s hard to write a test, your design is likely sub-optimal for the change you are trying to make.
For example, if there are lots of dependencies to mock and expectations to set up, we may have missed a needed abstraction that combines them.
The longer we put up with tests that are getting harder to write, the more our software degrades.
Designing feedback loops
We have to be careful how we design our feedback loops. If our tests are too fine grained our feedback loop will be too tight, limiting the changes we can make to our system. This will reduce our ability to change our software.
We need the feedback to ensure we haven’t broken anything while at the same time we want to be able to change the implementation details of our software.
Our tests are feedback loops, telling us, not only when changes are wrong, but also when our changes cannot easily be supported in the design.
If we ignore our tests it will become harder to work within our system over time.
We need to be careful how fine grained we make our tests; too fine and we won’t be able to change our software. Not fine enough and we will inadvertently break some behaviour of our software.