I’ve long felt that the use of a lot of mock objects and other test doubles was a sign of a design smell. I would occasionally hand-build a test double for the rare case when I couldn’t find a better way, but I’d always look for a different option.
More recently, I’ve read Growing Object-Oriented Software, Guided By Tests by Steve Freeman and Nat Pryce, and Practical Object-Oriented Design in Ruby: An Agile Primer by Sandi Metz. Both books are excellent, and I highly recommend reading them. Both books also advocate the use of mocks and other doubles, and both make a compelling case. So, I’ve decided to re-evaluate my position and apply some of the techniques I’ve learned to some of the code I write.
In particular, I’ve been trying to use Sandi’s “rules” for testing, roughly paraphrased as:
- Test incoming messages for state.
- Don’t test outgoing query messages; use a stub to return any values needed to make the tests pass.
- Test outgoing command messages using mocks.
- Don’t test messages sent to self (a.k.a. private messages).
I’ve been using these rules to good effect in some code I was developing. I’m definitely encouraged to keep applying them and learning from them. However, I ran into one case where I think this approach went a little too far.
When learning a new idea or technique, I often find it helpful to over-use it for a brief time. It’s hard to know where the line is if I don’t cross it, and I can often push a new technique much further than I originally expected.
I was developing a Smalltalk interface to a piece of hardware. This hardware exposes a number of registers. Control registers are used to ask the hardware to do something, or to set a value that the hardware will use. The hardware reports information back to the software in status registers.
Sometimes, these registers are paired such that a request to a control register is acknowledged by a response in a corresponding status register. The typical pattern with these paired registers is to make a “synchronous call” to the hardware. That is, to make a request and wait for the hardware to respond in the status register before proceeding. There needs to be a timeout to keep the software from blocking indefinitely waiting for a response that never comes.
Because this is a common pattern, I decided to encapsulate it in a
RegisterPair. I’d really like a better name, but no one on
our team has come up with one yet. My best option so far is
Controlinator, but that comes from watching too much Phineas and
Ferb with my kids, and doesn’t really communicate the intent of the
I made use of
StatusRegister classes I’d
already developed. These classes are implemented as valuables; that
is, they make use of the common Smalltalk idiom of using
set a value, and
#value to get a value.
Given those classes, I test-drove an implementation of
using mocks and stubs. In our code, we use SUnitToo and a
locally-customized version of the SmallMock package from the
Cincom Public Store Repository.
I ended up with the following tests:
When I was finished, I looked at the tests. It seemed like there was too much noise, and they weren’t as clear as I liked. There is certainly some duplication that could be extracted. That might help, but it might also make the tests less understandable. I could also do some additional work on SmallMock to make it less verbose. Having played with some Ruby mocking libraries lately, I have some ideas on how to go about that, but I didn’t want to shave that yak here.
So, I took another run at it, this time using the existing
ObservedValue as a duck-typed stand-in for the registers. Since
ObservedValue is also a valuable, this works pretty well. I kept
one of the mocks in
#waitsForStatusRegisterToChange, where the value
returned by the status register has to change in the middle of the
Here are the new tests:
I’m happier with this latest version. I’m still using test doubles; they just happen to be instances of an existing class in the system that shares the interface I need.
Since I wrote this code, I’ve done some work to make our mocking library less verbose. I have more to do, but I’m finding that I like the tests better when less of their surface area is taken up by the mock code.
I expect I’ll have more to say on this topic as I gain more experience with it.