Do mocks create high coupling ?
Posted: October 10, 2011 Filed under: Mock Objects, Test Driven Design | Tags: Mock Objects, TDD 2 Comments »In a recent discussion with a colleague, he bought up the age old myth that mock objects are bad because they cause brittle tests and pointed me to this post. My view is if using mock objects are causing brittle tests it is a hint that there is a issue with the design of objects collaborating, which needs to be addressed. Mock object are a great tool that aid in the design of OO systems if you subscribe the “mystical view” of object oriented programming as described by these books RDD and GOOS . Ralph Johnson notes “The mystical view is that an OO system is one that is built out of objects that communicate by sending messages to each other, and computation is the messages flying from object to object.”
lets start by looking at the example in this post
module Codebreaker
describe Game do
describe "#start" do
it "sends a welcome message" do
output = double('output')
game = Game.new(output)
output.should_receive(:puts).with('Welcome to Codebreaker!')
game.start
end
it "prompts for the first guess"
end
end
end
with implementation code as show below.
module Codebreaker
class Game
def initialize(output)
@output = output
end
def start
@output.puts("Welcome to Codebreaker!")
end
end
end
While it very clear in the above example the implementation code simply duplicate the test code, and severely inhibit any re factoring with out breaking the test code. As the author of the blog post points out any of the following implementations are valid but yet they would break the test
@output.write("Welcome to Codebreaker!\n")
@output.print("Welcome to Codebreaker!\n")
"Welcome to Codebreaker!".split.each{|c| output.write(c)}; output.write "\n"
@output << "Welcome to Codebreaker!\n"
The key here is to “listen to the test”, there is duplication in test and implementation, the test is highlighting a serious flaw in the inter-object protocol. One of the things I picked up over the years in various discussions at London Xtreme Tuesday group is that an object should send messages to its collaborator in terms of its domain language. In the example above an instance of the Game object sends the message
:puts
to its collaborator. now :puts is a meaningless message in terms of the Game. With the mystical style of OO the domain model is in the message flying between the object.
One way to fix the above code is as following. start with an end-to-end test to verify the end result for a given scenario.
the unit test would be as follows
module Codebreaker
describe Game do
describe "#start" do
it "notifies game has started" do
gameEventListener= double('gameEventlistener')
game = Game.new(gameEventListener)
gameEventListener.should_receive(:new_game_started)
game.start
end
it "prompts for the first guess"
end
end
end
The key thing to note here is that the message :new_game_started is meaningful in terms of the Game objects domain. The role (gameEvenListener) of its collaborator is now made explicit in the test. We can now have an implementation of this role with write out the appropriate welcome message when it receives :new_game_started . While this is very simplistic example and may not seem like there is a huge impact on example, In larger systems I find well designed inter-object object protocols leads to simpler flexible system where behaviour can easily change by composing different objects.
In chapter 6 of the RDD book the authors describe different control styles in a system. I find that mock objects is a tool that guides our design to a “delegated control style” , one of the key characteristic of this style is
“Dialogs between objects are at higher-level.Collaborations between a coordinator and the objects it coordinates are typically higher-level requests for work and not simple requests to store or retrieve data. Communications are more abstract and more powerful. Instead of asking objects to do small chores or divulge small bits of information, coordinators typically delegate larger-grained requests.”
At the same time if we distribute responsibilities across too many different objects we have the following side effects “Complicated collaborations between delegator and delegates. This can happen when not enough context is passed along with a delegated request. Lots of collaborations but not much work getting done.”
When using mock object the test will be very sensitive to these issues and will highlight these facts by being tightly coupled to implementation details of the object or by awkward tests
To summarize the mains points are we
- need to understand the philosophies and ideas of OO to effectively use and appreciate mock objects
- Using mock object is a great feedback tool that can be used to design high level dialogues between objects, brittle tests is usually an indication that the object design needs to be reviewed.
- in an OO system the domain model is in fact in the messages sent between objects.
- Object must send messages to it peers in terms of its domain language.
great post! Thank you!
Good post.
The adverts at the bottom look a little dodgy though, I’m seeing big flashing green “Download” and “Play Now” buttons.