RSpec User Stories
Upcoming SlideShare
Loading in...5
×
 

RSpec User Stories

on

  • 10,974 views

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

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

Statistics

Views

Total Views
10,974
Views on SlideShare
7,414
Embed Views
3,560

Actions

Likes
8
Downloads
377
Comments
1

4 Embeds 3,560

http://blog.brightbox.co.uk 3524
http://www.slideshare.net 32
http://static.slideshare.net 3
http://webcache.googleusercontent.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • By the way - the object-factory now lives at http://github.com/brightbox/object-factory/tree/master
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

RSpec User Stories RSpec User Stories Presentation Transcript

  • 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
  • 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.
  • 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.
  • 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
  • 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
  •  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”
  • 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
  • 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
  • registration and session steps Given /^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
  • 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.
  • describe RegistrationsController do describe quot;GET newquot; do it quot;should show the form to allow someone to registerquot; do on_getting :new do expect_to_create_a_blank_user end response.should be_success response.should render_template('registrations/new') end describe quot;POST createquot; do it quot;should create and log in a new userquot; do on_posting_to :create, :user => { quot;somequot; => :values } do expect_to_build_a_new_user_with quot;somequot; => :values expect_to_save_the_new_user_successfully end controller.current_user.should == @user end it quot;should redirect to the users dashboardquot; do on_posting_to :create, :user => { quot;somequot; => :values } do expect_to_build_a_new_user_with quot;somequot; => :values expect_to_save_the_new_user_successfully end
  • Cont’d… it quot;should fail to create a new user if given invalid valuesquot; do on_posting_to :create, :user => { quot;somequot; => :values } do expect_to_build_a_new_user_with quot;somequot; => :values expect_to_fail_to_save_the_new_user end controller.current_user.should be_blank end it quot;should reshow the registration form if given invalid valuesquot; do on_posting_to :create, :user => { quot;somequot; => :values } do expect_to_build_a_new_user_with quot;somequot; => :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 end end
  • 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 quot;somequot; => :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.
  • 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
  • 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
  • Implementing the Controller class 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 end end  The implementation is pretty simple, which is exactly as it should be.
  • 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.
  • 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
  • Specifying the Model First attempt… describe User do it quot;should have a usernamequot; do @user = User.new :username => '' @user.should_not be_valid @user.should have(1).errors_on(:username) end it quot;should have a unique usernamequot; 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…
  • Cont’d… it quot;should confirm the password before savingquot; do @user = User.new :password => 'secret', :password_confirmation => 'notsecret' @user.should_not be_valid @user.should have(1).errors_on(:password) end it quot;should encrypt the password before saving to the databasequot; 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' end end
  • 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
  •  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)
  • describe User do it quot;should have a usernamequot; do @user = a User, :username => '' @user.should_not be_valid @user.should have(1).errors_on(:username) end it quot;should have a unique usernamequot; 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 quot;should confirm the password before savingquot; do @user = a User, :password => 'secret', :password_confirmation => 'notsecret' @user.should_not be_valid @user.should have(1).errors_on(:password) end
  •  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.
  • 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.
  • 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)
  • www.brightbox.co.uk hello@brightbox.co.uk twitter.com/brightbox