“Seriously though,” one spectator commented, “no one really does any of this AgileDocs stuff. Who actually reads the names of tests to find out what the code ought to do?”
On a project in which the tests consistently describe the behaviour of the code, the answer is: everybody.
“Okay. But why is it always so hard to write good tests?”
1. Your tests are not actually describing the behaviour of your code
I frequently see tests which describe the behaviour of a dependency too. A dependency is anything which is used by the thing whose behaviour you’re trying to describe. eg:
The driver should stop at the zebra crossing.
The driver should press the brake pedal at the zebra crossing.
The car should stop when the brake pedal is pressed.
2. Your code is wrong.
This happens a lot to me if I’m pinning down legacy code, and I’ve assumed that the method called
getTheOneRing() does actually do what it says it does. If I find I’m getting a copy back instead, there’s really no point checking that it’ll melt in the fires of Mount Doom.
3. Your code has too many responsibilities.
It’s probably doing something better done in another class; think about introducing another dependency (like a
4. You’re trying to mock too many classes.
See 2 and 3.
Don’t mock domain objects. Your code should interact gracefully with them, and be able to rely on their behaviour. Mocking domain objects leads to fifteen expectations per test, and gives you no better certainty that your code is behaving itself.
Don’t mock data structures (which are frequently domain objects anyway).
Split dependency classes which consist of mixed data structures and methods to manipulate the data. That way you only need to mock the manipulations. (As an example, consider the separation of Pattern Matching and Strings). Or, even better…
Use role-based interfaces to limit the scope of dependencies. Your code should perform according to its responsibilities; it should depend on roles which help it to perform those responsibilities. If a driver’s only responsibility were to
pressTheBrakePedal(), then a driver would only need a
Pedal, not a whole
Car or even a
Hydraulic Braking System.
A pattern I use sometimes
If I have a
Driver that needs a
Pedal, I might define it as a public inner interface of the
Adapting from a
Car.Brake to a
Driver.Pedal turns out to be easy, for most instances of Cars, Drivers, Brakes and Pedals – and if, later, you want to put in that super duper new parachute breaking system for jet-powered vehicles, you can do so and adapt to the Driver’s pedal requirements without changing the behaviour of the Driver.