April 02, 2007
DRY RSpec by generating specifications in a loop
Occasionally, when doing Behavior Driven Development with RSpec, I'll need to specify a number of cases where the internal structure of the spec is identical. Specifying a simple function, for example, might lead to code like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
context "My one adder" do setup do @processor = Adder.new(1) end specify "should add 1 and get 2" do @processor.add(1).should == 2 end specify "should add -1 and get 0" do @processor.add(-1).should == 0 end specify "should add 0 and get 1" do @processor.add(0).should == 1 end end |
Given the information being conveyed, this is very verbose and also not DRY. (When writing specs, it's important to note that DRY is not the #1 priority—that's clarity—but overly "wet" code can still increase the chance of bugs.)
One way to reduce the length of the spec is to condense the expecations into a single specification block:
1 2 3 4 5 6 7 8 9 10 11 |
context "My one adder" do setup do @processor = Adder.new(1) end specify "should add numbers correctly" do @processor.add(1).should == 2 @processor.add(-1).should == 0 @processor.add(0).should == 1 end end |
This introduces two disadvantages. First, a failure on any expectation in the spec will cease execution of the specify block, and thus all further failures will be masked. Second, the specdoc produced by the sample is not as clear in conveying the expectations for the object.
Thanks to a quick tip from David Chelimsky, it's easy to have my cake and eat it too:
1 2 3 4 5 6 7 8 9 10 11 12 |
context "My one adder" do setup do @processor = Adder.new(1) end expected_results = {-1 => 2, -1 => 0, 0 => 1} expected_results.each do |input, result| specify "should add #{input} and get #{result}" do @processor.add(input).should == result end end end |
This produces the ideal specdoc, is DRY, and will not allow one failure to mask another during a spec run.