Max Woghiren

This boy's life among the electrical lights.

An Introduction to RSpec

with 8 comments

RSpec is a testing framework for Ruby based on the notion of behavior-driven development. It’s designed to allow unit tests to be easily written in terms of behavior, and provides simple, intuitive documentation for the entities being tested. It’s a valuable tool that makes test- and behavior-driven development enjoyable and straightforward. Let’s check it out.

Reverse Polish Notation

Suppose we want to write a calculator. The calculator will operate using Reverse Polish notation. In Reverse Polish notation, operations come after operands; for example, 3 + 4 becomes 3 4 +. A calculator using this notation maintains a stack of numbers, and whenever an operation is entered, we pop the top two numbers from the stack, perform the operation, and push the result back onto the stack.

Here’s an example; our stack is represented in square brackets with the top of the stack on the right.

> 4
[ 4 ]
> 2
[ 4 2 ]
> +
[ 6 ]
> 2
[ 6 2 ]
> 1
[ 6 2 1 ]
> +
[ 6 3 ]
> *
[ 18 ]

Introducing RSpec

Let’s suppose we’re starting off with an empty RPCalculator class. We’re going for pure TDD here.

First, we’ll sketch some tests of basic functionality using the basic building blocks of RSpec tests: describe and it.

describe is used to express the entity being tested—normally a class or method.

describe RPCalculator do

end

You can nest describe blocks to specify more specific things being tested.

describe RPCalculator do
  describe "#enter_number" do

  end
end

The next step is to determine what behavior you’re expecting for your cases. This is the job of the it block. it blocks answer the question: “What are you expecting the thing you’ve described to do?”

describe RPCalculator do
  describe "#enter_number" do
    it "puts the entered number on top of the stack" do

    end
  end
end

Let’s run it!

$ rspec rpcalculator_spec.rb
.

Finished in 0.00035 seconds
1 example, 0 failures

Excellent—our test passes. It isn’t checking anything though, so let’s fill in the blank.

describe RPCalculator do
  describe "#enter_number" do
    it "puts the entered number on top of the stack" do
      calculator = RPCalculator.new
      calculator.enter_number(4)
      calculator.stack.should eq([4])
    end
  end
end

Note the assertion syntax: We use a should method. It’s fairly intuitive, and there are many alternatives to eq(), such as be_nil, be_true. These are called matchers.

We’re clearly going to need to provide the #enter_number method and a #stack method. We haven’t yet, though, so…

$ rspec rpcalculator_spec.rb
F

Failures:

  1) RPCalculator#enter_number puts the entered number on top of the stack
     Failure/Error: calculator.enter_number(4)
     NoMethodError:
       undefined method `enter_number' for #<RPCalculator:0x007fef1b323950>
     # ./rpcalculator_spec.rb:7:in `block (3 levels) in <top (required)>'

Finished in 0.00044 seconds
1 example, 1 failure

Failed examples:

rspec ./rpcalculator_spec.rb:5 # RPCalculator#enter_number puts the entered number on top of the stack

…our test fails, as expected. Now we can implement #enter_number and #stack. When we do…

$ rspec rpcalculator_spec.rb
.

Finished in 0.00045 seconds
1 example, 0 failures

…success! We can continue this process iteratively, but this is only the beginning. RSpec has many more features to make this process simpler and even more useful.

Multiple Expectations

Each describe block can have multiple it blocks (representing individual expectations). For instance, when we add two numbers, we expect that stack is updated (with the topmost two numbers replaced with their sum) and their sum is returned.

describe RPCalculator do
  describe "#enter_number" do
    it "puts the entered number on top of the stack" do
      calculator = RPCalculator.new
      calculator.enter_number(4)
      calculator.stack.should eq([4])
    end
  end

  describe "#add" do
    it "pops the stack's two topmost numbers and pushes their sum" do
      calculator = RPCalculator.new
      calculator.enter_number(4)
      calculator.enter_number(2)
      calculator.add
      calculator.stack.should eq([6])
    end

    it "sets the last result to the sum of the stack's two topmost numbers" do
      calculator = RPCalculator.new
      calculator.enter_number(4)
      calculator.enter_number(2)
      calculator.add
      calculator.last_result.should eq(6)
    end
  end
end

Note the amount of repeated code. In each test, we’re doing a lot of redundant setup; namely, initializing our test calculator and, in the #add case, entering our two numbers and adding them.

Here, the before block comes to the rescue. before :each will allow us to set things up before each test inside that same block. before :all, as you can guess, will set things up once before all tests inside the same block.

