RSpec Best Practices (Behavior-Driven Development BDD tool for Ruby)

Methods & Tools Software Development Magazine

Software Development Magazine - Project Management, Programming, Software Testing

Scrum Expert - Articles, tools, videos, news and other resources on Agile, Scrum and Kanban

RSpec Best Practices

Jared Carroll, Carbon Five, http://blog.carbonfive.com

RSpec is a Behavior-Driven Development tool for Ruby programmers. BDD is an approach to software development that combines Test-Driven Development, Domain Driven Design and Acceptance Test-Driven Planning. RSpec helps you do the TDD part of that equation, focusing on the documentation and design aspects of TDD.

Web Site: http://relishapp.com/rspec
Version tested: 2.5
License & Pricing: MIT License, open source / free
Support: Community

RSpec Best Practices

RSpec is a great tool in the behavior driven design process of writing human readable specifictions that direct and validate the development of your application. I've found the following practices helpful in writing elegant and maintainable specifications.

First #describe What You Are Doing

Begin by using #describe for each of the methods you plan on defining, passing the method痴 name as the argument. For class method specs prefix a "." to the name, and for instance level specs prefix a "#". This follows standard Ruby documentation practices and will read well when output by the spec runner.

describe User do
 describe '.authenticate' do
 end
 describe '.admins' do
 end
 describe '#admin?' do
 end
 describe '#name' do
 end
end

Then Establish The #context

Next use #context to explain the different scenarios in which the method could be executed. Each #context establishes the state of the world before executing the method. Write one for each execution path through a method.

For example, the following method has two execution paths:

class SessionsController < ApplicationController
 def create
 user = User.authenticate :email => params[:email],
 :password => params[:password]
 if user.present?
 session[:user_id] = user.id
 redirect_to root_path
 else
 flash.now[:notice] = 'Invalid email and/or password'
 render :new
 end
 end
end

The spec for this method would consists of two contexts:

describe '#create' do
 context 'given valid credentials' do
 end
 context 'given invalid credentials' do
 end
end

Note the use of the word "given" in each #context. This communicates the context of receiving input. Another great word to use in a context for describing conditional driven behavior is "when".

describe '#destroy' do
 context 'when logged in' do
 end
 context 'when not logged in' do
 end
end

By following this style, you can then nest #contexts to clearly define further execution paths.

And Finally Specify The Behavior

Strive to have each example specify only one behavior. This will increase the readability of your specs and make failures more obvious and easier to debug.

The following is a spec with multiple un-related behaviors in a single example:

describe UsersController do
 describe '#create' do
 ...
 it 'creates a new user' do
 User.count.should == @count + 1
 flash[:notice].should be
 response.should redirect_to(user_path(assigns(:user)))
 end
 end
end

Break out the expectations into separate examples for a more clear definition of the different behaviors.

describe UsersController do
 describe '#create' do
 ...
 it 'creates a new user' do
 User.count.should == @count + 1
 end
 it 'sets a flash message' do
 flash[:notice].should be
 end
 it "redirects to the new user's profile" do
 response.should redirect_to(user_path(assigns(:user)))
 end
 end
end

Tips For Better Examples

Lose The Should

Don't begin example names with the word "should". It is redundant and results in hard to read spec output. Instead write examples by starting with a present tense verb that describes the behavior.

it 'creates a new user' do
end
it 'sets a flash message' do
end
it 'redirects to the home page' do
end
it 'finds published posts' do
end
it 'enqueues a job' do
end
it 'raises an error' do
end

Don't hesitate to use words like "the" or "a" or "an" in your examples when they improve readability.

Use The Right Matcher

RSpec comes with a lot of useful matchers to help your specs read more like natural language. When you feel there is a cleaner way ... there usually is.

Here are some common matcher refactorings to help improve readability.

# before: double negative
object.should_not be_nil
# after: without the double negative
object.should be
# before: "lambda" is too low level
lambda { model.save! }.should raise_error(ActiveRecord::RecordNotFound)
# after: for a more natural expectation replace "lambda" and
"should" with "expect" and "to"
expect { model.save! }.to raise_error(ActiveRecord::RecordNotFound)
# the negation is also available as "to_not"
expect { model.save! }.to_not raise_error(ActiveRecord::RecordNotFound)
# before: straight comparison
collection.size.should == 4
# after: a higher level size expectation
collection.should have(4).items

Prefer Explicitness

#it, #its and #specify may cut down on the amount of typing but they sacrifice readability. Using these methods requires you to read the body of the example in order to determine what its specifying. Use these sparingly if at all.

Let's compare the output from the documentation formatter of the following spec that uses these more concise example methods.

describe PostsController do
 describe '#new' do
 context 'when not logged in' do
 ...
 subject do
 response
 end
 it do
 should redirect_to(sign_in_path)
 end
 its :body do
 should match(/sign in/i)
 end
 end
 end
end
$ rspec spec/controllers/posts_controller_spec.rb --format documentation
PostsController
 #new
 when not logged in
 should redirect to "/sign_in"
 should match /sign in/i

Running this spec results in blunt, code-like output with redundancy from using the word "should" multiple times.

Here is the same spec using more verbose, explicit examples:

describe PostsController do
 describe '#new' do
 context 'when not logged in' do
 ...
 it 'redirects to the sign in page' do
 response.should redirect_to(sign_in_path)
 end
 it 'displays a message to sign in' do
 response.body.should match(/sign in/i)
 end
 end
 end
end
$ rspec spec/controllers/posts_controller_spec.rb --format documentation
PostsController
 #new
 when not logged in
 redirects to the sign in page
 displays a message to sign in

This version results in a very clear, readable specification.

Run Specs To Confirm Readability

Always run your specs with the "--format" option set to "documentation" (in RSpec 1.x the --format options are "nested" and "specdoc")

$ rspec spec/controllers/users_controller_spec.rb --format documentation
UsersController
 #create
 creates a new user
 sets a flash message
 redirects to the new user's profile
 #show
 finds the given user
 displays its profile
 #show.json
 returns the given user as JSON
 #destroy
 deletes the given user
 sets a flash message
 redirects to the home page

Continue to rename your examples until this output reads like clear conversation.

Formatting

Use "do..end" style multiline blocks for all blocks, even for one-line examples. Further improve readability and delineate behavior with a single blank line between all #describe blocks and at the beginning and end of the top level #describe.

Before:

describe PostsController do
 describe '#new' do
 context 'when not logged in' do
 ...
 subject { response }
 it { should redirect_to(sign_in_path) }
 its(:body) { should match(/sign in/i) }
 end
 end
end

And after:

describe PostsController do
 describe '#new' do
 context 'when not logged in' do
 ...
 it 'redirects to the sign in page' do
 response.should redirect_to(sign_in_path)
 end
 it 'displays a message to sign in' do
 response.body.should match(/sign in/i)
 end
 end
 end
end

A consistent formatting style is hard to achieve with a team of developers but the time saved from having to learn to visually parse each teammate's style makes it worthwhile.

Conclusion

As you can see, all these practices revolve around writing clear specifications readable by all developers. The ideal is to run all specs to not only pass but to have their output completely define your application. Every little step towards that goal helps.


More Software Testing Content


Click here to view the complete list of tools reviews

This article was originally published in the Spring 2011 issue of Methods & Tools

Methods & Tools
is supported by


Software Testing
Magazine


The Scrum Expert

AltStyle によって変換されたページ (->オリジナル) /