Writing Software not Code with Cucumber

263
With Ben Mabey Writing Software not code

description

Describes Outside-In development and Behvaiour Driven Development. Illustrates basic Cucumber usage within a Rails app and then goes over more advanced topics such as JS as web services.

Transcript of Writing Software not Code with Cucumber

Page 1: Writing Software not Code with Cucumber

With

Ben Mabey

Writing Software not code

Page 2: Writing Software not Code with Cucumber

With

Ben Mabey

Writing Software not code

Page 3: Writing Software not Code with Cucumber

With

Ben Mabey

Writing Software not code

Behaviour Driven Development

Page 4: Writing Software not Code with Cucumber

?

Page 5: Writing Software not Code with Cucumber
Page 6: Writing Software not Code with Cucumber

Tweet in the blanks...

"most software projects are like _ _ _ _ _ _ _ _"

#rubyhoedown #cucumber

Page 7: Writing Software not Code with Cucumber

"most software projects are like _ _ _ _ _ _ _ _"

#rubyhoedown #cucumber

Page 8: Writing Software not Code with Cucumber
Page 9: Writing Software not Code with Cucumber
Page 10: Writing Software not Code with Cucumber

So... why are software projects like “The Homer”?

Page 11: Writing Software not Code with Cucumber
Page 12: Writing Software not Code with Cucumber
Page 13: Writing Software not Code with Cucumber
Page 14: Writing Software not Code with Cucumber

Feature Devotion

TextPlacing emphasis on features instead of

overall outcome

Page 15: Writing Software not Code with Cucumber
Page 16: Writing Software not Code with Cucumber

Shingeo Shingo of Toyota says...

Page 17: Writing Software not Code with Cucumber

"Inspection to find defects is waste."

Page 18: Writing Software not Code with Cucumber

"Inspection to prevent defects is

essential."

"Inspection to find defects is waste."

Page 19: Writing Software not Code with Cucumber

56% of all bugs are introduced in requirements. (CHAOS Report)

Page 20: Writing Software not Code with Cucumber

Root Cause Analysis

Page 21: Writing Software not Code with Cucumber

Popping the Why Stack...

Page 22: Writing Software not Code with Cucumber
Page 23: Writing Software not Code with Cucumber

Protect Revenue

Increase Revenue

Manage Cost

Page 24: Writing Software not Code with Cucumber
Page 25: Writing Software not Code with Cucumber

Feature: title

In order to [Business Value]As a [Role]I want to [Some Action] (feature)

* not executed* documentation value* variant of contextra* business value up front

Page 26: Writing Software not Code with Cucumber

There is no template.What is important to have in narrative:

* business value * stakeholder role * user role * action to be taken by user

Page 27: Writing Software not Code with Cucumber

<rant>

Page 28: Writing Software not Code with Cucumber

With

Ben Mabey

Writing Software not code

Behaviour Driven Development

Page 29: Writing Software not Code with Cucumber

!= BDD

Page 30: Writing Software not Code with Cucumber

!= BDD

Page 31: Writing Software not Code with Cucumber

RSpec != BDD

Page 32: Writing Software not Code with Cucumber

RSpec != BDD

Page 33: Writing Software not Code with Cucumber

“All of these tools are great... but, in the end, tools are tools. While RSpec and Cucumber are optimized for BDD, using them

doesn’t automatically mean you’re doing BDD"

The RSpec Book

Page 34: Writing Software not Code with Cucumber

BDD is a mindset

not a tool set

Page 35: Writing Software not Code with Cucumber

</rant>

Page 36: Writing Software not Code with Cucumber

Feature: title

In order to [Business Value]As a [Role]I want to [Some Action] (feature)

* not executed* documentation value* variant of contextra* business value up front

Page 37: Writing Software not Code with Cucumber

Scenario: titleGiven [Context]When I do [Action]Then I should see [Outcome]

Page 38: Writing Software not Code with Cucumber

Scenario: titleGiven [Context]And [More Context]When I do [Action]And [Other Action]Then I should see [Outcome]But I should not see [Outcome]

Page 39: Writing Software not Code with Cucumber

project_root/| `-- features

Page 40: Writing Software not Code with Cucumber

project_root/| `-- features |-- awesomeness.feature |-- greatest_ever.feature

Page 41: Writing Software not Code with Cucumber