describe RPCalculator do
  before :each do
    @calculator = RPCalculator.new
  end

  describe "#enter_number" do
    before :each do
      @calculator.enter_number(4)
    end

    it "puts the entered number on top of the stack" do
      @calculator.stack.should eq([4])
    end
  end

  describe "#add" do
    before :each do
      @calculator.enter_number(4)
      @calculator.enter_number(2)
      @calculator.add
    end

    it "pops the stack's two topmost numbers and pushes their sum" do
      @calculator.stack.should eq([6])
    end

    it "sets the last result the sum of the stack's two topmost numbers" do
      @calculator.last_result.should eq(6)
    end
  end
end

Now, each it block simply tests the expectation, and readability has increased significantly. We can now implement #add and #last_result.

Multiple Scenarios

So far, we’ve only tested cases with no issues; for completeness, we’d want to set up scenarios where circumstances might be different. This is where context blocks come in. context blocks are functionally identical to describe blocks, but are typically used to set up different scenarios within a describe block.

When testing #add, then, we might have two contexts: one where there are at least two numbers on the stack and one where there’re fewer than two. In the former case, things should work just fine, but in the latter case, we have different behavior.

describe RPCalculator do
  before :each do
    @calculator = RPCalculator.new
  end

  describe "#enter_number" do
    before :each do
      @calculator.enter_number(4)
    end

    it "puts the entered number on top of the stack" do
      @calculator.stack.should eq([4])
    end
  end

  describe "#add" do
    context "when at least two numbers are on the stack" do
      before :each do
        @calculator.enter_number(4)
        @calculator.enter_number(2)
        @calculator.add
      end

      it "pops the stack's two topmost numbers and pushes their sum" do
        @calculator.stack.should eq([6])
      end

      it "sets the last result to the sum of the stack's two topmost numbers" do
        @calculator.last_result.should eq(6)
      end
    end

    context "when fewer than two numbers are on the stack" do
      before :each do
        @calculator.enter_number(4)
        @calculator.add
      end

      it "leaves the stack untouched" do
        @calculator.stack.should eq([4])
      end

      it "sets the last result to nil" do
        @calculator.last_result.should be_nil
      end
    end
  end
end

Once we amend #add to fit our specifications, we can run our tests and, hopefully, have no failures. Furthermore—and this is huge—we can use the following command to generate documentation:

$ rspec rpcalculator_spec.rb --format documentation

RPCalculator
  #enter_number
    puts the entered number on top of the stack
  #add
    when two numbers are on the stack
      pops the stack's two topmost numbers and pushes their sum
      sets the last result to the sum of the stack's two topmost numbers
    when one number is on the stack
      leaves the stack untouched
      sets the last result to nil

Finished in 0.00116 seconds
5 examples, 0 failures

It’s…beautiful.

One simplification we can make to our spec is to use let statements. let statements define memoized helper methods that are lazily run the first time they’re used. They’re re-run for each example, and only if they’re called; they’re not evaluated otherwise. This is a nice advantage over global variables set in before blocks.

In our case, it’s a small change, but we can change the global calculator variable to use a let statement. This is also a good opportunity to factor out our constants.

describe RPCalculator do
  let (:calculator) { RPCalculator.new }
  let (:first_number) { 4 }
  let (:second_number) { 2 }
  let (:sum) { first_number + second_number }
  let (:product) { first_number * second_number }

  describe "#enter_number" do
    before :each do
      calculator.enter_number(first_number)
    end

    it "puts the entered number on top of the stack" do
      calculator.stack.should eq([first_number])
    end
  end

  describe "#add" do
    context "when at least two numbers are on the stack" do
      before :each do
        calculator.enter_number(first_number)
        calculator.enter_number(second_number)
        calculator.add
      end

      it "pops the stack's two topmost numbers and pushes their sum" do
        calculator.stack.should eq([sum])
      end

      it "sets the last result to the sum of the stack's two topmost numbers" do
        calculator.last_result.should eq(sum)
      end
    end

    context "when fewer than two numbers are on the stack" do
      before :each do
        calculator.enter_number(first_number)
        calculator.add
      end

      it "leaves the stack untouched" do
        calculator.stack.should eq([first_number])
      end

      it "sets the last result to nil" do
        calculator.last_result.should be_nil
      end
    end
  end
end

Shared Examples

Sometimes we expect the same thing to happen in multiple describe blocks. For instance, in our calculator, whether we’re adding or multiplying, we expect the same behavior when there are fewer than two numbers on the stack (ie. return nil and leave the stack untouched). We don’t want to repeat that entire context block, so we can pull it out into a shared_examples_for block. We can then use a special kind of it statement, it_behaves_like, to specify when we expect that behavior.

