Integration Testing in Ruby with RSpecs Story Automation - - PowerPoint PPT Presentation

integration testing in ruby with rspec s story automation
SMART_READER_LITE
LIVE PREVIEW

Integration Testing in Ruby with RSpecs Story Automation - - PowerPoint PPT Presentation

Integration Testing in Ruby with RSpecs Story Automation Framework David Chelimsky articulatedman.com Behaviour-Driven Development BDD Writing software that matters - Dan North BDD Dan North/Aslak Hellesy 2004 Improve


slide-1
SLIDE 1

Integration Testing in Ruby with RSpec’s Story Automation Framework

David Chelimsky articulatedman.com

slide-2
SLIDE 2

Behaviour-Driven Development

slide-3
SLIDE 3

BDD

Writing software that matters

  • Dan North
slide-4
SLIDE 4

BDD

  • Dan North/Aslak Hellesøy 2004
  • Improve communication about Test

Driven Development

  • JBehave
slide-5
SLIDE 5

BDD

  • Second generation “full stack” Agile

methodology rooted in:

  • Extreme Programming
  • Acceptance Test Driven Planning
  • Test Driven Development
slide-6
SLIDE 6

RSpec

slide-7
SLIDE 7

RSpec

  • Behaviour Driven Development Framework
  • Story Framework
  • Acceptance Test Driven Planning
  • Example Framework
  • Test Driven Development
slide-8
SLIDE 8

RSpec Origins

  • Inspired by a blog post by Dave Astels
  • Authored by Steven Baker
  • Summer ‘05
  • Maintained by me
  • Since Summer ‘06

http://daveastels.com/2005/07/05/a-new-look-at-test-driven-development/

slide-9
SLIDE 9

BDD Process

slide-10
SLIDE 10

Process

  • Inject features discovered through analysis
  • Feature Injection - Chris Matts
  • Extract stories from features
  • Focus on outputs
  • Break stories down into scenarios
  • Acceptance Criteria
slide-11
SLIDE 11

Feature Injection

slide-12
SLIDE 12

Popping the “Why?” Stack

slide-13
SLIDE 13

I want people to be able to register

slide-14
SLIDE 14

Why?

slide-15
SLIDE 15

I want to know how many people registered

slide-16
SLIDE 16

Why?

slide-17
SLIDE 17

So I can measure progress towards registration goals

slide-18
SLIDE 18

Why?

slide-19
SLIDE 19

This is getting really, really annoying

slide-20
SLIDE 20

I know ...

slide-21
SLIDE 21

Why?

slide-22
SLIDE 22

Why do you want to measure progress towards registration goals?

slide-23
SLIDE 23

SO I CAN MANAGE COST

slide-24
SLIDE 24
  • If you keep asking “why?”, you’ll eventually

land on one of:

  • Generate/protect revenue
  • Reduce/manage cost
  • When you do, the answer to the previous

“why?” is often a feature waiting to be discovered.

Popping the “Why?” Stack

slide-25
SLIDE 25

User Stories

  • High level analysis and planning tool
  • “Token for a conversation”
slide-26
SLIDE 26

BDD User Stories

  • Add Scenarios that serve as Acceptance

Criteria

slide-27
SLIDE 27

So what does this all have to do with Integration Testing?

slide-28
SLIDE 28

Integration Testing

  • Make sure the component parts play nice

together

slide-29
SLIDE 29

Automated User Stories/Scenarios

  • Document the expected behaviour of the

system

  • Verify that behaviour by executing a thin

vertical slice of the system

slide-30
SLIDE 30

Conference Organizer (Example Application)

slide-31
SLIDE 31

Example Story

slide-32
SLIDE 32

Example Story

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-33
SLIDE 33

Title

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-34
SLIDE 34

Narrative

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-35
SLIDE 35

Scenarios

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-36
SLIDE 36

Narrative Format

“The Connextra Format”

slide-37
SLIDE 37

As a Role

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-38
SLIDE 38

I want Action

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-39
SLIDE 39

So that Goal

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-40
SLIDE 40

Narrative Format

  • Completely arbitrary, but ...
  • Look for a format that
  • Identifies the goal
  • Identifies the user/persona
  • Identifies the action
slide-41
SLIDE 41

Alternate Format