project_root/| `-- features |-- awesomeness.feature |-- greatest_ever.feature `-- support |-- env.rb `-- other_helpers.rb

Page 42: Writing Software not Code with Cucumber

project_root/| `-- features |-- awesomeness.feature |-- greatest_ever.feature `-- support |-- env.rb `-- other_helpers.rb |-- step_definitions | |-- domain_concept_A.rb | `-- domain_concept_B.rb

Page 43: Writing Software not Code with Cucumber

Step

Given a widget

Page 44: Writing Software not Code with Cucumber

Step

Given a widgetGiven /^a widget$/ do #codes go hereend

Definition

Page 45: Writing Software not Code with Cucumber

Step

Given a widgetGiven /^a widget$/ do #codes go hereend

Definition

Step Mother

Page 46: Writing Software not Code with Cucumber

Step

Given a widgetGiven /^a widget$/ do #codes go hereend

Definition

Step Mother

Page 47: Writing Software not Code with Cucumber

a.featureb.feature

Page 48: Writing Software not Code with Cucumber

a.featureb.feature

Page 49: Writing Software not Code with Cucumber

a.featureb.feature

Page 50: Writing Software not Code with Cucumber

a.featureb.feature 28+

Languages

Page 51: Writing Software not Code with Cucumber

a.featureb.feature

x_steps.rby_steps.rb

28+ Languages

Page 52: Writing Software not Code with Cucumber

a.featureb.feature

x_steps.rby_steps.rb

RSpec, Test::Unit, etc

28+ Languages

Page 53: Writing Software not Code with Cucumber

a.featureb.feature

x_steps.rby_steps.rb

Your Code

RSpec, Test::Unit, etc

28+ Languages

Page 54: Writing Software not Code with Cucumber

Not Just for Rails

Page 55: Writing Software not Code with Cucumber

Outside-In

Page 56: Writing Software not Code with Cucumber
Page 57: Writing Software not Code with Cucumber
Page 58: Writing Software not Code with Cucumber

Write Scenarios

Page 59: Writing Software not Code with Cucumber

Steps are pending

Page 60: Writing Software not Code with Cucumber

Write Step Definition

Page 61: Writing Software not Code with Cucumber

Go Down A Gear

Page 62: Writing Software not Code with Cucumber

RSpec, TestUnit, etc

Page 63: Writing Software not Code with Cucumber

Write Code Example(Unit Test)

Page 64: Writing Software not Code with Cucumber

Make Example Pass

Page 65: Writing Software not Code with Cucumber

REFACTOR!!

Page 66: Writing Software not Code with Cucumber

Where Are we?

Page 67: Writing Software not Code with Cucumber

Continue until...

Page 68: Writing Software not Code with Cucumber

REFACTORand

REPEAT

Page 69: Writing Software not Code with Cucumber

features/manage_my_wishes.feature

Feature: manage my wishes

In order to get more stuff As a greedy person I want to manage my wish list for my family members to view @proposed Scenario: add wish

@proposed Scenario: remove wish

@proposed Scenario: tweet wish

Page 70: Writing Software not Code with Cucumber

features/manage_my_wishes.feature

Feature: manage my wishes

In order to get more stuff As a greedy person I want to manage my wish list for my family members to view

@wip Scenario: add wish Given I am logged in When I make a "New car" wish Then "New car" should appear on my wish list

@proposedScenario: remove wish

@proposedScenario: tweet wish

Work In Progress

Page 71: Writing Software not Code with Cucumber

Workflow

Page 72: Writing Software not Code with Cucumber

Workflow

git branch -b add_wish_tracker#

Page 73: Writing Software not Code with Cucumber

Workflow

git branch -b add_wish_tracker#Tag Scenario or Feature with @wip

Page 74: Writing Software not Code with Cucumber

Workflow

git branch -b add_wish_tracker#Tag Scenario or Feature with @wip

cucumber --wip --tags @wip

Page 75: Writing Software not Code with Cucumber

Workflow

git branch -b add_wish_tracker#Tag Scenario or Feature with @wip

cucumber --wip --tags @wipDevelop it Outside-In

Page 76: Writing Software not Code with Cucumber

Workflow

git branch -b add_wish_tracker#Tag Scenario or Feature with @wip

cucumber --wip --tags @wipDevelop it Outside-In

git rebase ---interactive; git merge

Page 77: Writing Software not Code with Cucumber

Workflow

git branch -b add_wish_tracker#Tag Scenario or Feature with @wip

cucumber --wip --tags @wipDevelop it Outside-In

git rebase ---interactive; git mergeRepeat!

Page 78: Writing Software not Code with Cucumber

@wip on master?

Page 79: Writing Software not Code with Cucumber

$ rake -T cucumber

@wip on master?

Page 80: Writing Software not Code with Cucumber

$ rake -T cucumberrake cucumber:ok OR rake cucumber

@wip on master?

Page 81: Writing Software not Code with Cucumber

$ rake -T cucumberrake cucumber:ok OR rake cucumber

cucumber --tags ~@wip --strict

@wip on master?

Page 82: Writing Software not Code with Cucumber

$ rake -T cucumberrake cucumber:ok OR rake cucumber

cucumber --tags ~@wip --strict

@wip on master?Tag Exclusion

Page 83: Writing Software not Code with Cucumber

@wip on master?$ rake -T cucumber

Page 84: Writing Software not Code with Cucumber

@wip on master?$ rake -T cucumberrake cucumber:wip

Page 85: Writing Software not Code with Cucumber

cucumber --tags @wip:2 --wip

@wip on master?$ rake -T cucumberrake cucumber:wip

Page 86: Writing Software not Code with Cucumber

cucumber --tags @wip:2 --wip

@wip on master?$ rake -T cucumberrake cucumber:wip

Limit tags in flow

Page 87: Writing Software not Code with Cucumber

cucumber --tags @wip:2 --wip

@wip on master?$ rake -T cucumberrake cucumber:wip

Limit tags in flow

Expect failure - Success == Failure

Page 88: Writing Software not Code with Cucumber

@wip on master?$ rake -T cucumberrake cucumber:all

Runs both ok and wip -- great for CI

Page 89: Writing Software not Code with Cucumber

features/manage_my_wishes.feature

Feature: manage my wishes

In order to get more stuff As a greedy person I want to manage my wish list for my family members to view

@wip Scenario: add wish Given I am logged in When I make a "New car" wish Then "New car" should appear on my wish list

@proposedScenario: remove wish

@proposedScenario: tweet wish

Page 90: Writing Software not Code with Cucumber
Page 91: Writing Software not Code with Cucumber

Line # of scenario

Page 92: Writing Software not Code with Cucumber
Page 93: Writing Software not Code with Cucumber

Look Ma! backtraces!Given I am logged in #features/manage_my_wishes.feature:8

Page 94: Writing Software not Code with Cucumber
Page 95: Writing Software not Code with Cucumber
Page 96: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

end

Page 97: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

end

Test Data Builder / Object Mother

Page 98: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

end

spec/fixjour_builders.rb

Fixjour do define_builder(User) do |klass, overrides| klass.new( :email => "user#{counter(:user)}@email.com", :password => 'password', :password_confirmation => 'password' ) endend

Fixture Replacement, Fixjour, Factory Girl, etc

Page 99: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

end

Page 100: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

visit new_session_path fill_in "Email", :with => @current_user.email fill_in "Password", :with => valid_user_attributes["password"] click_buttonend

Page 101: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

visit new_session_path fill_in "Email", :with => @current_user.email fill_in "Password", :with => valid_user_attributes["password"] click_buttonend

Webrat / Awesomeness

Page 102: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

visit new_session_path fill_in "Email", :with => @current_user.email fill_in "Password", :with => valid_user_attributes["password"] click_buttonend

Webrat / Awesomeness

Page 103: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

visit new_session_path fill_in "Email", :with => @current_user.email fill_in "Password", :with => valid_user_attributes["password"] click_buttonend

features/support/env.rb

require 'webrat'

Webrat.configure do |config| config.mode = :railsend

Webrat / Awesomeness

Page 104: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

visit new_session_path fill_in "Email", :with => @current_user.email fill_in "Password", :with => valid_user_attributes["password"] click_buttonend

features/support/env.rb

require 'webrat'

Webrat.configure do |config| config.mode = :railsend

Adapter

Webrat / Awesomeness

Page 105: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

visit new_session_path fill_in "Email", :with => @current_user.email fill_in "Password", :with => valid_user_attributes["password"] click_buttonend

features/step_definitions/webrat_steps.rb

When /^I press "(.*)"$/ do |button| click_button(button)end

When /^I follow "(.*)"$/ do |link| click_link(link)end

When /^I fill in "(.*)" with "(.*)"$/ do |field, value| fill_in(field, :with => value) end

When /^I select "(.*)" from "(.*)"$/ do |value, field| select(value, :from => field) end

# Use this step in conjunction with Rail's datetime_select helper. For example:# When I select "December 25, 2008 10:00" as the date and time When /^I select "(.*)" as the date and time$/ do |time| select_datetime(time)end

# Use this step when using multiple datetime_select helpers on a page or # you want to specify which datetime to select. Given the following view:# <%= f.label :preferred %><br /># <%= f.datetime_select :preferred %># <%= f.label :alternative %><br /># <%= f.datetime_select :alternative %># The following steps would fill out the form:# When I select "November 23, 2004 11:20" as the "Preferred" data and time# And I select "November 25, 2004 10:30" as the "Alternative" data and timeWhen /^I select "(.*)" as the "(.*)" date and time$/ do |datetime, datetime_label| select_datetime(datetime, :from => datetime_label)end

# Use this step in conjunction with Rail's time_select helper. For example:# When I select "2:20PM" as the time# Note: Rail's default time helper provides 24-hour time-- not 12 hour time. Webrat# will convert the 2:20PM to 14:20 and then select it. When /^I select "(.*)" as the time$/ do |time| select_time(time)end

# Use this step when using multiple time_select helpers on a page or you want to# specify the name of the time on the form. For example:# When I select "7:30AM" as the "Gym" timeWhen /^I select "(.*)" as the "(.*)" time$/ do |time, time_label| select_time(time, :from => time_label)end

# Use this step in conjunction with Rail's date_select helper. For example:# When I select "February 20, 1981" as the dateWhen /^I select "(.*)" as the date$/ do |date| select_date(date)end

# Use this step when using multiple date_select helpers on one page or# you want to specify the name of the date on the form. For example:# When I select "April 26, 1982" as the "Date of Birth" dateWhen /^I select "(.*)" as the "(.*)" date$/ do |date, date_label| select_date(date, :from => date_label)end

When /^I check "(.*)"$/ do |field| check(field) end

When /^I uncheck "(.*)"$/ do |field| uncheck(field) end

When /^I choose "(.*)"$/ do |field| choose(field)end

When /^I attach the file at "(.*)" to "(.*)" $/ do |path, field| attach_file(field, path)end

Then /^I should see "(.*)"$/ do |text| response.should contain(text)end

Then /^I should not see "(.*)"$/ do |text| response.should_not contain(text)end

Then /^the "(.*)" checkbox should be checked$/ do |label| field_labeled(label).should be_checkedend

Webrat / Awesomeness

20+ Steps Out-of-box

Page 106: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

visit new_session_path fill_in "Email", :with => @current_user.email fill_in "Password", :with => valid_user_attributes["password"] click_buttonend

Page 107: Writing Software not Code with Cucumber

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

visit new_session_path fill_in "Email", :with => @current_user.email fill_in "Password", :with => valid_user_attributes["password"] click_button

end

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do

# make sure we have actually logged in- so we fail fast if not session[:user_id].should == @current_user.idend

Page 108: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do

# make sure we have actually logged in- so we fail fast if not controller.current_user.should == @current_userend

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

visit new_session_path fill_in "Email", :with => @current_user.email fill_in "Password", :with => valid_user_attributes["password"] click_button session[:user_id].should == @current_user.id

Page 109: Writing Software not Code with Cucumber

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

visit new_session_path fill_in "Email", :with => @current_user.email fill_in "Password", :with => valid_user_attributes["password"] click_button

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do

# make sure we have actually logged in- so we fail fast if not session[:user_id].should == @current_user.id controller.current_user.should == @current_user response.should contain("Signed in successfully")end

Specify outcome, not implementation.

Page 110: Writing Software not Code with Cucumber

features/step_definitions/user_steps.rb

Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true)

visit new_session_path fill_in "Email", :with => @current_user.email fill_in "Password", :with => valid_user_attributes["password"] click_button # make sure we have actually logged in- so we fail fast if not response.should contain("Signed in successfully")end

Page 111: Writing Software not Code with Cucumber
Page 112: Writing Software not Code with Cucumber

No route matches “/sessions/create” with{:method=>:post} (ActionController::RoutingError)

Page 113: Writing Software not Code with Cucumber

I’m going to cheat...

Page 114: Writing Software not Code with Cucumber

I’m going to cheat...$ gem install thoughtbot-clearance$ ./script generate clearance$ ./script generate clearance_features

Page 116: Writing Software not Code with Cucumber
Page 117: Writing Software not Code with Cucumber

features/step_definitions/wish_steps.rb

When /^I make a "(.+)" wish$/ do |wish| end

Then /^(.+) should appear on my wish list$/ do |wish| end

Page 118: Writing Software not Code with Cucumber

features/step_definitions/wish_steps.rb

When /^I make a "(.+)" wish$/ do |wish| end

Then /^(.+) should appear on my wish list$/ do |wish| end

Regexp Capture -> Yielded Variable

Page 119: Writing Software not Code with Cucumber

features/step_definitions/wish_steps.rb

When /^I make a "(.+)" wish$/ do |wish| visit "/wishes" click_link "Make a wish" fill_in "Wish", :with => wish click_buttonend

Then /^(.+) should appear on my wish list$/ do |wish| end

Page 120: Writing Software not Code with Cucumber

features/step_definitions/wish_steps.rb

Then /^(.+) should appear on my wish list$/ do |wish| response.should contain("Your wish has been added!") response.should contain(wish)end

When /^I make a "(.+)" wish$/ do |wish| visit "/wishes" click_link "Make a wish" fill_in "Wish", :with => wish click_buttonend

Page 121: Writing Software not Code with Cucumber

features/step_definitions/wish_steps.rb

When /^I make a "(.+)" wish$/ do |wish| visit "/wishes" click_link "Make a wish" fill_in "Wish", :with => wish click_buttonend

Then /^(.+) should appear on my wish list$/ do |wish| response.should contain("Your wish has been added!") response.should contain(wish)end

No route matches “/wishes” with{:method=>:get} (ActionController::RoutingError)

Page 122: Writing Software not Code with Cucumber

config/routes.rb

ActionController::Routing::Routes.draw do |map| map.resources :wishes

Page 123: Writing Software not Code with Cucumber

config/routes.rb

ActionController::Routing::Routes.draw do |map| map.resources :wishes

When I make a “New car” wishuninitialized constant WishesController (NameError)

Page 124: Writing Software not Code with Cucumber

config/routes.rb

ActionController::Routing::Routes.draw do |map| map.resources :wishes

$./script generate rspec_controller new create

Page 125: Writing Software not Code with Cucumber

config/routes.rb

ActionController::Routing::Routes.draw do |map| map.resources :wishes

When I make a “New car” wishCould not find link with text or title orid “Make a wish” (Webrat::NotFoundError)

Page 126: Writing Software not Code with Cucumber

app/views/wishes/index.html.erb

<%= link_to "Make a wish", new_wish_path %>

Page 127: Writing Software not Code with Cucumber

app/views/wishes/index.html.erb

<%= link_to "Make a wish", new_wish_path %>

When I make a “New car” wish Could not find field: “Wish” (Webrat::NotFoundError)

Page 128: Writing Software not Code with Cucumber

features/step_definitions/wish_steps.rb

When /^I make a "(.+)" wish$/ do |wish| visit "/wishes" click_link "Make a wish" fill_in "Wish", :with => wish click_buttonend

Page 129: Writing Software not Code with Cucumber

features/step_definitions/wish_steps.rb

When /^I make a "(.+)" wish$/ do |wish| visit "/wishes" click_link "Make a wish" fill_in "Wish", :with => wish click_buttonend

app/views/wishes/new.html.erb

<% form_for :wish do |f| %> <%= f.label :name, "Wish" %> <%= f.text_field :name %> <%= submit_tag "Make the wish!" %><% end %>

Page 130: Writing Software not Code with Cucumber

fill_in "Wish", :with => wish

<%= f.label :name, "Wish" %> <%= f.text_field :name %>

features/step_definitions/wish_steps.rb

When /^I make a "(.+)" wish$/ do |wish| visit "/wishes" click_link "Make a wish" fill_in "Wish", :with => wish click_buttonend

app/views/wishes/new.html.erb

<% form_for :wish do |f| %> <%= submit_tag "Make the wish!" %><% end %>

Location Strategy FTW!

Page 131: Writing Software not Code with Cucumber

View

Controller

Page 132: Writing Software not Code with Cucumber

spec/controllers/wishes_controller_spec.rb

describe WishesController do describe "POST / (#create)" do

endend

Page 133: Writing Software not Code with Cucumber

spec/controllers/wishes_controller_spec.rb

describe WishesController do describe "POST / (#create)" do it "creates a new wish for the user with the params" do user = mock_model(User, :wishes => mock("wishes association")) controller.stub!(:current_user).and_return(user) user.wishes.should_receive(:create).with(wish_params)

post :create, 'wish' => {'name' => 'Dog'} end endend

Page 134: Writing Software not Code with Cucumber

app/controllers/wishes_controller.rb

class WishesController < ApplicationController

def create current_user.wishes.create(params['wish']) end

end

Page 135: Writing Software not Code with Cucumber

spec/controllers/wishes_controller_spec.rb

describe WishesController do describe "POST / (#create)" do before(:each) do .....

endend

spec/controllers/wishes_controller_spec.rb

it "redirects the user to their wish list" do do_post response.should redirect_to(wishes_path) end

Page 136: Writing Software not Code with Cucumber

app/controllers/wishes_controller.rb

def create current_user.wishes.create(params['wish']) redirect_to :action => :index end

Page 137: Writing Software not Code with Cucumber

View

Controller

Model

Page 138: Writing Software not Code with Cucumber

app/controllers/wishes_controller.rb

def create current_user.wishes.create(params['wish']) redirect_to :action => :index end

When I make a “New car” wishundefined method `wishes` for #<User:0x268e898> (NoMethodError)

Page 139: Writing Software not Code with Cucumber

app/controllers/wishes_controller.rb

def create current_user.wishes.create(params['wish']) redirect_to :action => :index end

$./script generate rspec_model wish name:string user_id:integer

Page 140: Writing Software not Code with Cucumber

app/models/wish.rb

class Wish < ActiveRecord::Base belongs_to :userend

app/models/user.rb

class User < ActiveRecord::Base include Clearance::App::Models::User has_many :wishesend

Page 141: Writing Software not Code with Cucumber

app/models/wish.rb

class Wish < ActiveRecord::Base belongs_to :userend

app/models/user.rb

class User < ActiveRecord::Base include Clearance::App::Models::User has_many :wishesend

When I make a “New car” wishThen “New car” should appear on my wishexpected the following element’s content to include “Your wish has been added!”

Page 142: Writing Software not Code with Cucumber

spec/controllers/wishes_controller_spec.rb

it "notifies the user of creation via the flash" do do_post flash[:success].should == "Your wish has been added!"end

Page 143: Writing Software not Code with Cucumber

current_user.wishes.create(params['wish']) redirect_to :action => :index

app/controllers/wishes_controller.rb

def create flash[:success] = "Your wish has been added!" end

spec/controllers/wishes_controller_spec.rb

it "notifies the user of creation via the flash" do do_post flash[:success].should == "Your wish has been added!"end

Page 144: Writing Software not Code with Cucumber

current_user.wishes.create(params['wish']) redirect_to :action => :index

app/controllers/wishes_controller.rb

def create flash[:success] = "Your wish has been added!" end

spec/controllers/wishes_controller_spec.rb

it "should notifies the user of creation via the flash" do do_post flash[:success].should == "Your wish has been added!"end

Then “New car” should appear on my wishexpected the following element’s content to include “New car”

Page 145: Writing Software not Code with Cucumber

app/views/wishes/index.html.erb

<ul><% @wishes.each do |wish| %> <li><%= wish.name %></li><% end %></ul>

Page 146: Writing Software not Code with Cucumber

spec/controllers/wishes_controller_spec.rb

describe "GET / (#index)" do def do_get get :index end

it "assigns the user's wishes to the view" do do_get assigns[:wishes].should == @current_user.wishes end

end

Page 147: Writing Software not Code with Cucumber

app/controllers/wishes_controller.rb

def index @wishes = current_user.wishes end

Page 148: Writing Software not Code with Cucumber
Page 149: Writing Software not Code with Cucumber

FAQ

How do I test JS and

AJAX?

Page 150: Writing Software not Code with Cucumber
Page 151: Writing Software not Code with Cucumber

Slow

Fast

Page 152: Writing Software not Code with Cucumber

Slow

Fast

Integrated

Isolated

Page 153: Writing Software not Code with Cucumber

Slow

Fast

Integrated

Isolated

Page 154: Writing Software not Code with Cucumber

Slow

Fast

Integrated

Isolated

Page 155: Writing Software not Code with Cucumber

Slow

Fast

Integrated

Isolated

Page 156: Writing Software not Code with Cucumber

Slow

Fast

Integrated

Isolated

Page 157: Writing Software not Code with Cucumber

Slow

Fast

Page 158: Writing Software not Code with Cucumber

Slow

Fast Joyful

Page 159: Writing Software not Code with Cucumber

Slow

Fast

Painful

Joyful

Page 160: Writing Software not Code with Cucumber

Slow

Fast

Painful

Joyful

Celerity

Page 161: Writing Software not Code with Cucumber

Celerity

Page 162: Writing Software not Code with Cucumber

CelerityHtmlUnit

Page 163: Writing Software not Code with Cucumber

CelerityHtmlUnit

Page 164: Writing Software not Code with Cucumber

CelerityHtmlUnit

Page 165: Writing Software not Code with Cucumber

CelerityHtmlUnit

Page 166: Writing Software not Code with Cucumber

require "rubygems"require "celerity"

browser = Celerity::Browser.new

browser.goto('http://www.google.com')browser.text_field(:name, 'q').value = 'Celerity'browser.button(:name, 'btnG').click

puts "yay" if browser.text.include? 'celerity.rubyforge.org'

Page 167: Writing Software not Code with Cucumber

What if I use MRI?

Page 169: Writing Software not Code with Cucumber

require "rubygems"require "culerity"

culerity_server = Culerity::run_server

browser = Culerity::RemoteBrowserProxy.new(culerity_server)browser.goto('http://www.google.com')browser.text_field(:name, 'q').value = 'Celerity'browser.button(:name, 'btnG').click

puts "yay" if browser.text.include? 'celerity.rubyforge.org'

Page 172: Writing Software not Code with Cucumber

CodeNote

http://github.com/bmabey/codenote

Page 173: Writing Software not Code with Cucumber

Feature: CLI Server In order to save me time and headaches As a presenter of code I create a presentation in plaintext a'la Slidedown (from Pat Nakajima) and have CodeNote serve it up for me

Scenario: basic presentation loading and viewing Given that the codenote server is not running And a file named "presentation.md" with: """ !TITLE My Presentation !PRESENTER Ben Mabey # This is the title slide !SLIDE # This is second slide... """ When I run "codenote_load presentation.md" And I run "codenote" And I visit the servers address

For example of how to test CLI tools take a look at CodeNote on github.

RSpec and Cucumber also have good examples of how to do this.

Page 174: Writing Software not Code with Cucumber

Feature: Twitter Quiz In order to encourage audience participation where 90% of the audience is hacking on laptops As a presenter I want audience members To answer certain questions via twitter

Page 175: Writing Software not Code with Cucumber

Feature: Twitter Quiz In order to encourage audience participation where 90% of the audience is hacking on laptops As a presenter I want audience members To answer certain questions via twitter

@proposed Scenario: waiting for an answer

@proposed Scenario: winner is displayed

@proposed Scenario: fail whale

@proposed Scenario: network timeout

Page 176: Writing Software not Code with Cucumber

Feature: Twitter Quiz In order to encourage audience participation where 90% of the audience is hacking on laptops As a presenter I want audience members To answer certain questions via twitter

@wip Scenario: waiting for an answer

Page 177: Writing Software not Code with Cucumber

@wipScenario: waiting for an answer Given the following presentation """ !TITLE American History !PRESENTER David McCullough # Wanna win a prize? ### You'll have to answer a question... ### in a tweet! First correct tweet wins! !SLIDE # Who shot Alexander Hamilton? ## You must use #free_stuff in your tweet. !DYNAMIC-SLIDE TwitterQuiz '#free_stuff "aaron burr"' !SLIDE Okay, that was fun. Lets actually start now. """

Page 178: Writing Software not Code with Cucumber

!DYNAMIC-SLIDE TwitterQuiz '#free_stuff "aaron burr"'

@wipScenario: waiting for an answer Given the following presentation """ !TITLE American History !PRESENTER David McCullough # Wanna win a prize? ### You'll have to answer a question... ### in a tweet! First correct tweet wins! !SLIDE # Who shot Alexander Hamilton? ## You must use #free_stuff in your tweet. !SLIDE Okay, that was fun. Lets actually start now. """

Page 179: Writing Software not Code with Cucumber

@wipScenario: waiting for an answer Given the following presentation ... And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search When the presenter goes to the 3rd slide And I go to the 3rd slide Then I should see "And the winner is..." And I should see an ajax spinner

Page 180: Writing Software not Code with Cucumber

Given /the following presentation$/ do |presentation| end

Given the following presentation """ blah, blah """

Page 181: Writing Software not Code with Cucumber

Given /the following presentation$/ do |presentation| end

Given the following presentation """ blah, blah """

Yields the multi-line string

Page 182: Writing Software not Code with Cucumber

CodeNote::PresentationLoader.setup(presentation)

Given /the following presentation$/ do |presentation| end

Given the following presentation """ blah, blah """

Page 183: Writing Software not Code with Cucumber

RSpec Cycle

Page 184: Writing Software not Code with Cucumber

And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search

Page 185: Writing Software not Code with Cucumber

FAQ

How do I test webservices?

Page 187: Writing Software not Code with Cucumber

http://github.com/chrisk/fakeweb

page = `curl -is http://www.google.com/`FakeWeb.register_uri(:get, "http://www.google.com/",

