- Test-Driving Randomness
- Overtesting
- Inhibiting Refactoring
- A Solution
Inhibiting Refactoring
The third technique I described earlier, where we inject a Random object with specific, known behavior and state into the deck, could be considered a mock technique. We would be replacing the production random behavior with mock behavior, which would allow us to code a unit test. This would be a state-based test; it would verify that the appropriate card was dealt with each request sent to the Deck object.
The problem I have with the mocking techniques—either the interaction-based mock or the state-based mock—is that they both violate encapsulation. The test becomes acutely aware of the fact that Deck needs a Random instance in order to do its work.
Violating encapsulation with respect to a unit test might not seem like a big deal. In fact, it probably isn’t, at least not in this case. However, it is fundamentally a step in a risky direction. Let’s say tomorrow we found a better randomizer class, a better pseudorandom number generator. Incorporating this new class would by definition break the test—minimally, we would need to recompile. More likely, the interface to the new class would be different. We would not only need to change the production code, but modify code in the test.
On such a small scale, it’s okay to take this risk. If you keep your use of mock objects to a minimum, the impact of similar changes should be small. If you’re not careful, though, you might find that a small change to your production code breaks a number of tests.
Remember that one key to making TDD work is keeping the code as clean as possible at all times—with the introduction of each new bit of test and production code. The TDD cycle is to write a bit of test, write a bit of code to make that test pass, and then refactor in order to clean up any design deficiencies you might have just introduced. If you’re reluctant to refactor because your mock tests might break, you’ve allowed your code to degrade just a little bit more.
As far as I’m concerned, anything that inhibits my ability to refactor at will is something to be regarded with high levels of suspicion. I do use mocking techniques in my code, but only when I have no other choice.
There is a distinct TDD sub-community that actively promotes heavy use of mocking. From my standpoint, this community suggests what I consider an almost entirely different TDD approach, based on their mindset, the technique, and the goals. The Fowler article on mocks can provide you with more information if you find this avenue appealing.