This was an excellent talk Ian Cooper gave at NDC a couple years ago.
It hits on a lot of the challenges and common problems Ian and others have seen with test suites and TDD in general.
It’s a great talk. Which you can watch in it’s entirety here.
Here are some notes.
Where did it all go wrong
Two keep points
Not writing tests against behaviors.
Coupling our implementation details to our tests.
If we fix these we will have smaller test suites, much more self explaining, and much less painful to own.
There was advice in early TDD that said when you are given a new method on a class, that was the trigger point for writing a new test.
And that’s really wrong.
The trigger for writing a new test is to have some piece of behavior that I want to implement. The test needs to capture that behavior.
The reason why when you go back to your tests that you find them so hard to read is there is a lost connection between the low level test you are writing and the high-level feature you are trying to solve. It’s lost in the noise.
What you need to do is express in your test that we are testing a given behavior of our system.
Before you get to the point where you are going to put the implementation details down, you first put down the behaviours you are trying to capture, and then the implementation. Don’t jump ahead too early.
Testing outside in
Start with the use case, the story, the scenario or feature we want to solve, on the outside, then work your way in from there.
Recommendation: don’t start this at the UI level. Start one level beneath at the plain old object level. Then if your UI changes, it won’t matter. Focus on the domain models.
Test the public API of classes. Not the internals. Test the surface only, and it should be quite narrow.
As soon as you start testing the internals you are coupling your tests to your implementation details. To change your implementation details you now have to change your tests. And that’s the key problem.
So your surface area should be much narrowly than many people are testing today. Just the API.
That means you will write less tests, tests against the use cases, and refactoring the contract remains the same, the internals change, and you don’t break any tests.
This is what BDD is all about. Testing behavior, and not low level details, and methods on classes.
What is a unit test
The simplest thing
The real problem with TDD is Kent asked us to go green as fast as possible, committing as many sins as we want in that step. So go to project, cut and paste some code, and stick in application. Hack it. Make it work. Don’t worry about the design. Get it green ASAP.
Kent’s point here is that it’s difficult to do two things at once. Make the test pass and design at the same time. Better to separate out.
Green is about solving the problem. Refactoring is about doing the design.
You do not write new tests here when refactoring. You already have the high-level behavior tests written. So long as those pass, you are OK. If you need another test, write it at that level.
But when you are refactoring, you are free to focus on design and engineer it right.
If you write tests here you bind your implementation details to your tests.
Coupling is the biggest problem in software. It is the enemy. Forget DRY. Coupling will kill you. Do you couple tests to implementation details.
Dependency is the key problem in software development at all scales.
We need to eliminate the dependency between our tests and our code. Tests should not depend on details. Tests should depend on contracts or public interfaces. This allows us to change implementations without changes tests.
Test behaviors not implementations.
Means less tests.
Going quickly to green.
Now you are refactoring and making it cleaner.
Better forward progress, the tests aren’t slowing you down so much.