:response => page)

Net::HTTP.get(URI.parse("http://www.google.com/")) # => Full response, including headers

Page 188: Writing Software not Code with Cucumber

Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| end

And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search

Page 189: Writing Software not Code with Cucumber

Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))end

And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search

Page 190: Writing Software not Code with Cucumber

Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))end

Page 191: Writing Software not Code with Cucumber

Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))end

Helpers

Page 192: Writing Software not Code with Cucumber

Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))end

def search_url_for(query) "http://search.twitter.com/search.json?q=#{CGI.escape(query)}" end

def canned_response_for(query) .... return file_path end

Helpers

Page 193: Writing Software not Code with Cucumber

Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))end

def search_url_for(query) "http://search.twitter.com/search.json?q=#{CGI.escape(query)}" end

def canned_response_for(query) .... return file_path end

Page 194: Writing Software not Code with Cucumber

Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))end

def search_url_for(query) "http://search.twitter.com/search.json?q=#{CGI.escape(query)}" end

def canned_response_for(query) .... return file_path end

Page 195: Writing Software not Code with Cucumber

Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))end

def search_url_for(query) "http://search.twitter.com/search.json?q=#{CGI.escape(query)}" end

def canned_response_for(query) .... return file_path end

“Every time you monkeypatch Object, a kitten dies.”

Page 196: Writing Software not Code with Cucumber

Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))end