Story: measure progress towards registration goals In order to measure progress towards registration goals As a conference organizer I want to see a report of registrations Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-42
SLIDE 42

Scenario Format

slide-43
SLIDE 43

Given | When | Then

The other GWT

slide-44
SLIDE 44

Given | When | Then

  • A simple way of saying:
  • Pre-conditions, Event, Post-conditions
  • Context, Action, Outcome
  • Build, Operate, Check
  • Uncle Bob Martin
slide-45
SLIDE 45

Given | When | Then

  • Words that can be understood equally well

by:

  • stakeholders
  • business analysts
  • developers
  • testers
slide-46
SLIDE 46

Given

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-47
SLIDE 47

When

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-48
SLIDE 48

Then

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-49
SLIDE 49

Automation

slide-50
SLIDE 50

Ruby

Story "measure progress towards registration goals",%( As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals ), :type => RailsStory, :steps_for => :registrations do Scenario "one registration shows as 1%" do Given "a goal of 200 registrations" When "1 attendee registers" Then "the goal should be 1% achieved" end Scenario "one registration less than the goal shows as 99%" do Given "a goal of 200 registrations" When "199 attendees register" Then "the goal should be 99% achieved" end end

slide-51
SLIDE 51

Plain Text

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-52
SLIDE 52

Plain Text

with_steps_for :registrations do run "#{File.dirname(__FILE__)}/measure_progress.story", :type => RailsStory end

slide-53
SLIDE 53

Step Definitions

slide-54
SLIDE 54

Direct Model Access

slide-55
SLIDE 55

Given

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-56
SLIDE 56

Given

steps_for :registrations do Given "a goal of $goal registrations" do |goal| @conference = Conference.create!( :name => "BDD", :goal => goal ) end end

slide-57
SLIDE 57

Given

steps_for :registrations do Given "a goal of $goal registrations" do |goal| @conference = Conference.create!( :name => "BDD", :goal => goal ) end end

slide-58
SLIDE 58

When

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-59
SLIDE 59

When

steps_for :registrations do ... When /(\d+) attendees? registers?/ do |count| (1..(count.to_i)).each do |n| Registration.create!( :name => "Name #{n}", :email => "email#{n}@site.com", :conference => @conference ) end end end

slide-60
SLIDE 60

When

steps_for :registrations do ... When /(\d+) attendees? registers?/ do |count,_,_| (1..(count.to_i)).each do |n| Registration.create!( :name => "Name #{n}", :email => "email#{n}@site.com", :conference => @conference ) end end end

slide-61
SLIDE 61

Then

Story: measure progress towards registration goals As a conference organizer I want to see a report of registrations So that I can measure progress towards registration goals Scenario: one registration shows as 1% Given a goal of 200 registrations When 1 attendee registers Then the goal should be 1% achieved Scenario: one registration less than the goal shows as 99% Given a goal of 200 registrations When 199 attendees register Then the goal should be 99% achieved

slide-62
SLIDE 62

Then

steps_for :registrations do ... Then "the goal should be $percentage% achieved" do |percentage| @conference.percentage_of_goal.should == percentage.to_i end end

slide-63
SLIDE 63

Then

steps_for :registrations do ... Then "the goal should be $percentage% achieved" do |percentage| @conference.percentage_of_goal.should == percentage.to_i end end

slide-64
SLIDE 64

Direct Model Access

  • Pros
  • More flexible
  • Less brittle
  • Cons
  • Less thorough
  • No views/controllers
slide-65
SLIDE 65

(Almost) Full Stack using Rails Integration Test and Webrat

slide-66
SLIDE 66

Rails Integration Test

  • Simulate HTTP requests
  • Goes through routing (unlike Rails

functional tests)

  • Simulate multiple sessions
slide-67
SLIDE 67

RailsStory

  • Wraps Rails Integration Test
  • Access to everything you get from Rails
  • Access to everything you get from RSpec
slide-68
SLIDE 68

Given

steps_for :registrations_through_rails_stack do Given "a goal of $goal registrations" do |goal| get "/conferences/new" response.should have_tag( "form[action=?]", conferences_path) do with_tag("input#conference_name") with_tag("input#conference_goal") end @conference_name = "BDD #{Time.new.to_i}" post conferences_path, :conference => { :name => @conference_name, :goal => goal } end end

