Reply to comment

Jul 10 13:42

Simplifying your test code

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:

class << self

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.

Reply

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. PHP source code can also be enclosed in <?php ... ?> or <% ... %>.

More information about formatting options