module TwitterHelpers def search_url_for(query) "http://search.twitter.com/search.json?q=#{CGI.escape(query)}" end

def canned_response_for(query) .... return file_path end

end

Page 197: Writing Software not Code with Cucumber

Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))end

module TwitterHelpers def search_url_for(query) "http://search.twitter.com/search.json?q=#{CGI.escape(query)}" end

def canned_response_for(query) .... return file_path end

end

World(TwitterHelpers)

Page 198: Writing Software not Code with Cucumber

Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))end

module TwitterHelpers def search_url_for(query) "http://search.twitter.com/search.json?q=#{CGI.escape(query)}" end

def canned_response_for(query) .... return file_path end

end

World(TwitterHelpers)

Page 199: Writing Software not Code with Cucumber

When the presenter goes to the 3rd slide

Page 200: Writing Software not Code with Cucumber

When the presenter goes to the 3rd slide

When /the presenter goes to the (\d+)(?:st|nd|rd|th) slide$/ do |slide_number|

presenter_browser.goto path('/') (slide_number.to_i - 1).times do presenter_browser.link(:text, "Next").click endend

Page 201: Writing Software not Code with Cucumber

When the presenter goes to the 3rd slide

When /the presenter goes to the (\d+)(?:st|nd|rd|th) slide$/ do |slide_number|

