May 11, 2014

...Learn TDD with Codemanship

When Really To Use Mocks? First Ask: "What Are Mocks?"

I should probably stay out of this, but just couldn't resist sharing my own thoughts about Uncle Bob's latest blog post on the subject of when to use mock objects.

He makes some fair points about isolation and using test doubles at architectural boundaries, as well as testing exceptional paths and making "random" things repeatable using the built-in features of many mocking frameworks that allow developers to deliberatly throw exceptions and return hardcoded dates and times and all that sort of thing.

But, and we need to be clear about this, this is the not intended purpose of mock objects, as far as I understand it.

Mocks exist to address a specific design need - not a testing need. The inventors of mock objects were answering the question: how do we test-drive the interactions between the key roles in our design?

How can I write a test that fails - a specification in test form - because a collaboration between two objects didn't take place in the way I wanted it to?

Enter stage right mock objects, and their - now legion - associated enabling frameworks, like JMock, Mockito, MockStockAndTwoSmokingBarrels, MockTheWeek, and, of course, Mockney Radio 1 DJ.

The idea's very simple; in thinking about object oriented design, we can organise our thoughts - expressed perhaps using simple modeling tools like index cards or UML diagrams - into three categories:

1. Roles - what roles do objects play in achieving a goal?

2. Responsibilities - what does each role do in achieving the goal?

3. (And here's the crux of good OO design) Collaborations - how do these roles collaborate - by sending messages to each other - to co-ordinate getting the job done?

Now, however you're driving the implementation of your design, in OO paradigms - indeed, in any paradigm where code is organised into modules that do chunks of work and invoke functions on other modules to do the rest - those 3 things form the basis of how code is organised.

But we can come at an implementation from different angles, all of which can overlap. I might choose to test-drive responsibilities using traditional assertion-based tests, and isolate that code from external systems or frameworks using stubs. I might choose to drive out key collaborations in my code using mock objects. I might sketch out an OO design and build the thing bottom up, I might start by test-driving the outermost objects and work my way in, using test doubles to defer the implementations further down the call stack. Tomato. Tomato. (That sounds funnier than it reads.)

You might just start by passing the tests in the simplest way possible and refactoring to an OO design, allowing roles, responsibilities and collaborations to emerge completely organically, with design decisions coming down purely to "what's the cleanest code that will pass these tests?"

Or you might plan the whole OO design up-front and just implement it.

In practice, these are two extremes: no up-front planning of our OO design requires very strong refactoring muscles. Not recommended for mere mortals like me and you.

Similarly, trying to think of everything up-front tends to take a very long time, and inevitably we discover as we get into the implementation details that the map is not the terrain. Hence, also not recommended for us mere mortals.

And so, we strike a balance. And that balance differs from person to person and from one team cultue to the next.

The same also goes for the balance between "classic TDD", which is mostly feedback-driven when it comes to OO design, but not completely, and the "London School", which relies on more up-front thought and planning about key roles (interfaces) and the collaborations between them than classic TDD.

The lines are necessarily blurred between these different approaches, just as the lines are blurred between mocking and stubbing in many popular mock object frameworks.

At the risk of giving away pithy and meaningless advice, you should use what's appropriate. But it helps to be clear in your mind what question your test is asking. Are you asking whether a calculation was done correctly, perhaps using hardcoded data returned by a stub pretending to be the database, or are you asking if the database request was built and sent correctly, regardless of the response?

You should also be very careful not to fall back on mock objects as a crutch for making code that's untestable because of dependencies more testable. For that way lies the madness of baking in a bad design. Yes, you can get more tests in there, but those tests will likely as not expose the internal interactions of your legacy code, making them doubly difficult to refactor.

Ultimately, remember this: mock obects - test doubles used to test interactions - are supposed to be an OO design tool that enables us to drive out the collaborations in our architecture by writing failing tests. Their purpose is not specifically about isolating code to make it more testable. Indeed, testability is not the point of mock objects - it's a nice side-effect of doing code-level design with them.

Your own comfort-level - your tolerance for mocks, if you like - will probably be different to mine. Mine is quite low. I like to mosty discover designs, but undoubtedly will test-drive collaborations at system boundaries, as Uncle Bob recommends.

But it's not my way or the highway on this matter. If your OO designs are effectively modular, with cohesive and loosely-coupled components that perform a specific job and have few collaborations, mocks won't hurt you and can be an effective TDD aid. That's their original purpose, after all. TDD-ing code in the Tell, Don't Ask style is undoubtedy easier with mocks.

We have no reason to doubt that well-known exponents of the London School of TDD are successful in their approach. Even though we see many teams fall foul of "mock abuse", relying on mocking frameworks to make bad designs testable.

Likewise, we have no reason to doubt that key exponents of classic TDD are not equally successful in the designs their more feedback-driven approach reaps. Even though we see many teams fall foul of both too little up-front planning, and too little after-the-fact refactoring to keep the code clean.

So, when to use mocks? When it makes sense






Posted 3 years, 8 months ago on May 11, 2014