Mocks, the removal of test detail, and dynamically-typed languages
Simplify, simplify, simplify!
– Henry David Thoreau
(A billboard I saw once.)
Part 1: Mocking as a way of removing words
One of the benefits of mocks is that tests don’t have to build up complicated object structures that have nothing essential to do with the purpose of a test. For example, I have an entry point to a webapp that looks like this:
get ‘/json/animals_that_can_be_taken_out_of_service‘, :date => ‘2009-01-01‘
It is to return a JSON version of something like this:
{ ‘unused animals‘ => [’jake‘] }
Jake can be taken out of service on Jan 1, 2009 because he is not reserved for that day or any following day.
In typical object-oriented fashion, the controller doesn’t do much except ask something else to do something. The code will look something like this:
get ‘/json/animals_that_can_be_taken_out_of_service‘ do # Tell the “timeslice” we are concerned with the date given. # Ask the timeslice: What animals can be reserved on/after that date? # (That excludes the animals already taken out of service.) # Those animals fall into two categories: # - some have reservations after the timeslice date. # - some do not. # Ask the timeslice to create the two categories. # Return the list of animals without reservations. # Those are the ones that can be taken out of service as of the given date. end
If I were testing this without mocks, I’d be obliged to arrange things so that there would be examples of each of the categories. Here’s the creation of a minimal such structure:
jake = Animal.random(:name => ‘jake‘) brooke = Animal.random(:name => ‘brooke‘) Reservation.random(:date => Date.new(2009, 1, 1)) do use brooke use Procedure.random end
The random
methods save a good deal of setup by defaulting unmentioned parameters and by hiding the fact that Reservations
have_many Groups
, Groups
have_many Uses
, and each Use
has an Animal
and a Procedure
. But they still distract the eye with irrelevant information. For example, the controller method we’re writing really cares nothing for the existence of Reservations
or Procedures
–but the test has to mention them. That sort of thing makes tests harder to read and more fragile.
In constrast to this style of TDD, mocking lets the test ignore everything that the code can. Here’s a mock test for this controller method:
should “return a list of animals with no pending reservations“ do brooke = Animal.random(:name => ‘brooke‘) jake = Animal.random(:name => ‘jake‘) during { get ‘/json/animals_that_can_be_taken_out_of_service‘, :date => ‘2009-01-01‘ }.behold! { @timeslice.should_receive(:move_to).once.with(Date.new(2009,1,1)) @timeslice.should_receive(:animals_that_can_be_reserved).once. and_return([brooke, jake]) @timeslice.should_receive(:hashes_from_animals_to_pending_dates).once. with([brooke, jake]). and_return([{brooke => [Date.new(2009,1,1), Date.new(2010,1,1)]}, {jake => []}]) } assert_json_response assert_jsonification_of(’unused animals‘ => [’jake‘]) end
There are no Reservations
and no Procedures
and no code-discussions of irrelevant connections amongst objects. The test is more terse and–I think–more understandable (once you understand my weird conventions and allow for my inability to choose good method names). That’s an advantage of mocks.
Part 2: Dynamic languages let you remove even more irrelevant detail
But I’m starting to think we can actually go a little further in languages like Ruby and Objective-J. I’ll use different code to show that.
When the client side of this app receives the list of animals that can be removed from service, it uses that to populate the GUI. The user chooses some animals and clicks a button. Various code ensues. Eventually, a PersistentStore
object spawns off a Future
that asynchronously sends a POST request and deals with the response. It does that by coordinating with two objects: one that knows about converting from the lingo of the program (model objects and so forth) into HTTP/JSON, and a FutureMaker
that makes an appropriate future. The real code and its test are written in Objective-J, but here’s a version in Ruby:
should “coordinate taking animals out of service“ do during { @sut.remove_from_service(”some animals“, “an effective date“) }.behold! { @http_maker.should_receive(:take_animals_out_of_service_route).at_least.once. and_return: “some route“ @http_maker.should_receive(:POST_content_from).once. with(:date => ‘an effective date‘, :animals => “some animals“). and_return(’post content‘) @future_maker.should_receive(:spawn_POST).once. with(’some route‘, ‘post content‘) } end
I’ve done something sneaky here. In real life, remove_from_service
will take actual Animal
objects. In Objective-J, they’d be created like this:
betsy = [[Animal alloc] initWithName: ‘betsy‘ kind: ‘cow‘];
But facts about Animals–that, say, they have names and kinds–are irrelevant to the purpose of this method. All it does is hand an incoming list of them to a converter method. So–in such a case–why not use strings that describe the arguments instead of the arguments themselves?
@sut.remove_from_service(”some animals“, “an effective date“)
In Java, type safety rarely lets you do that, but why let the legacy of Java affect us in languages like Ruby?
Now, I’m not sure how often these descriptive arguments are a good idea. One could argue that integration errors are a danger with mocks anyway, and that not using real examples of what flows between objects only increases that danger. Or that the increase in clarity for some is outweighed by a decrease for others: if you don’t understand what’s meant by the strings, there’s nothing (like looking at how test data was constructed) to help you. I haven’t found either of those to be a problem yet, but it is my own code after all.
(I will note that I do add some type hints. For example, I’m increasingly likely to write this:
@sut.remove_from_service([”some animals“], “an effective date“)
I’ve put “some animals” in brackets to emphasize that the argument is an array.)
If you’ve done something similar to this, let’s talk about it at a conference sometime. In the next few months, I’ll be at Speakerconf, the Scandinavian Developer Conference, Philly Emerging Tech, an Agile Day in Costa Rica, and possibly Scottish Ruby Conference.
January 14th, 2010 at 7:09 am
[…] Mocks, the removal of test detail, and dynamically-typed languages (Brian Marick) […]
January 23rd, 2010 at 7:14 pm
[…] It’s really interesting to read about the way that others are trying to write better tests and Brian Marick also has a post where he describes how he is able to create even more intention reveali…. […]