presenter_browser.goto path('/') (slide_number.to_i - 1).times do presenter_browser.link(:text, "Next").click endend

Presenter has own browser,multiple sessions!

Page 202: Writing Software not Code with Cucumber

And I go to the 3rd slide Then I should see "And the winner is..."

Page 203: Writing Software not Code with Cucumber

And I go to the 3rd slide Then I should see "And the winner is..."

When /I go to the (\d+)(?:st|nd|rd|th) slide$/ do |slide_number| browser.goto path("/slides/#{slide_number}")end

Page 204: Writing Software not Code with Cucumber

And I go to the 3rd slide Then I should see "And the winner is..."

When /I go to the (\d+)(?:st|nd|rd|th) slide$/ do |slide_number| browser.goto path("/slides/#{slide_number}")end

Then /I should see "(["]*)"$/ do |text| browser.should contain(text)end

Page 205: Writing Software not Code with Cucumber

And I should see an ajax spinner

Page 206: Writing Software not Code with Cucumber

And I should see an ajax spinner

Then /I should see an ajax spinner$/ do browser.image(:id, 'spinner').exists?.should be_trueend

Page 207: Writing Software not Code with Cucumber

Brief RSpec cycle?

Page 208: Writing Software not Code with Cucumber

Scenario: waiting for an answer Given the following presentation ... And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search When the presenter goes to the 3rd slide And I go to the 3rd slide Then I should see "And the winner is..." And I should see an ajax spinner

