Your SlideShare is downloading. ×
RSpec User Stories
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

RSpec User Stories

8,143
views

Published on

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

Published in: Technology

1 Comment
8 Likes
Statistics
Notes
  • By the way - the object-factory now lives at http://github.com/brightbox/object-factory/tree/master
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
8,143
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
392
Comments
1
Likes
8
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. 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
  • 2. 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.
  • 3. 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.
  • 4. 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
  • 5. 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
  • 6.  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”
  • 7. 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
  • 8. 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
  • 9. 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
  • 10. 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.
  • 11. 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
  • 12. 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
  • 13. 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.
  • 14. 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
  • 15. 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
  • 16. 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.
  • 17. 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.
  • 18. 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
  • 19. 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…
  • 20. 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
  • 21. 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
  • 22.  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)
  • 23. 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
  • 24.  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.
  • 25. 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.
  • 26. 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)
  • 27. www.brightbox.co.uk hello@brightbox.co.uk twitter.com/brightbox

×