slide-69
SLIDE 69

Duplication

steps_for :registrations_through_rails_stack do Given "a goal of $goal registrations" do |goal| get "/conferences/new" response.should have_tag( "form[action=?]", conferences_path) do with_tag("input#conference_name") with_tag("input#conference_goal") end @conference_name = "BDD #{Time.new.to_i}" post conferences_path, :conference => { :name => @conference_name, :goal => goal } end end

slide-70
SLIDE 70

Webrat

  • Ruby Gem maintained by Bryan Helmkamp
  • http://github.com/brynary/webrat
  • stores DOM in memory
  • manipulates DOM
  • builds POST from DOM
  • logically binding the form to the POST
slide-71
SLIDE 71

Given (with Webrat)

steps_for :registrations_through_rails_stack do Given "a goal of $goal registrations" do |goal| @conference_name = "BDD #{Time.new.to_i}" visits new_conference_path fills_in "Name", :with => @conference_name fills_in "Goal", :with => goal clicks_button "Goal" end end

slide-72
SLIDE 72

When

steps_for :registrations_through_rails_stack do ... When /(\d+) attendees? registers?/ do |count| (1..(count.to_i)).each do |n| visits new_registration_path fills_in "Name", :with => "Name #{n}" fills_in "E-Mail", :with => "email#{n}@site.com" selects @conference_name clicks_button end end end

slide-73
SLIDE 73

Then