Page 209: Writing Software not Code with Cucumber

Feature: Twitter Quiz In order to encourage audience participation where 90% of the audience is hacking on laptops As a presenter I want audience members To answer certain questions via twitter

Scenario: waiting for an answer ..... @wip Scenario: winner is displayed

Page 210: Writing Software not Code with Cucumber

@wipScenario: winner is displayed Given the following presentation ... And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search

Page 211: Writing Software not Code with Cucumber

@wipScenario: winner is displayed Given the following presentation ... And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search

Duplication of context!

Page 212: Writing Software not Code with Cucumber

Feature: Twitter Quiz ... Background: A presentation with a Twitter Quiz

Given the following presentation """ blah, blah """ And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search

Scenario: waiting for an answer When the presenter goes to the 3rd slide And I go to the 3rd slide Then I should see "And the winner is..." And I should see an ajax spinner

@wip Scenario: winner is displayed

Extract to ‘Background’

Page 213: Writing Software not Code with Cucumber

@wipScenario: winner is displayed When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search | From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago | And the presenter goes to the 3rd slide And I go to the 3rd slide

Then I should see @jefferson's tweet along with his avatar

Page 214: Writing Software not Code with Cucumber

When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search

| From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |

Page 215: Writing Software not Code with Cucumber

When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table|

