Robby, one of the developers I worked along side at BlueSpire, used a pattern in our test suite to simplify the setup of objects under test. I thought it very useful and it made the code so much more readable.
The idea is, when setting up an object n different ways, it's nice to set only the parameters you need on the same line as the instantiation.
The other day, while working on Canadian Hair Forecast code, I was writing a unit test for a class that translates numerical weather data into plain English. For example, if the cloud coverage is 75%, the wind 8 knots, and the rain chance 60%, it translates this to "Mostly Cloudy Skies, Breezy, Good Chance of Rain".
While writing the unit test for my "weather translator" class, it occurred to me that I need to set the object up in many combinations to test each type of weather conditions. This could be quite tedious. But then I remembered Robby's simple pattern.
The pattern consisted of writing a builder class, that builds the object with the properties you need for each test. Robby's pattern allowed you to chain methods together to set up each property, like:
@weather = Weatherbuilder.withprecip(30).withtemp(20).build
The builder class sets up the object in test with default values, but you can override them just by tacking on some methods.
So my builder class looks like this:
class Weatherbuilder
@temp = 30
@clouds = 10
@wind = 2
@precip = 10
class << self
def withtemp(temp)
@temp = temp
self
end
def withclouds(clouds)
@clouds = clouds
self
end
def withwind(wind)
@wind = wind
self
end
def withprecip(precip)
@precip = precip
self
end
def build
CanadaWeatherTranslator.new(@temp,@clouds,@wind,@precip)
end
end
end
And here's just a few examples of some test methods that make use of the builder:
def test_good_chance_of_rain
@weather = Weatherbuilder.withprecip(60).withtemp(20).build
assert_match(/Good Chance of Rain/, @weather.text('precip'))
end
def test_slight_chance_of_snow
@weather = Weatherbuilder.withprecip(30).withtemp(-5).build
assert_match(/Slight Chance of Snow/, @weather.text('precip'))
end
In case you are wondering about this line:
That is one way to make a singleton class in Ruby. Otherwise I would end up having to use the .new instantiation method at the beginning of my method chains.
In light of the fact the Ruby supports passing parameters as hash tables, it is arguable that it may not be "Ruby like" to have a pattern like this with chained methods. Had I used a parameter hash, my builder would look more like:
@weather = Weatherbuilder.build(precip => 60, temp => -5)
Which, I think, will still be just as readable if I ever re-visit the code at a later date. It just boils down to your favorite way to handle a variable number of parameters.