describe RPCalculator do
  let (:calculator) { RPCalculator.new }
  let (:first_number) { 4 }
  let (:second_number) { 2 }
  let (:sum) { first_number + second_number }
  let (:product) { first_number * second_number }

  describe "#enter_number" do
    before :each do
      calculator.enter_number(first_number)
    end

    it "puts the entered number on top of the stack" do
      calculator.stack.should eq([first_number])
    end
  end

  shared_examples_for "all operations when fewer than two numbers are on the stack" do
    it "leaves the stack untouched" do
      calculator.stack.should eq([first_number])
    end

    it "sets the last result to nil" do
      calculator.last_result.should be_nil
    end
  end

  describe "#add" do
    context "when at least two numbers are on the stack" do
      before :each do
        calculator.enter_number(first_number)
        calculator.enter_number(second_number)
        calculator.add
      end

      it "pops the stack's two topmost numbers and pushes their sum" do
        calculator.stack.should eq([sum])
      end

      it "sets the last result the sum of the stack's two topmost numbers" do
        calculator.last_result.should eq(sum)
      end
    end

    context "when fewer than two numbers are on the stack" do
      before :each do
        calculator.enter_number(first_number)
        calculator.add
      end

      it_behaves_like "all operations when fewer than two numbers are on the stack"
    end
  end

  describe "#multiply" do
    context "when at least two numbers are on the stack" do
      before :each do
        calculator.enter_number(first_number)
        calculator.enter_number(second_number)
        calculator.multiply
      end

      it "pops the stack's two topmost numbers and pushes their product" do
        calculator.stack.should eq([product])
      end

      it "sets the last result to the product of the stack's two topmost numbers" do
        calculator.last_result.should eq(product)
      end
    end

    context "when fewer than two numbers are on the stack" do
      before :each do
        calculator.enter_number(first_number)
        calculator.multiply
      end

      it_behaves_like "all operations when fewer than two numbers are on the stack"
    end
  end
end

And, our documentation:

$ rspec rpcalculator_spec.rb --format documentation

RPCalculator
  #enter_number
    puts the entered number on top of the stack
  #add
    when at least two numbers are on the stack
      pops the stack's two topmost numbers and pushes their sum
      sets the last result the sum of the stack's two topmost numbers
    when fewer than two numbers are on the stack
      behaves like all operations when fewer than two numbers are on the stack
        leaves the stack untouched
        sets the last result to nil
  #multiply
    when at least two numbers are on the stack
      pops the stack's two topmost numbers and pushes their product
      sets the last result to the product of the stack's two topmost numbers
    when fewer than two numbers are on the stack
      behaves like all operations when fewer than two numbers are on the stack
        leaves the stack untouched
        sets the last result to nil

Finished in 0.00209 seconds
9 examples, 0 failures

You can see how the usefulness of shared_examples_for can compound when we add other operators, and as more behavioral tests are added, the generated documentation becomes more and more handy.

There’s More!

There’s quite a bit more to RSpec, but these basic features are enough to make behavior-driven development in Ruby an enjoyable experience. For more details, check out the documentation.

Written by Max

February 22nd, 2012 at 6:00 pm

8 Responses to 'An Introduction to RSpec'

Subscribe to comments with RSS or TrackBack to 'An Introduction to RSpec'.

  1. Good example! Agile is the way to go

    Kevin

    23 Feb 12 at 07:37

  2. Do you mind if i guest post this on my blog?

    Kevin

    23 Feb 12 at 07:39

  3. I don’t mind at all—go for it!

    Max

    23 Feb 12 at 09:07

  4. Why do people insist on using things like Rspec. Too complex. Minitest is more than enough! Not?

    Meta

    23 Feb 12 at 11:45

  5. I’ve never used Minitest, but based on a quick search, it seems fairly similar to RSpec in some ways, and there is indeed some debate on which is preferable. I can’t really comment in any more detail but I’ll check it out. Thanks!

    Max

    23 Feb 12 at 11:50

  6. Thanks. Add me on LinkedIn when you get a chance

    Kevin Woghiren

    23 Feb 12 at 13:03

  7. very well done tutorial.
    thank you.

    Fred

    24 Feb 12 at 00:17

  8. Some truly quality weblog posts on this web website , saved to my bookmarks .

    click here

    12 Sep 12 at 11:01

Leave a Reply