An Introduction to RSpec
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.
Good example! Agile is the way to go
Kevin
23 Feb 12 at 07:37
Do you mind if i guest post this on my blog?
Kevin
23 Feb 12 at 07:39
I don’t mind at all—go for it!
Max
23 Feb 12 at 09:07
Why do people insist on using things like Rspec. Too complex. Minitest is more than enough! Not?
Meta
23 Feb 12 at 11:45
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
Thanks. Add me on LinkedIn when you get a chance
Kevin Woghiren
23 Feb 12 at 13:03
very well done tutorial.
thank you.
Fred
24 Feb 12 at 00:17
Some truly quality weblog posts on this web website , saved to my bookmarks .
click here
12 Sep 12 at 11:01