rspec user stories

27
RSpec and User Stories A step by step tutorial By Rahoul Baruah http://www.brightbox.co.uk Released under the Creative Commons Attribution Share-Alike Licence

Upload: rahoulb

Post on 19-May-2015

8.175 views

Category:

Technology


3 download

DESCRIPTION

A step-by-step tutorial outlining the use of RSpec and Cucumber to develop applications with automated acceptance tests

TRANSCRIPT

Page 1: RSpec User Stories

RSpec and User StoriesA step by step tutorial

By Rahoul Baruahhttp://www.brightbox.co.uk

Released under the Creative Commons Attribution Share-Alike Licence

Page 2: RSpec User Stories

What is RSpec? Behaviour Driven Development An evolution of Test Driven Development Concentrates on the “behaviour” of your

system Specify the behaviour first, implement it

second, refactor it third.

Page 3: RSpec User Stories

What are User Stories? Acceptance Tests for RSpec Describe the functionality of your application

in terms your customer will understand Prove that the application does what it is

supposed to.

Page 4: RSpec User Stories

A Simple Authentication System Write the feature Write verification code for the feature Specify the controller Implement the controller Specify the models Implement the models Verify the feature works as required Refactor

Page 5: RSpec User Stories

Write our Feature Write our feature and save it as features/

allow-a-user-to-login.feature Show it to the customer and have it approved Run rake features

Page 6: RSpec User Stories

Feature: Allow a User to log in As a user, I want to log in, So I can see my stuff Scenario: Successful login

Given a user called Dave with a password of secret And 15 items of stuff When I log in as Dave with password secret Then I should see the Dashboard page And it should list my 15 items of stuff

Scenario: Unsuccessful login Given a user called Dave with a password of secret When I log in as Dave with password potato Then I should see the Login page And I should see a message saying “an incorrect username or

password was supplied”

Page 7: RSpec User Stories

rake features When we run ‘rake features’ it tells us that

none of the features are implemented So we start with the first step and implement

that

Page 8: RSpec User Stories

Steps - proving a feature works Create Ruby files, registration-steps.rb and

session-steps.rb, in features/steps These contain the code verifying that the

feature works as expected

Page 9: RSpec User Stories

registration and session stepsGiven /^a user called (.*) with a password of (.*)$/ do | username, password |

user = User.find_by_username username

user.destroy unless user.nil?

visits '/registrations/new'

fills_in 'User name', :with => username

fills_in 'Password', :with => password

fills_in 'Password Confirmation', :with => password

clicks_button 'Register'

end

When /^I log in as (.*) with password (.*)$/ do | username, password |

visits '/sessions/new'

fills_in 'User name', :with => username

fills_in 'Password', :with => password

clicks_button 'Log in'

end

Then /^I should see the Dashboard page$/ do

response.should redirect_to('/dashboard')

end

Use Webrat to define our interactions with the application Use RSpec to check the responses from the application

Page 10: RSpec User Stories

Specify our Controller ‘rake features’ fails So we need to start writing some code to make it pass But before we write any code, we need a specification So we run… ruby script/generate rspec_controller Registrations

…to build a blank controller and specification Now to implement the RegistrationsController. Similar work needs to be done with the

SessionsController for actually logging in.

Page 11: RSpec User Stories

describe RegistrationsController do describe "GET new" do it "should show the form to allow someone to register" do on_getting :new do expect_to_create_a_blank_user end response.should be_success response.should render_template('registrations/new') end

describe "POST create" do it "should create and log in a new user" do on_posting_to :create, :user => { "some" => :values } do expect_to_build_a_new_user_with "some" => :values expect_to_save_the_new_user_successfully end controller.current_user.should == @user end it "should redirect to the users dashboard" do on_posting_to :create, :user => { "some" => :values } do expect_to_build_a_new_user_with "some" => :values expect_to_save_the_new_user_successfully end

Page 12: RSpec User Stories

Cont’d…

it "should fail to create a new user if given invalid values" do on_posting_to :create, :user => { "some" => :values } do expect_to_build_a_new_user_with "some" => :values expect_to_fail_to_save_the_new_user end controller.current_user.should be_blank end

it "should reshow the registration form if given invalid values" do on_posting_to :create, :user => { "some" => :values } do expect_to_build_a_new_user_with "some" => :values expect_to_fail_to_save_the_new_user end response.should render_template('/sessions/new') flash[:error].should == 'There were some errors when registering your details' end endend

Page 13: RSpec User Stories

Specification for new registrations

Use helper methods to make the specification easier to read Use mock objects so we are testing just the controller, not the

(non-existent) models Note how we can supply "some" => :values for

the :registration parameter. As we are not using real models, the actual fields do not matter; what counts is how the controller behaves in response to certain actions.

Page 14: RSpec User Stories

def expect_to_create_a_blank_user @user = mock_model User User.should_receive(:new).and_return(@user)end

def expect_to_build_a_new_user_with parameters @user = mock_model User User.should_receive(:new).with(parameters).and_return(@user)end

