Think about how you refactor your code. You write the test, pass the test, then take a step back and see how you could improve things. It’s often not until you take that strategic view that you can decide whether you made the best decisions in the heat of battle. But, the beauty of the technique is you get that second look. Safely.
I was pairing a few days ago on my current project and my pair and I managed to work ourselves into a bit of a twist over a single test. The end result was a couple of headaches and over an hour and a half of time lost. But, it was the source of a lot of talking. One of the things Jeff (my pair) mentioned was something I’d never really thought of before, and a light clicked.
The way we refactor code is from the wrong side.
A key part of the power of Test Driven Development (and other such techniques) is that they force you to think about what you’re trying to do first, before you dive in and just start doing it.
Say you’re writing a data provider for a chart that should look at all the transactions for a client and plot percentages. Already you’ve made a statement about your code and what it’s interactions with some collaborating objects are. If you’re using mock objects to help write and design your code, chances are you’ll be writing expectations of how those dependent objects are to be used. The interface of your collaborating objects has been exposed through writing your test.
One suggestion in the 2004 paper, Mock Roles Not Objects (pdf), is that you should only mock types you own. It’s something I tend to forget, and will be trying harder not to in future. If you don’t, your test ends up being coupled to code you don’t own and an unrelated test could break should the external interface you depend on change. Instead, you write a thin wrapper that is defined closer to your need. This is one such use of mocks- proving you can integrate well with other code - but, more importantly, mocks free you into focusing on testing one thing at a time and specifying how you want to talk to other things.
All this is demonstration of a client interface-oriented approach.
As a developer sitting down to work, I’m interested in knowing how I can use other types for my own to get it’s job done. If I’m writing a chart data provider that plots the last 5 months worth of transactions, I’m not interested that I get a client or a market, I just want something that implements the interface I’ve defined through writing my test. I shouldn’t be using concrete classes from the domain, but rather interfaces for the things I’m interested in. The client interface. I don’t care about anything else.
The key thing of note is that it’s the client that determines the interface. It’s not about what I as a class do, but what others do with me.
However, of the refactoring tools and IDEs that I’ve used (shamedly small I know :), all of them are focused around the following steps:
- Extract an interface from the concrete class.
- Introduce selected members from the concrete class to the interface
- (Potentiall) replace dependent classes’ use of the concrete class with the interface.
Step 3 reveals the flaw. We’re saying that we’ll change the interface that was exposed to other classes, not that other classes ask us what they think of us.
In the previous example of a type that had been introduced as a result of writing some mock-based tests our need-driven interface has been changed from the wrong side. Our need-driven interface was evolved through writing how our type was going to be used. Only in such a need-driven situation are we able to understand the intent of the interface. Extracting an interface from the concrete class (the wrong side) results in us making decisions away from where the intention is. Bad.
Instead, imagine a situation where we have a concrete class that is collaborating with a number of dependent classes. Wouldn’t it be great if you could refactor from directly within that situation, when you’re working with the class under test and (most importantly) surrounded by intent. To have the refactoring tool show what you’re using on the classes and suggest you pull those into a client interface. You make the refactoring decision based on what your type needs from others, rather than what your other types can give you, and when you know what you’re actually using them for. When you understand what the object is.
If we’d had such a tool we could have spotted that we were mocking a third party library, and missing a valuable opportunity to see what we were truly dependent on, what we truly needed. We missed an opportunity to introduce a wrapper interface for what we actually needed. We weren’t really interested in a Market
or Client
to do our work, but rather just something that had Transaction
s. Our headaches would’ve been avoided!