steps_for :registrations_through_rails_stack do ... Then "the goal should be $percentage% achieved" do |percentage| @conference = Conference.find_by_name(@conference_name) visits conference_path(@conference) response.should have_text(/#{percentage}%/) end end

slide-74
SLIDE 74

Full Stack (sans browser)

  • Pros
  • Full stack
  • High level of coverage
  • Confidence
slide-75
SLIDE 75

Full Stack (sans browser)

  • Cons
  • Full Stack
  • Subject to changes from larger area
  • Requires known html elements/structure
  • Not too bad if you follow conventions
  • Webrat helps too
slide-76
SLIDE 76

Full Stack using Selenium-RC

slide-77
SLIDE 77

Given

steps_for :registrations_through_browser do Given "a goal of $goal registrations" do |goal| $browser.open "http://localhost:3000/conferences/new" @conference_name = "BDD #{Time.new.to_i}" $browser.type "conference_name", @conference_name $browser.type "conference_goal", goal $browser.submit "new_conference" $browser.wait_for_page_to_load(5000) @conference_url = $browser.get_location end end

slide-78
SLIDE 78

steps_for :registrations_through_browser do ... When /(\d+) attendees? registers?/ do |count| (1..(count.to_i)).each do |n| $browser.open "http://localhost:3000/registrations/new" $browser.type "css=#registration_name", "Name #{n}" $browser.type "css=#registration_email", "email#{n}@site.com" $browser.select "css=#registration_conference_id", @conference_name $browser.submit "css=#new_registration" end end end

When

slide-79
SLIDE 79

Then

steps_for :registrations_through_browser do ... Then "the goal should be $percentage% achieved" do |percentage| $browser.open @conference_url $browser.get_text("css=#percentage_of_goal"). should =~ /#{percentage}%/ end end

slide-80
SLIDE 80

Full Stack (with browser)

  • Pros
  • Test javascript/ajax
  • Test some aspects of HTML too
  • You can watch it!
  • High impact for customers
slide-81
SLIDE 81

Full Stack (with browser)

  • Cons
  • Brittle
  • Like in-memory, coupled to entire stack
  • Possibly even more subject to UI

changes

  • S L O W
slide-82
SLIDE 82

Full Stack (with browser)

  • Recommendation
  • Use in-memory first
  • Use in-browser when
  • Testing javascript/ajax
  • Useful when necessary to increase

customer confidence

slide-83
SLIDE 83

Detailed Scenarios

slide-84
SLIDE 84

Detailed Scenarios

Story: attendee registers As a potential attendee I want to register for a conference So that I may attend and learn great stuff Scenario: successful registration Given I am viewing the registration form When I enter Name: Joe Smith And I enter E-Mail: jsmith@site.com And I check Tutorials And I click Register Then I should see the Registration Confirmation And it should show Name: Joe Smith And it should show E-Mail: jsmith@site.com And it should show Tutorials: Yes

slide-85
SLIDE 85

Detailed Scenarios

... Scenario: missing email address Given I am viewing the registration form When I enter Name: Joe Smith And I do not enter E-Mail And I click Register Then I should see the Registration Form And it should show Email is required

slide-86
SLIDE 86

Givens

steps_for :registration do Given "I am viewing the registration form" do visits new_registration_path end Given "a conference named $name" do |name| visits new_conference_path fills_in "Name", :with => name fills_in "Goal", :with => 200 clicks_button end end

slide-87
SLIDE 87

Whens

steps_for :registration do When "I enter $label: $value" do |label, value| fills_in label, :with => value end When "I do not enter $label" do |label| # no-op - doc purposes only end end

slide-88
SLIDE 88

Whens

steps_for :registration do When "I select $label" do |label| selects label end When "I check $label" do |label| checks label end When "I click $button" do |button| clicks_button button end end

slide-89
SLIDE 89

steps_for :registration do Then /I should see the Registration (Form|Confirmation)/ do |form_or_confirmation| case form_or_confirmation when "Form" response.should render_template("registrations/new") when "Confirmation" r = Registration.find(:all, :order => 'id').last response.should render_template("registrations/show") end end Then "it should show $text" do |text| response.should include_text(text) end end

Thens

slide-90
SLIDE 90

Detailed Scenarios

  • More “design up front” feel vs “story as

token for conversation”

  • Scenarios are more subject to changes from

customer

  • Steps are more granular
  • Easier to write
  • Easier to change
slide-91
SLIDE 91

Multiple Sessions

slide-92
SLIDE 92

Multiple Sessions

Story "can not view other group's calendar", %( As a member of one group with calendars I do not want members of other groups to see my calendars So that my data is kept private ), :type => RailsStory, :steps_for => [ :users_and_groups, :calendars, :navigation ] do ... end

slide-93
SLIDE 93

Multiple Sessions

Story "can not view other group's calendar", %( ... Scenario "two users, two groups, two calendars" do Given "a group named Group1" And "a group named Group2" And "Group1 has a calendar named Calendar1" And "Group2 has a calendar named Calendar2" And "a Group1 member named Person1 is logged in" And "a Group2 member named Person2 is logged in" When "Person1 visits the calendar list" Then "he should see Calendar1" And "he should not see Calendar2" When "Person2 visits the calendar list" Then "she should see Calendar2" And "she should not see Calendar1" end end

slide-94
SLIDE 94

Given

Given "a group named $group" do |group| set_ivar :group, group, Group.create!(:name => group) end Given "a $group member named $name is logged in" do |group, name| create_user_named(name) do |user| user.activate user.groups << get_ivar(:group, group) end login_as(name) end

slide-95
SLIDE 95

When

When "$person visits the $page" do |person, page| page_map = { "calendar list" => "/calendars" } @current_session = get_ivar(:session, person) @current_session.visits page_map[page] end

slide-96
SLIDE 96

Then

Then /(he|she) (should|should not) see (.*)/ do |_, yes_or_no, calendar| if yes_or_no == 'should' @current_session.response.should include_text(calendar) else @current_session.response.should_not include_text(calendar) end end

slide-97
SLIDE 97

Multiple Sessions

def create_user_named(login, password=login) User.find_by_login(login).destroy rescue nil user = User.create!( :login => login, :password => password, :password_confirmation => password, :email => "#{login}@company.com" ) yield user if block_given? user end def login_as(login, password=login) set_ivar :session, login, open_session { |user| user.visits "/sessions/new" user.fills_in :login, :with => login user.fills_in :password, :with => password user.clicks_button } end

slide-98
SLIDE 98

Multiple Sessions

module InstanceVariableHelpers def set_ivar(type, name, obj) instance_variable_set ivar_name(type, name), obj end def get_ivar(type, name) returning instance_variable_get(ivar_name(type, name)) do |obj| yield obj if block_given? end end private def ivar_name(type, name) "@#{type}_#{name.gsub(/[ -]/,'_').gsub('&','and')}" end end

slide-99
SLIDE 99

Questions?

slide-100
SLIDE 100

Thank You

  • rspec.info
  • blog.davidchelimsky.net
  • articulatedman.com