def expect_to_save_the_new_user_successfully @user.should_receive(:save!).and_return(true)end

def expect_to_fail_to_save_the_new_user prepare_for_errors_on @user @user.should_receive(:save!).and_raise(ActiveRecord::RecordInvalid.new(@user))end

The helpers build a mock user We tell the mock to expect certain method calls and return the

correct responses

Page 15: RSpec User Stories

Implement the Controller We do need to build a model… ruby script/generate rspec_model User …so that our steps will run Remove the < ActiveRecord::Base from the definition for now

(so that ActiveRecord does not complain about our lack of database tables)

But we don’t implement anything in it yet; our controller specification is using mocks so does not actually need a real object

We also need to add some routes to get things moving… map.resources :registrations map.resources :sessions map.resource :dashboard

Page 16: RSpec User Stories

Implementing the Controllerclass RegistrationsController < ApplicationController def new @user = User.new end def create @user = User.new params[:user] @user.save! redirect_to dashboard_path rescue ActiveRecord::RecordInvalid flash[:error] = 'There were some errors when registering your details' render :template => 'registrations/new', :status => 422 endend

The implementation is pretty simple, which is exactly as it should be.

Page 17: RSpec User Stories

Specifying the Model Now we have our controller behaving as

specified we need to specify the behaviour of our User model

We have already generated the RSpec files, we just need to tell it how we expect a User to behave.

Page 18: RSpec User Stories

Specifying the Model We write the specs Update the migration to deal with the fields we

need We switch the model back so that it descends

from ActiveRecord::Base We run the migration We implement the model

Page 19: RSpec User Stories

Specifying the ModelFirst attempt…describe User do it "should have a username" do @user = User.new :username => '' @user.should_not be_valid @user.should have(1).errors_on(:username) end it "should have a unique username" do @first_user = User.create! :username => 'arthur', :password =>

'12345', :password_confirmation => '12345' @second_user = User.new :username => 'arthur' @second_user.should_not be_valid @second_user.should have(1).errors.on(:username) end

Cont’d…

Page 20: RSpec User Stories

Cont’d…

it "should confirm the password before saving" do @user = User.new :password => 'secret', :password_confirmation => 'notsecret' @user.should_not be_valid @user.should have(1).errors_on(:password) end it "should encrypt the password before saving to the database" do PasswordEncrypter.should_receive(:encrypt).with('12345').and_return('3ncrypt3d') @user = User.new :username => 'arthur', :password => '12345', :password_confirmation

=> '12345' @user.save! @user.encrypted_password.should == '3ncrypt3d' endend

Page 21: RSpec User Stories

Specifying the Model There are two areas of the specification that “smell

bad” Both “it should have a unique username” and “it should

encrypt the password before saving to the database” require a valid object to be saved; which in turn require knowledge of the object beyond that individual specification clause

Ideally this would not be necessary but is a problem with the ActiveRecord pattern; mixing business logic and persistence logic in a single class

Page 22: RSpec User Stories

The (imperfect) solution is to use object ‘factories’ that encapsulate knowledge of a valid model.

We are still spreading that knowledge outside of the specification but at least it is a single place to make that change (usually in spec/spec_helper.rb)

Page 23: RSpec User Stories

describe User do it "should have a username" do @user = a User, :username => '' @user.should_not be_valid @user.should have(1).errors_on(:username) end it "should have a unique username" do @first_user = a_saved User, :username => ‘arthur’ @second_user = a User, :username => 'arthur' @second_user.should_not be_valid @second_user.should have(1).errors.on(:username) end it "should confirm the password before saving" do @user = a User, :password => 'secret', :password_confirmation => 'notsecret' @user.should_not be_valid @user.should have(1).errors_on(:password) end

Page 24: RSpec User Stories

Rewritten to use “a Something” and “a_saved Something” as an object factory

Each specification clause only specifies the information it needs.

The factory ensures that the rest of the object is valid.

Page 25: RSpec User Stories

Acceptance By now we should have done enough to let the

first step in our story pass its acceptance test rake features will prove it That should be all we need to do for that

particular step - any extra development is unnecessary as it has not been requested by the customer

However, we can now safely refactor the code, to organise it better, safe in the knowledge that we can prove that it still does what is required without breakages.

Page 26: RSpec User Stories

Appendix We have a fork of RSpec for Rails that provides a set of helper

methods: prepare_for_errors_on, on_getting, on_posting_to, on_putting_to and on_deleting_from

See http://github.com/rahoulb/rspec-rails/wikis/home We are working on an object factory that makes building

models (without full knowledge of their internals) a bit simpler…

Object.factory.when_creating_a User, :auto_generate => :username, :auto_confirm => :password

@user = a User @user = a_saved User # as above but auto-saves the user @user.should be_valid

See http://github.com/rahoulb/object-factory/wikis (although at the time of writing, November 2008, this is not quite ready)

Page 27: RSpec User Stories

[email protected]/brightbox