Introduction to RSpec
RSpec is a testing tool for the Ruby programming language that allows you to write an automated test suite. RSpec provides great ways to combine code from similar tests, provides good error messages, and encourages you to specify exactly what you are testing.
The main documentation for RSpec can be found from the project's website at rspec.info. This page documents the most important things you should know about RSpec.
File structure
The recommended way to set up RSpec is to put your test suite in a
directory named spec
, as discussed in the
Quick-start guide. When you run the
rspec
command, RSpec will look for all the files in
spec
and its subdirectories that have a name ending in
_spec.rb
.
Example groups and examples
Each spec file contains one or more example groups, which are
defined using RSpec's describe
or context
commands. For example:
describe "my device" do
# examples of how your device should behave go here
end
You can have nested example groups:
describe "my device" do
context "when RA1 is held high" do
# examples go here
end
end
The outer-most example group must be a describe
, not a
context
.
An example is the basic unit of an RSpec test suite. Examples are
defined using the it
or specify
commands inside
an example group:
describe "my device" do
it "toggles RA2 often" do
# code to test the toggling goes here
end
end
The strings passed to describe
, context
,
it
, and specify
are optional, but are used to
generate helpful error messages and documentation. If an example fails,
then a string about the expected behavior is produced by concatenating all
the provided strings, from outermost to innermost.
Expectations
In general, an RSpec example consists of some code to set up the situation being tested, and some code called an expectation that defines the expected outcome. If an expectation fails, an exception is raised, the example stops executing, and a failure message is displayed. Expectations look like this:
expect(something).to have_some_property
expect(something).to_not have_some_property
-
expect
is a method defined by RSpec which you can use inside an example. -
something
is the object being tested, which is usually the output of the system under test or part of the system under test. -
to
andto_not
are special methods defined by RSpec. -
have_some_property
is a method call that returns a Matcher object. The Matcher object decides whether the expectation was met or not. In the example below, this method call iseq(6)
, which uses one of the built-in matcher types that RSpec provides. Many RSpec examples only useeq
matchers. -
Sometimes, as with
eq(6)
, you will pass arguments into the methods that create matchers. Often these arguments represent expected values, states, or properties being tested.
Here is a concrete, runnable example:
describe "the number 5" do
it "when added to one is six" do
num = 1 + 5
expect(num).to eq 6
end
end
First, the code adds 1 to 5 and stores the result. Then the number 6 is
passed as the first argument to eq
, making an equality matcher
that tests whether the result is equal to 6. Note that parentheses are not
required when calling a method in Ruby: you can write eq(6)
or
eq 6
.
A common practice in RSpec is to have just one expectation per example. This helps you organize your examples and makes it clear to the reader what each example is supposed to be testing. However, it is not always practical because it makes the specs slower; there will be more examples and the code that gets the system into the the situation to be tested has to be run more times than necessary.
For more information, see the RSpec Expectations documentation.
Before hooks
Inside an example group, RSpec lets you define various kinds of hooks. The only one we need for RPicSim is the before hook. A before hook runs before each example in the example group. For example:
describe "my device" do
before do
# Code here runs before each example in this group.
# This is equivalent to before(:each).
end
specify do
# Code for example 1
end
specify do
# Code for example 2
end
end
In RPicSim, you will usually have a before hook that starts the simulation of your firmware.
If you have a context referring to your system being in a particular state, a before hook is a great place for the code that actually gets the system into that state. The code in that hook can easily be reused in multiple examples that test different things about the state. For example:
describe "my car" do
context "in second gear at 20 MPH" do
before do
car.speed = 20
car.gear = 2
end
it "has a lot of torque" do
expect(car.torque).to eq 7
end
it "has a high RPM" do
expect(car.rpm).to eq 9001
end
end
end
Instance variables
In Ruby, a variable whose name starts with @
behaves specially
and is called an instance variable. Instance variables defined in
a before hook can be accessed in examples within the example group.
For example:
describe "foo" do
before do
@car = Car.new
@wheel = car.wheels.find_by_location(:front, :left).first
end
it "is round" do
expect(@wheel).to be_round
end
end
Let variables
Another way to define a variable in RSpec is to use the let
syntax. The let
method should be called inside an example
group. The first argument is the name of a variable to define. The
let
method is passed a block that computes the value of the
variable, and the block will be called if the value of the variable is ever
needed. In other words, let
variables are lazily evaluated.
The example below shows how a let
variable could be useful for
making your tests more readable. In this case, let
allows us
to separate the description of the expected behavior (the car can shift
gears) from the arbitrary value (2) that we used to test it.
describe "my car" do
let(:gear) { 2 }
it "can shift gears" do
car.gear = gear
expect(car.gear).to eq gear
end
end
These let
variables can also be a useful way to share data or
bits of code between different examples.
describe "my car's front left wheel" do
let(:car) { Car.new }
let(:wheel) { car.wheels.find_by_location(:front, :left).first }
it "is round" do
expect(wheel).to be_round
end
it "is a wheel" do
expect(wheel).to be_a_kind_of Car::Wheel
end
end
Blocks in RSpec
Whenever you see code between the Ruby keywords do
and
end
, or between {
and }
, that code
is inside a Ruby block. A block is a chunk of executable code that
can be easily created, passed around, and called like a method.
In RSpec, the blocks you pass to the describe
and
context
methods are executed right away. However, the blocks
passed to the it
, specify
, before
,
after
, and let
commands serve to define how the
examples will be executed, and those blocks do not get called until later
after RSpec has chosen which examples to run.
You cannot read a spec file like you would read a simple step-by-step program. Any given block could be called once, multiple times, or never depending on the situation.
Shared examples
Sometimes you will be tempted to copy a set of examples from one context to another, because the system should behave the same in both contexts. However, your tests will be long and hard to maintain if you have too much duplicated code. In this situation, you should consider making a shared example group. See the RSpec shared examples documentation for more information.