The so-called writings of Michael Gorsuch.

Diving into RSpec

For Christmas, my wife bought me a copy of The RSpec Book by The Pragmatic Programmers. It has changed my entire approach to development.

Over the past several years, I’ve grown apart from TDD. I began using Test::Unit with my early Rails projects, but always found that things became too brittle. My tests would often dive way too deep into the implementation of my classes and then break when I would refactor. After much frustration, I would eventually abandon automated testing altogether. I highly doubt the fault lies with the library — I didn’t really grasp the philosophy and therefore sucked when trying to apply it.

Everything changed with The RSpec Book. I now have a better grasp (maybe): focus on defining and testing behavior rather than the internals. After working through all the examples in the first five chapters this evening, I decided to apply RSpec to Imran’s FizzBuzz problem:

Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.

If we decide that a resonable way to solve this is to create a class named FizzBuzz and a calculate method to do the work, we can start out with a bare spec:

describe FizzBuzz, "#calculate" do
  it "returns 'Fizz' for all multiples of 3" 
  it "returns 'Buzz' for all multiples of 5"
  it "returns 'FizzBuzz' for all multiples of 3 and 5" 
  it "returns the passed number if not a multiple of 3 or 5"
end

This is all very readable, and encapsulates the problem quite well. I can marvel at this for hours…

And when I’m done, I can type rspec <filename> and see something like this:

Pending:
  FizzBuzz#calculate returns 'Fizz' for all multiples of 3
    # Not Yet Implemented
    # ./fizzbuzz_spec.rb:16
  FizzBuzz#calculate returns 'Buzz' for all multiples of 5
    # Not Yet Implemented
    # ./fizzbuzz_spec.rb:17
  FizzBuzz#calculate returns 'FizzBuzz' for all multiples of 3 and 5
    # Not Yet Implemented
    # ./fizzbuzz_spec.rb:18
  FizzBuzz#calculate returns the passed number if not a multiple of 3 or 5
    # Not Yet Implemented
    # ./fizzbuzz_spec.rb:19

Finished in 0.00061 seconds
4 examples, 0 failures, 4 pending

Amazingly clear, right? I have four examples, and all of them are pending further definition. So I don’t bother you with all of the boring details, here is the complete implementation. Since this is so simple of a problem, I decided to combine the spec and class definition in the same file:

class FizzBuzz
  def calculate(n)
    if n % 3 == 0 and n % 5 == 0
      "FizzBuzz"
    elsif n % 3 == 0
      "Fizz"
    elsif n % 5 == 0
      "Buzz"
    else
      n
    end
  end
end

describe FizzBuzz, "#calculate" do
  let(:fb) { FizzBuzz.new }
  
  it "returns 'Fizz' for all multiples of 3" do
    fb.calculate(3).should == "Fizz"
  end
  it "returns 'Buzz' for all multiples of 5" do
    fb.calculate(5).should == "Buzz"
  end
  it "returns 'FizzBuzz' for all multiples of 3 and 5" do
    fb.calculate(15).should == "FizzBuzz"
  end
  it "returns the passed number if not a multiple of 3 or 5" do
    fb.calculate(77).should == 77
  end
end

When running this completed spec, we see that all is good:

....

Finished in 0.00162 seconds
4 examples, 0 failures

Awesome, right? Learn more about RSpec here.