end

When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search

| From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |

Page 216: Writing Software not Code with Cucumber

When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table|

end

When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search

Cucumber::AST::Table

| From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |

Page 217: Writing Software not Code with Cucumber

When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))

end

When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search

| From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |

Page 218: Writing Software not Code with Cucumber

When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))

end

Umm... that won’t work.

When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search

| From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |

Page 219: Writing Software not Code with Cucumber

When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table| FakeWeb.register_uri(:get, search_url_for(query), :body => canned_response_for(query))

end

What I would really like is atest data builder/factory for

twitter searches...

When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search

| From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |

Page 220: Writing Software not Code with Cucumber

http://github.com/bmabey/faketwitterrequire 'faketwitter'

FakeTwitter.register_search("#cheese", {:results => [{:text => "#cheese is good"}]})

require 'twitter_search'TwitterSearch::Client.new('').query('#cheese')=> [#<TwitterSearch::Tweet:0x196cef8 @id=1, @text="#cheese is good", @created_at="Fri, 21 Aug 2009 09:31:27 +0000", @to_user_id=nil, @from_user_id=1, @to_user=nil, @source="<a href="http://twitter.com/">web</a>", @iso_language_code="en", @from_user="jojo", @language="en", @profile_image_url="http://s3.amazonaws.com/twitter_production/profile_images/1/photo.jpg">]

Page 221: Writing Software not Code with Cucumber

When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table| FakeTwitter.register_search(query, { :results => tweet_table.hashes})

end

When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search

| From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |

Page 222: Writing Software not Code with Cucumber

When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table| FakeTwitter.register_search(query, { :results => tweet_table.hashes})

end

When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search

| From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |

Our headers and columnsaren’t compatible with API.

Page 223: Writing Software not Code with Cucumber

When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table| tweet_table.map_headers! do |header| header.downcase.gsub(' ','_') end

FakeTwitter.register_search(query, {:results => tweet_table.hashes})

end

When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search

| From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |

Page 224: Writing Software not Code with Cucumber

When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table| tweet_table.map_headers! do |header| header.downcase.gsub(' ','_') end

tweet_table.map_column!('created_at') do |relative_time| interpret_time(relative_time) end

FakeTwitter.register_search(query, {:results => tweet_table.hashes})

end

When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search

| From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |

Page 225: Writing Software not Code with Cucumber

@wipScenario: winner is displayed When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search | From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago | And the presenter goes to the 3rd slide And I go to the 3rd slide Then I should see @jefferson's tweet along with his avatar

Page 226: Writing Software not Code with Cucumber

Then %r{I should see @([']+)'s tweet along with (?:his|her) avatar$} do |user|

tweet = FakeTwitter.tweets_from(user).first browser.should contain(tweet['text'], :wait => 10) browser.should have_image(:src, tweet['profile_image_url'])end

Timeout

Page 227: Writing Software not Code with Cucumber

Then %r{I should see @([']+)'s tweet along with (?:his|her) avatar$} do |user|

tweet = FakeTwitter.tweets_from(user).first browser.should contain(tweet['text'], :wait => 10) browser.should have_image(:src, tweet['profile_image_url'])end

Spec::Matchers.define :contain do |text, options| match do |browser| options[:wait] ||= 0 browser.wait_until(options[:wait]) do browser.text.include?(text) end endend

Page 228: Writing Software not Code with Cucumber

Then %r{I should see @([']+)'s tweet along with (?:his|her) avatar$} do |user|

tweet = FakeTwitter.tweets_from(user).first browser.should contain(tweet['text'], :wait => 10) browser.should have_image(:src, tweet['profile_image_url'])end

Spec::Matchers.define :contain do |text, options| match do |browser| options[:wait] ||= 0 browser.wait_until(options[:wait]) do browser.text.include?(text) end endend

Keep trying after sleepinguntil it times out

Page 229: Writing Software not Code with Cucumber

RSpec Cycle

Page 230: Writing Software not Code with Cucumber

Scenario: winner is displayed When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search | From User | Text | Created At | | @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago | | @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago | And the presenter goes to the 3rd slide And I go to the 3rd slide

Then I should see @jefferson's tweet along with his avatar

Page 231: Writing Software not Code with Cucumber

Demo!

Page 232: Writing Software not Code with Cucumber

More tricks...

Page 233: Writing Software not Code with Cucumber
Page 234: Writing Software not Code with Cucumber

Scenario: view members list Given the following wishes exist | Wish | Family Member | | Laptop | Thomas | | Nintendo Wii | Candace | | CHEEZBURGER | FuzzBuzz | When I view the wish list for "Candace"

Then I should see the following wishes | Wish | | Nintendo Wii |

Page 235: Writing Software not Code with Cucumber

Given the following wishes exist | Wish | Family Member | | Laptop | Thomas | | Nintendo Wii | Candace | | CHEEZBURGER | FuzzBuzz |

features/step_definitions/wish_steps.rb

Given /^the following wishes exist$/ do |table|

endend

Page 236: Writing Software not Code with Cucumber

| Wish | Family Member | features/step_definitions/wish_steps.rb

Given /^the following wishes exist$/ do |table| table.hashes.each do |row| member = User.find_by_name(row["Family Member"]) || create_user(:name => row["Family Member"])

member.wishes.create!(:name => row["Wish"]) endend

Given the following wishes exist | Wish | Family Member | | Laptop | Thomas | | Nintendo Wii | Candace | | CHEEZBURGER | FuzzBuzz |

Page 237: Writing Software not Code with Cucumber

Table Diffinghttp://wiki.github.com/aslakhellesoy/cucumber/multiline-step-arguments

Page 238: Writing Software not Code with Cucumber

Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers

Scenario Outline: Add two numbers Given I have entered <input_1> into the calculator And I have entered <input_2> into the calculator When I press <button> Then the result should be <output> on the screen

Scenarios: | input_1 | input_2 | button | output | | 20 | 30 | add | 50 | | 2 | 5 | add | 7 | | 0 | 40 | add | 40 |

<input_1> <input_2> <button> <output>

| input_1 | input_2 | button | output |

Page 239: Writing Software not Code with Cucumber

Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers

Scenario Outline: Add two numbers Given I have entered <input_1> into the calculator And I have entered <input_2> into the calculator When I press <button> Then the result should be <output> on the screen

Scenarios: addition | input_1 | input_2 | button | output | | 20 | 30 | add | 50 | | 2 | 5 | add | 7 | Scenarios: subtraction | 0 | 40 | minus | -40 |

Page 240: Writing Software not Code with Cucumber

Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers

Scenario Outline: Add two numbers Given I have entered <input_1> into the calculator And I have entered <input_2> into the calculator When I press <button> Then the result should be <output> on the screen

Scenarios: | input_1 | input_2 | button | output | | 20 | 30 | add | 50 | | 2 | 5 | add | 7 | | 0 | 40 | add | 40 |

<input_1> <input_2> <button> <output>

| 20 | 30 | add | 50 |

Page 241: Writing Software not Code with Cucumber

<input_1> <input_2> <button> <output>

| 2 | 5 | add | 7 |

Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers

Scenario Outline: Add two numbers Given I have entered <input_1> into the calculator And I have entered <input_2> into the calculator When I press <button> Then the result should be <output> on the screen

Scenarios: | input_1 | input_2 | button | output | | 20 | 30 | add | 50 | | 2 | 5 | add | 7 | | 0 | 40 | add | 40 |

Page 242: Writing Software not Code with Cucumber

<input_1> <input_2> <button> <output>

| 0 | 40 | add | 40 |

Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers

Scenario Outline: Add two numbers Given I have entered <input_1> into the calculator And I have entered <input_2> into the calculator When I press <button> Then the result should be <output> on the screen

Scenarios: | input_1 | input_2 | button | output | | 20 | 30 | add | 50 | | 2 | 5 | add | 7 | | 0 | 40 | add | 40 |

Page 243: Writing Software not Code with Cucumber

When /^I view the wish list for "(.+)"$/ do |user_name| Given "I am logged in" visit "/wishes/#{user_name}"end

Steps Within Steps

Page 244: Writing Software not Code with Cucumber

When /^I view the wish list for "(.+)"$/ do |user_name| visit "/wishes/#{user_name}"end

Steps Within Steps Given "I am logged in"

Page 245: Writing Software not Code with Cucumber

HooksBefore doend

After do |scenario|end

World doend

World(MyModule)World(HerModule)

Page 246: Writing Software not Code with Cucumber

Tagged HooksBefore('@im_special', '@me_too') do @icecream = trueend

@me_tooFeature: Lorem Scenario: Ipsum Scenario: Dolor

Feature: Sit @im_special Scenario: Amet Scenario: Consec

Page 247: Writing Software not Code with Cucumber

Spork

http://github.com/timcharper/spork

Sick of slow loading times? Spork will load your main environment once. It then runs a DRB server so cucumber (or RSpec) can run against it with the --drb flag. For each test run Spork forks a child process to run them in a clean memory state. So.. it is a DRb server that forks.. hence Spork. :)

Page 248: Writing Software not Code with Cucumber

Drinking the Cucumber Kool-Aid?

Page 249: Writing Software not Code with Cucumber

Integration tests are a scam

http://www.jbrains.ca/permalink/239

J. B. Rainsberger

Obviously, I don’t agree with this 100%. But he has some valid points. Integrations tests are not a replacement for good unit tests. Use cucumber for happy paths. Use lower level tests for design and to isolate object behavior.

Page 250: Writing Software not Code with Cucumber

Cucumber is a good hammer

Page 251: Writing Software not Code with Cucumber

Cucumber is a good hammer

Not everything is a nail

Page 252: Writing Software not Code with Cucumber

I can skp teh unit testz?

Page 253: Writing Software not Code with Cucumber

Acceptance Tests

Application LevelFor CustomersSlowGood confidencePrevent against regression

Unit Tests

Object Level- Isolated!For developersFAST! (should be at least)- Tighter Feedback LoopMore about design!!!!!!!!!!!!

Will need both gears! Some things are easier to test at the application level and vice-versa.

Page 254: Writing Software not Code with Cucumber

SRSLY?Model specs,Controller Specs,and view specs!?

Page 255: Writing Software not Code with Cucumber
Page 256: Writing Software not Code with Cucumber
Page 257: Writing Software not Code with Cucumber
Page 258: Writing Software not Code with Cucumber

W

Page 259: Writing Software not Code with Cucumber

M

Page 260: Writing Software not Code with Cucumber

More tests == More Maintenance

Page 261: Writing Software not Code with Cucumber

Test Value =Design +

Documentation +Defence (regression)

Page 262: Writing Software not Code with Cucumber

if test.value > test.cost Suite.add(test)end

Page 263: Writing Software not Code with Cucumber

KTHXBYE!BenMabey.com

github.com/bmabey

Twitter: bmabeyIRC: mabes