SlideShare a Scribd company logo
1 of 263
Download to read offline
Writing Software
    not code
      With




   Ben Mabey
Writing Software
    not code
      With




   Ben Mabey
Writing Software
    not code
      With
Behaviour Driven
  Development
   Ben Mabey
?
Tweet in the
      blanks...
"most software projects are
      like _ _ _ _ _ _ _ _"
 #rubyhoedown #cucumber
"most software projects are
      like _ _ _ _ _ _ _ _"
 #rubyhoedown #cucumber
So... why are
software projects
like “The Homer”?
Feature
  Devotion
       Text
Placing emphasis on
features instead of
 overall outcome
Shingeo Shingo of
  Toyota says...
"Inspection to find
defects is waste."
"Inspection to find
defects is waste."

  "Inspection to
prevent defects is
    essential."
56% of all bugs are introduced in
requirements. (CHAOS Report)
Root Cause
 Analysis
Popping the Why
    Stack...
Protect Revenue

Increase Revenue

Manage Cost
* not executed
                  * documentation value

Feature: title    * variant of contextra
                  * business value up front




In order to [Business Value]
As a [Role]
I want to [Some Action] (feature)
There is no template.
What is important to
have in narrative:

 * business value
 * stakeholder role
 * user role
 * action to be taken by
user
<rant>
Writing Software
    not code
      With
Behaviour Driven
  Development
   Ben Mabey
!= BDD
!= BDD
RSpec != BDD
RSpec != BDD
“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
BDD is a
  mindset
not a tool set
</rant>
* not executed
                  * documentation value

Feature: title    * variant of contextra
                  * business value up front




In order to [Business Value]
As a [Role]
I want to [Some Action] (feature)
Scenario: title
Given [Context]
When I do [Action]
Then I should see [Outcome]
Scenario: title
Given [Context]
And [More Context]
When I do [Action]
And [Other Action]
Then I should see [Outcome]
But I should not see [Outcome]
project_root/
|
`-- features
project_root/
|
`-- features
    |-- awesomeness.feature
    |-- greatest_ever.feature
project_root/
|
`-- features
    |-- awesomeness.feature
    |-- greatest_ever.feature
    `-- support
        |-- env.rb
        `-- other_helpers.rb
project_root/
|
`-- features
    |-- awesomeness.feature
    |-- greatest_ever.feature
    `-- support
        |-- env.rb
        `-- other_helpers.rb
    |-- step_definitions
    |   |-- domain_concept_A.rb
    |   `-- domain_concept_B.rb
Step

Given a widget
Step         Definition

                 Given /^a widget$/ do
Given a widget     #codes go here
                 end
Step           Definition
             Step Mother

                   Given /^a widget$/ do
Given a widget       #codes go here
                   end
Step           Definition
             Step Mother

                   Given /^a widget$/ do
Given a widget       #codes go here
                   end
28+
Languages
28+
Languages
28+
                         Languages




RSpec, Test::Unit, etc
28+
                         Languages



                         Your Code
RSpec, Test::Unit, etc
Not Just for Rails
Outside-In
Write Scenarios
Steps are pending
Write Step Definition
Go Down A Gear
RSpec, TestUnit, etc
Write Code Example
    (Unit Test)
Make Example Pass
REFACTOR!!
Where Are we?
Continue until...
REFACTOR
   and
 REPEAT
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
features/manage_my_wishes.feature
Feature: manage my wishes

 In order to get more stuff
              Work In Progress
 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

  @proposed
  Scenario: remove wish

  @proposed
  Scenario: tweet wish
Workflow
Workflow
git branch -b add_wish_tracker#
Workflow
 git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
Workflow
 git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
   cucumber --wip --tags @wip
Workflow
 git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
   cucumber --wip --tags @wip
        Develop it Outside-In
Workflow
 git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
    cucumber --wip --tags @wip
        Develop it Outside-In
git rebase ---interactive; git merge
Workflow
 git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
    cucumber --wip --tags @wip
        Develop it Outside-In
git rebase ---interactive; git merge
               Repeat!
@wip on master?
@wip on master?
$ rake -T cucumber
@wip on master?
$ rake -T cucumber
rake cucumber:ok OR rake cucumber
@wip on master?
$ rake -T cucumber
rake cucumber:ok OR rake cucumber
     cucumber --tags ~@wip --strict
@wip on master?
$ rake -T cucumber
           Tag Exclusion
rake cucumber:ok OR rake cucumber
     cucumber --tags ~@wip --strict
@wip on master?
$ rake -T cucumber
@wip on master?
$ rake -T cucumber
rake cucumber:wip
@wip on master?
$ rake -T cucumber
rake cucumber:wip
      cucumber --tags @wip:2 --wip
@wip on master?
$ rake -T cucumber in flow
             Limit tags
rake cucumber:wip
      cucumber --tags @wip:2 --wip
@wip on master?
$ rake -T cucumber in flow
             Limit tags
rake cucumber:wip
      cucumber --tags @wip:2 --wip



          Expect failure - Success == Failure
@wip on master?
$ rake -T cucumber
rake cucumber:all
     Runs both ok and wip -- great for CI
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

  @proposed
  Scenario: remove wish

  @proposed
  Scenario: tweet wish
Line # of scenario
Look Ma! backtraces!
Given I am logged in   #features/manage_my_wishes.feature:8
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)

end
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
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)

end



 Fixture Replacement, Fixjour, Factory Girl, etc
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'
    )
  end
end
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)

end
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
end
features/step_definitions/user_steps.rb
       Webrat / Awesomeness
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
       Webrat / Awesomeness
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
       Webrat / Awesomeness
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/support/env.rb
require 'webrat'

Webrat.configure do |config|
  config.mode = :rails
end
features/step_definitions/user_steps.rb
       Webrat / Awesomeness
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/support/env.rb
require 'webrat'

Webrat.configure do |config|
  config.mode = :rails
end
                                    Adapter
features/step_definitions/user_steps.rb
       Webrat / Awesomeness
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/webrat_steps.rb
When /^I press "(.*)"$/ do |button|
  click_button(button)
end
                                      20+ Steps Out-of-box
When /^I follow "(.*)"$/ do |link|
  click_link(link)
end

When /^I fill in "(.*)" with "(.*)"$/ do |field, value|
  fill_in(field, :with => value)
end
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
end
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
  session[:user_id].should == @current_user.id
end
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
  session[:user_id].should == @current_user.id
  controller.current_user.should == @current_user
end
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)

  visit new_session_path
Specify outcome, not implementation.
  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
  session[:user_id].should == @current_user.id
  controller.current_user.should == @current_user
  response.should contain("Signed in successfully")
end
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
No route matches “/sessions/create” with
{:method=>:post} (ActionController::RoutingError)
I’m going to cheat...
I’m going to cheat...
$ gem install thoughtbot-clearance
$ ./script generate clearance
$ ./script generate clearance_features
Authlogic?


http://github.com/hectoregm/groundwork
features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|

end

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

end
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
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_button
end

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

end
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_button
end

Then /^(.+) should appear on my wish list$/ do |wish|
  response.should contain("Your wish has been added!")
  response.should contain(wish)
end
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_button
end
No route matches “/wishes” with
{:method=>:get} appear on my wish list$/ do |wish|
 Then /^(.+) should (ActionController::RoutingError)
  response.should contain("Your wish has been added!")
  response.should contain(wish)
end
config/routes.rb
ActionController::Routing::Routes.draw do |map|

 map.resources :wishes
config/routes.rb
ActionController::Routing::Routes.draw do |map|

  map.resources :wishes



When I make a “New car” wish
  uninitialized constant WishesController (NameError)
config/routes.rb
 ActionController::Routing::Routes.draw do |map|

  map.resources :wishes




$./script generate rspec_controller new create
config/routes.rb
ActionController::Routing::Routes.draw do |map|

 map.resources :wishes




When I make a “New car” wish
 Could not find link with text or title or
 id “Make a wish” (Webrat::NotFoundError)
app/views/wishes/index.html.erb
<%= link_to "Make a wish", new_wish_path %>
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)
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_button
end
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_button
end


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 %>
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_button
end
                     Location Strategy FTW!
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 %>
View

Controller
spec/controllers/wishes_controller_spec.rb
describe WishesController do
  describe "POST / (#create)" do


  end
end
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
  end
end
app/controllers/wishes_controller.rb
class WishesController < ApplicationController

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

end
spec/controllers/wishes_controller_spec.rb
describe WishesController do
  describe "POST / (#create)" do
    before(:each) do
      .....

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

  end
end
app/controllers/wishes_controller.rb
 def create
   current_user.wishes.create(params['wish'])
   redirect_to :action => :index
 end
View

Controller

  Model
app/controllers/wishes_controller.rb
  def create
    current_user.wishes.create(params['wish'])
    redirect_to :action => :index
  end



When I make a “New car” wish
  undefined method `wishes` for #<User:0x268e898>
  (NoMethodError)
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
app/models/wish.rb
class Wish < ActiveRecord::Base
  belongs_to :user
end



app/models/user.rb
class User < ActiveRecord::Base
  include Clearance::App::Models::User
  has_many :wishes
end
app/models/wish.rb
class Wish < ActiveRecord::Base
  belongs_to :user
end



app/models/user.rb
When I make a “New car” wish
Then “New <car” should appear on my wish
 class User   ActiveRecord::Base
   include Clearance::App::Models::User
   has_many the following element’s content to include
  expected:wishes
  “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
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



app/controllers/wishes_controller.rb
def create
  current_user.wishes.create(params['wish'])
  flash[:success] = "Your wish has been added!"
  redirect_to :action => :index
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



 app/controllers/wishes_controller.rb
Then “New car” should appear on my wish
  expected the following element’s content to include
 def create
  “New car”
   current_user.wishes.create(params['wish'])
  flash[:success] = "Your wish has been added!"
  redirect_to :action => :index
end
app/views/wishes/index.html.erb
<ul>
<% @wishes.each do |wish| %>
  <li><%= wish.name %></li>
<% end %>
</ul>
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
app/controllers/wishes_controller.rb
 def index
   @wishes = current_user.wishes
 end
How do I
test JS and
  AJAX?

              FAQ
Slow




Fast
Slow   Integrated




Fast    Isolated
Slow   Integrated




Fast    Isolated
Slow   Integrated




Fast    Isolated
Slow   Integrated




Fast    Isolated
Slow   Integrated




Fast    Isolated
Slow




Fast
Slow




Fast   Joyful
Slow   Painful




Fast   Joyful
Slow          Painful


       Celerity




Fast              Joyful
Celerity
Celerity
           HtmlUnit
Celerity
           HtmlUnit
Celerity
           HtmlUnit
Celerity
           HtmlUnit
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'
What if I use MRI?
Culerity

http://github.com/langalex/culerity
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'
Celerity

                 +
       http://github.com/dstrelau/webrat
HtmlUnit
             +
    http://github.com/johnnyt/webrat
CodeNote
http://github.com/bmabey/codenote
Feature: CLI Server
                                                For example of how to
  In order to save me time and headaches        test CLI tools take a look
  As a presenter of code                        at CodeNote on github.

  I create a presentation in plaintext          RSpec and Cucumber also
                                                have good examples of
  a'la Slidedown (from Pat Nakajima)            how to do this.
  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
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
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
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
@wip
Scenario: 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.
     """
@wip
Scenario: 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.
     """
@wip
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
Given the following presentation
  """
  blah, blah
  """

Given /the following presentation$/ do |presentation|

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

Given /the following presentation$/ do |presentation|

end



           Yields the multi-line string
Given the following presentation
  """
  blah, blah
  """

Given /the following presentation$/ do |presentation|
  CodeNote::PresentationLoader.setup(presentation)
end
RSpec Cycle
And no tweets have been tweeted that match
      the '#free_stuff "aaron burr"' search
How do I
test web
services?

            FAQ
http://github.com/chrisk/fakeweb
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
And no tweets have been tweeted that match
      the '#free_stuff "aaron burr"' search


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


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
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
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
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
 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
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
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
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


            “Every time you monkeypatch
 def search_url_for(query)
                  Object, a kitten dies.”
  "http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
 end

 def canned_response_for(query)
   ....
  return file_path
 end
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
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)
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)
When the presenter goes to the 3rd slide
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
  end
end
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
  end
end

               Presenter has own browser,
                   multiple sessions!
And I go to the 3rd slide
Then I should see "And the winner is..."
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
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
And I should see an ajax spinner
And I should see an ajax spinner


Then /I should see an ajax spinner$/ do
  browser.image(:id, 'spinner').exists?.should be_true
end
Brief RSpec cycle?
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
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
@wip
Scenario: winner is displayed
  Given the following presentation ...
  And no tweets have been tweeted that match
         the '#free_stuff "aaron burr"' search
@wip
Scenario: winner is displayed
  Given the following presentation ...
  And no tweets have been tweeted that match
         the '#free_stuff "aaron burr"' search




           Duplication of context!
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
                                  Extract to ‘Background’
    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
@wip
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
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 |
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 |

 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 |

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




                        Cucumber::AST::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 |

 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 |

 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))




                               Umm... that won’t work.
 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 |

 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))



                     What I would really like is a
                     test data builder/factory for
 end
                          twitter searches...
http://github.com/bmabey/faketwitter
require '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">]
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 |

 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 |

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




                     Our headers and columns
                    aren’t compatible with API.
 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 |

 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 |

 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
@wip
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
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
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
  end
end
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
  end
end


       Keep trying after sleeping
           until it times out
RSpec Cycle
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
Demo!
More tricks...
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 |
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|




  end
end
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|
  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"])
  end
end
Table Diffing
http://wiki.github.com/aslakhellesoy/cucumber/multiline-step-arguments
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       |
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   |
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       |
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       |
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       |
Steps Within Steps
When /^I view the wish list for "(.+)"$/ do |user_name|
  Given "I am logged in"
  visit "/wishes/#{user_name}"
end
Steps Within Steps
When /^I view the wish list for "(.+)"$/ do |user_name|
  Given "I am logged in"
  visit "/wishes/#{user_name}"
end
Hooks
Before do
end

After do |scenario|
end

World do
end

World(MyModule)
World(HerModule)
Tagged Hooks
Before('@im_special', '@me_too') do
  @icecream = true
end

@me_too             Feature: Sit
Feature: Lorem        @im_special
  Scenario: Ipsum     Scenario: Amet
  Scenario: Dolor     Scenario: Consec
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
                           ser ver that forks.. hence Spork. :)




http://github.com/timcharper/spork
Drinking the
 Cucumber
 Kool-Aid?
Integration tests
       are a scam
                                        J. B. Rainsberger
       http://www.jbrains.ca/permalink/239
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.
Cucumber is a
good hammer
Cucumber is a
good hammer
Not everything
   is a nail
I can skp teh
 unit testz?
Acceptance Tests               Unit Tests

Application Level              Object Level- Isolated!
For Customers                  For developers
Slow                           FAST! (should be at least)
Good confidence                 - Tighter Feedback Loop
Prevent against                More about design!!!!!!!!!!!!
regression




   Will need both gears!
   Some things are easier to
   test at the application
   level and vice-versa.
SRSLY?
Model specs,
Controller Specs,
and view specs!?
W
M
More tests ==
More Maintenance
Test Value =
    Design +
Documentation +
    Defence
   (regression)
if test.value > test.cost
  Suite.add(test)
end
KTHXBYE!
  BenMabey.com
github.com/bmabey
 Twitter: bmabey
   IRC: mabes

More Related Content

What's hot

Writing your Third Plugin
Writing your Third PluginWriting your Third Plugin
Writing your Third PluginJustin Ryan
 
Scalable web application architecture
Scalable web application architectureScalable web application architecture
Scalable web application architecturepostrational
 
The LAZY Developer's Guide to BDD (with Cucumber)
The LAZY Developer's Guide to BDD (with Cucumber)The LAZY Developer's Guide to BDD (with Cucumber)
The LAZY Developer's Guide to BDD (with Cucumber)Tze Yang Ng
 
Automated Testing with Ruby
Automated Testing with RubyAutomated Testing with Ruby
Automated Testing with RubyKeith Pitty
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasminePaulo Ragonha
 
Webinar: AngularJS and the WordPress REST API
Webinar: AngularJS and the WordPress REST APIWebinar: AngularJS and the WordPress REST API
Webinar: AngularJS and the WordPress REST APIWP Engine UK
 
Capybara testing
Capybara testingCapybara testing
Capybara testingFutureworkz
 
RSpec User Stories
RSpec User StoriesRSpec User Stories
RSpec User Storiesrahoulb
 
Jumping Into WordPress Plugin Programming
Jumping Into WordPress Plugin ProgrammingJumping Into WordPress Plugin Programming
Jumping Into WordPress Plugin ProgrammingDougal Campbell
 
jQuery Proven Performance Tips & Tricks
jQuery Proven Performance Tips & TricksjQuery Proven Performance Tips & Tricks
jQuery Proven Performance Tips & TricksAddy Osmani
 
Browser Automated Testing Frameworks - Nightwatch.js
Browser Automated Testing Frameworks - Nightwatch.jsBrowser Automated Testing Frameworks - Nightwatch.js
Browser Automated Testing Frameworks - Nightwatch.jsLuís Bastião Silva
 
Djangocon 2014 angular + django
Djangocon 2014 angular + djangoDjangocon 2014 angular + django
Djangocon 2014 angular + djangoNina Zakharenko
 
Tek 2013 - Building Web Apps from a New Angle with AngularJS
Tek 2013 - Building Web Apps from a New Angle with AngularJSTek 2013 - Building Web Apps from a New Angle with AngularJS
Tek 2013 - Building Web Apps from a New Angle with AngularJSPablo Godel
 
Getting big without getting fat, in perl
Getting big without getting fat, in perlGetting big without getting fat, in perl
Getting big without getting fat, in perlDean Hamstead
 
JavaScript, React Native and Performance at react-europe 2016
JavaScript, React Native and Performance at react-europe 2016JavaScript, React Native and Performance at react-europe 2016
JavaScript, React Native and Performance at react-europe 2016Tadeu Zagallo
 
Introduction to VueJS & The WordPress REST API
Introduction to VueJS & The WordPress REST APIIntroduction to VueJS & The WordPress REST API
Introduction to VueJS & The WordPress REST APICaldera Labs
 

What's hot (20)

Writing your Third Plugin
Writing your Third PluginWriting your Third Plugin
Writing your Third Plugin
 
Night Watch with QA
Night Watch with QANight Watch with QA
Night Watch with QA
 
GAEO
GAEOGAEO
GAEO
 
Scalable web application architecture
Scalable web application architectureScalable web application architecture
Scalable web application architecture
 
Cucumber
CucumberCucumber
Cucumber
 
The LAZY Developer's Guide to BDD (with Cucumber)
The LAZY Developer's Guide to BDD (with Cucumber)The LAZY Developer's Guide to BDD (with Cucumber)
The LAZY Developer's Guide to BDD (with Cucumber)
 
Automated Testing with Ruby
Automated Testing with RubyAutomated Testing with Ruby
Automated Testing with Ruby
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
 
Webinar: AngularJS and the WordPress REST API
Webinar: AngularJS and the WordPress REST APIWebinar: AngularJS and the WordPress REST API
Webinar: AngularJS and the WordPress REST API
 
Capybara testing
Capybara testingCapybara testing
Capybara testing
 
RSpec User Stories
RSpec User StoriesRSpec User Stories
RSpec User Stories
 
Jumping Into WordPress Plugin Programming
Jumping Into WordPress Plugin ProgrammingJumping Into WordPress Plugin Programming
Jumping Into WordPress Plugin Programming
 
jQuery Proven Performance Tips & Tricks
jQuery Proven Performance Tips & TricksjQuery Proven Performance Tips & Tricks
jQuery Proven Performance Tips & Tricks
 
Browser Automated Testing Frameworks - Nightwatch.js
Browser Automated Testing Frameworks - Nightwatch.jsBrowser Automated Testing Frameworks - Nightwatch.js
Browser Automated Testing Frameworks - Nightwatch.js
 
Djangocon 2014 angular + django
Djangocon 2014 angular + djangoDjangocon 2014 angular + django
Djangocon 2014 angular + django
 
RSpec 2 Best practices
RSpec 2 Best practicesRSpec 2 Best practices
RSpec 2 Best practices
 
Tek 2013 - Building Web Apps from a New Angle with AngularJS
Tek 2013 - Building Web Apps from a New Angle with AngularJSTek 2013 - Building Web Apps from a New Angle with AngularJS
Tek 2013 - Building Web Apps from a New Angle with AngularJS
 
Getting big without getting fat, in perl
Getting big without getting fat, in perlGetting big without getting fat, in perl
Getting big without getting fat, in perl
 
JavaScript, React Native and Performance at react-europe 2016
JavaScript, React Native and Performance at react-europe 2016JavaScript, React Native and Performance at react-europe 2016
JavaScript, React Native and Performance at react-europe 2016
 
Introduction to VueJS & The WordPress REST API
Introduction to VueJS & The WordPress REST APIIntroduction to VueJS & The WordPress REST API
Introduction to VueJS & The WordPress REST API
 

Viewers also liked

Introduction to BDD with Cucumber for Java
Introduction to BDD with Cucumber for JavaIntroduction to BDD with Cucumber for Java
Introduction to BDD with Cucumber for JavaSeb Rose
 
Story Testing Approach for Enterprise Applications using Selenium Framework
Story Testing Approach for Enterprise Applications using Selenium FrameworkStory Testing Approach for Enterprise Applications using Selenium Framework
Story Testing Approach for Enterprise Applications using Selenium FrameworkOleksiy Rezchykov
 
Gerrit is Getting Native with RPM, Deb and Docker
Gerrit is Getting Native with RPM, Deb and DockerGerrit is Getting Native with RPM, Deb and Docker
Gerrit is Getting Native with RPM, Deb and DockerLuca Milanesio
 
Introduction to Bdd and cucumber
Introduction to Bdd and cucumberIntroduction to Bdd and cucumber
Introduction to Bdd and cucumberNibu Baby
 
DevQA: make your testers happier with Groovy, Spock and Geb (Greach 2014)
DevQA: make your testers happier with Groovy, Spock and Geb (Greach 2014)DevQA: make your testers happier with Groovy, Spock and Geb (Greach 2014)
DevQA: make your testers happier with Groovy, Spock and Geb (Greach 2014)Alvaro Sanchez-Mariscal
 
Continuous integration using Jenkins and Sonar
Continuous integration using Jenkins and SonarContinuous integration using Jenkins and Sonar
Continuous integration using Jenkins and SonarPascal Larocque
 
Sonar qube to impove code quality
Sonar qube   to impove code qualitySonar qube   to impove code quality
Sonar qube to impove code qualityMani Sarkar
 
Design First API's with RAML and SoapUI
Design First API's with RAML and SoapUIDesign First API's with RAML and SoapUI
Design First API's with RAML and SoapUIDaniel Feist
 
Gerrit Code Review with GitHub plugin
Gerrit Code Review with GitHub pluginGerrit Code Review with GitHub plugin
Gerrit Code Review with GitHub pluginLuca Milanesio
 
How To Use Selenium Successfully (Java Edition)
How To Use Selenium Successfully (Java Edition)How To Use Selenium Successfully (Java Edition)
How To Use Selenium Successfully (Java Edition)Dave Haeffner
 
Automated Testing With Jasmine, PhantomJS and Jenkins
Automated Testing With Jasmine, PhantomJS and JenkinsAutomated Testing With Jasmine, PhantomJS and Jenkins
Automated Testing With Jasmine, PhantomJS and JenkinsWork at Play
 
Tracking and improving software quality with SonarQube
Tracking and improving software quality with SonarQubeTracking and improving software quality with SonarQube
Tracking and improving software quality with SonarQubePatroklos Papapetrou (Pat)
 
How Git and Gerrit make you more productive
How Git and Gerrit make you more productiveHow Git and Gerrit make you more productive
How Git and Gerrit make you more productiveKarsten Dambekalns
 
BDD testing with cucumber
BDD testing with cucumberBDD testing with cucumber
BDD testing with cucumberDaniel Kummer
 
Automated Acceptance Tests & Tool choice
Automated Acceptance Tests & Tool choiceAutomated Acceptance Tests & Tool choice
Automated Acceptance Tests & Tool choicetoddbr
 
Behavior Driven Development and Automation Testing Using Cucumber
Behavior Driven Development and Automation Testing Using CucumberBehavior Driven Development and Automation Testing Using Cucumber
Behavior Driven Development and Automation Testing Using CucumberKMS Technology
 

Viewers also liked (20)

Automate you Appium test like a pro!
Automate you Appium test like a pro!Automate you Appium test like a pro!
Automate you Appium test like a pro!
 
Introduction to BDD with Cucumber for Java
Introduction to BDD with Cucumber for JavaIntroduction to BDD with Cucumber for Java
Introduction to BDD with Cucumber for Java
 
Story Testing Approach for Enterprise Applications using Selenium Framework
Story Testing Approach for Enterprise Applications using Selenium FrameworkStory Testing Approach for Enterprise Applications using Selenium Framework
Story Testing Approach for Enterprise Applications using Selenium Framework
 
Next level of Appium
Next level of AppiumNext level of Appium
Next level of Appium
 
Gerrit is Getting Native with RPM, Deb and Docker
Gerrit is Getting Native with RPM, Deb and DockerGerrit is Getting Native with RPM, Deb and Docker
Gerrit is Getting Native with RPM, Deb and Docker
 
Introduction to Bdd and cucumber
Introduction to Bdd and cucumberIntroduction to Bdd and cucumber
Introduction to Bdd and cucumber
 
DevQA: make your testers happier with Groovy, Spock and Geb (Greach 2014)
DevQA: make your testers happier with Groovy, Spock and Geb (Greach 2014)DevQA: make your testers happier with Groovy, Spock and Geb (Greach 2014)
DevQA: make your testers happier with Groovy, Spock and Geb (Greach 2014)
 
Continuous integration using Jenkins and Sonar
Continuous integration using Jenkins and SonarContinuous integration using Jenkins and Sonar
Continuous integration using Jenkins and Sonar
 
Sonar qube to impove code quality
Sonar qube   to impove code qualitySonar qube   to impove code quality
Sonar qube to impove code quality
 
Design First API's with RAML and SoapUI
Design First API's with RAML and SoapUIDesign First API's with RAML and SoapUI
Design First API's with RAML and SoapUI
 
Gerrit Code Review with GitHub plugin
Gerrit Code Review with GitHub pluginGerrit Code Review with GitHub plugin
Gerrit Code Review with GitHub plugin
 
How To Use Selenium Successfully (Java Edition)
How To Use Selenium Successfully (Java Edition)How To Use Selenium Successfully (Java Edition)
How To Use Selenium Successfully (Java Edition)
 
Automated Testing With Jasmine, PhantomJS and Jenkins
Automated Testing With Jasmine, PhantomJS and JenkinsAutomated Testing With Jasmine, PhantomJS and Jenkins
Automated Testing With Jasmine, PhantomJS and Jenkins
 
Tracking and improving software quality with SonarQube
Tracking and improving software quality with SonarQubeTracking and improving software quality with SonarQube
Tracking and improving software quality with SonarQube
 
Automation Testing by Selenium Web Driver
Automation Testing by Selenium Web DriverAutomation Testing by Selenium Web Driver
Automation Testing by Selenium Web Driver
 
How Git and Gerrit make you more productive
How Git and Gerrit make you more productiveHow Git and Gerrit make you more productive
How Git and Gerrit make you more productive
 
Gerrit Code Review
Gerrit Code ReviewGerrit Code Review
Gerrit Code Review
 
BDD testing with cucumber
BDD testing with cucumberBDD testing with cucumber
BDD testing with cucumber
 
Automated Acceptance Tests & Tool choice
Automated Acceptance Tests & Tool choiceAutomated Acceptance Tests & Tool choice
Automated Acceptance Tests & Tool choice
 
Behavior Driven Development and Automation Testing Using Cucumber
Behavior Driven Development and Automation Testing Using CucumberBehavior Driven Development and Automation Testing Using Cucumber
Behavior Driven Development and Automation Testing Using Cucumber
 

Similar to Writing Software not Code with Cucumber

Your own (little) gem: building an online business with Ruby
Your own (little) gem: building an online business with RubyYour own (little) gem: building an online business with Ruby
Your own (little) gem: building an online business with RubyLindsay Holmwood
 
Introduce cucumber
Introduce cucumberIntroduce cucumber
Introduce cucumberBachue Zhou
 
OSDC 2009 Rails Turtorial
OSDC 2009 Rails TurtorialOSDC 2009 Rails Turtorial
OSDC 2009 Rails TurtorialYi-Ting Cheng
 
More Secrets of JavaScript Libraries
More Secrets of JavaScript LibrariesMore Secrets of JavaScript Libraries
More Secrets of JavaScript Librariesjeresig
 
What's new in Rails 2?
What's new in Rails 2?What's new in Rails 2?
What's new in Rails 2?brynary
 
Introduction à Ruby
Introduction à RubyIntroduction à Ruby
Introduction à RubyMicrosoft
 
Bdd From The Trenches
Bdd From The TrenchesBdd From The Trenches
Bdd From The Trenchesjjggss
 
Merb Slices
Merb SlicesMerb Slices
Merb Sliceshassox
 
Building a Single Page Application using Ember.js ... for fun and profit
Building a Single Page Application using Ember.js ... for fun and profitBuilding a Single Page Application using Ember.js ... for fun and profit
Building a Single Page Application using Ember.js ... for fun and profitBen Limmer
 
Be happy with Ruby on Rails - CEUNSP Itu
Be happy with Ruby on Rails - CEUNSP ItuBe happy with Ruby on Rails - CEUNSP Itu
Be happy with Ruby on Rails - CEUNSP ItuLucas Renan
 
Monitoring web application behaviour with cucumber-nagios
Monitoring web application behaviour with cucumber-nagiosMonitoring web application behaviour with cucumber-nagios
Monitoring web application behaviour with cucumber-nagiosLindsay Holmwood
 
Cucumber: How I Slice It
Cucumber: How I Slice ItCucumber: How I Slice It
Cucumber: How I Slice Itlinoj
 
Adventurous Merb
Adventurous MerbAdventurous Merb
Adventurous MerbMatt Todd
 
Becoming a Git Master - Nicola Paolucci
Becoming a Git Master - Nicola PaolucciBecoming a Git Master - Nicola Paolucci
Becoming a Git Master - Nicola PaolucciAtlassian
 
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...Coupa Software
 
BDD / cucumber /Capybara
BDD / cucumber /CapybaraBDD / cucumber /Capybara
BDD / cucumber /CapybaraShraddhaSF
 

Similar to Writing Software not Code with Cucumber (20)

Your own (little) gem: building an online business with Ruby
Your own (little) gem: building an online business with RubyYour own (little) gem: building an online business with Ruby
Your own (little) gem: building an online business with Ruby
 
Introduce cucumber
Introduce cucumberIntroduce cucumber
Introduce cucumber
 
OSDC 2009 Rails Turtorial
OSDC 2009 Rails TurtorialOSDC 2009 Rails Turtorial
OSDC 2009 Rails Turtorial
 
Rails OO views
Rails OO viewsRails OO views
Rails OO views
 
Rails::Engine
Rails::EngineRails::Engine
Rails::Engine
 
More Secrets of JavaScript Libraries
More Secrets of JavaScript LibrariesMore Secrets of JavaScript Libraries
More Secrets of JavaScript Libraries
 
What's new in Rails 2?
What's new in Rails 2?What's new in Rails 2?
What's new in Rails 2?
 
Introduction à Ruby
Introduction à RubyIntroduction à Ruby
Introduction à Ruby
 
Bdd From The Trenches
Bdd From The TrenchesBdd From The Trenches
Bdd From The Trenches
 
Merb Slices
Merb SlicesMerb Slices
Merb Slices
 
Building a Single Page Application using Ember.js ... for fun and profit
Building a Single Page Application using Ember.js ... for fun and profitBuilding a Single Page Application using Ember.js ... for fun and profit
Building a Single Page Application using Ember.js ... for fun and profit
 
Be happy with Ruby on Rails - CEUNSP Itu
Be happy with Ruby on Rails - CEUNSP ItuBe happy with Ruby on Rails - CEUNSP Itu
Be happy with Ruby on Rails - CEUNSP Itu
 
Monitoring web application behaviour with cucumber-nagios
Monitoring web application behaviour with cucumber-nagiosMonitoring web application behaviour with cucumber-nagios
Monitoring web application behaviour with cucumber-nagios
 
Cucumber: How I Slice It
Cucumber: How I Slice ItCucumber: How I Slice It
Cucumber: How I Slice It
 
Mojolicious
MojoliciousMojolicious
Mojolicious
 
Adventurous Merb
Adventurous MerbAdventurous Merb
Adventurous Merb
 
Becoming a Git Master - Nicola Paolucci
Becoming a Git Master - Nicola PaolucciBecoming a Git Master - Nicola Paolucci
Becoming a Git Master - Nicola Paolucci
 
Mojolicious
MojoliciousMojolicious
Mojolicious
 
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
 
BDD / cucumber /Capybara
BDD / cucumber /CapybaraBDD / cucumber /Capybara
BDD / cucumber /Capybara
 

More from Ben Mabey

PCA for the uninitiated
PCA for the uninitiatedPCA for the uninitiated
PCA for the uninitiatedBen Mabey
 
Clojure, Plain and Simple
Clojure, Plain and SimpleClojure, Plain and Simple
Clojure, Plain and SimpleBen Mabey
 
Cucumber: Automating the Requirements Language You Already Speak
Cucumber: Automating the Requirements Language You Already SpeakCucumber: Automating the Requirements Language You Already Speak
Cucumber: Automating the Requirements Language You Already SpeakBen Mabey
 
Outside-In Development With Cucumber
Outside-In Development With CucumberOutside-In Development With Cucumber
Outside-In Development With CucumberBen Mabey
 
Disconnecting the Database with ActiveRecord
Disconnecting the Database with ActiveRecordDisconnecting the Database with ActiveRecord
Disconnecting the Database with ActiveRecordBen Mabey
 
SVD and the Netflix Dataset
SVD and the Netflix DatasetSVD and the Netflix Dataset
SVD and the Netflix DatasetBen Mabey
 
The WHY behind TDD/BDD and the HOW with RSpec
The WHY behind TDD/BDD and the HOW with RSpecThe WHY behind TDD/BDD and the HOW with RSpec
The WHY behind TDD/BDD and the HOW with RSpecBen Mabey
 

More from Ben Mabey (8)

PCA for the uninitiated
PCA for the uninitiatedPCA for the uninitiated
PCA for the uninitiated
 
Clojure, Plain and Simple
Clojure, Plain and SimpleClojure, Plain and Simple
Clojure, Plain and Simple
 
Github flow
Github flowGithub flow
Github flow
 
Cucumber: Automating the Requirements Language You Already Speak
Cucumber: Automating the Requirements Language You Already SpeakCucumber: Automating the Requirements Language You Already Speak
Cucumber: Automating the Requirements Language You Already Speak
 
Outside-In Development With Cucumber
Outside-In Development With CucumberOutside-In Development With Cucumber
Outside-In Development With Cucumber
 
Disconnecting the Database with ActiveRecord
Disconnecting the Database with ActiveRecordDisconnecting the Database with ActiveRecord
Disconnecting the Database with ActiveRecord
 
SVD and the Netflix Dataset
SVD and the Netflix DatasetSVD and the Netflix Dataset
SVD and the Netflix Dataset
 
The WHY behind TDD/BDD and the HOW with RSpec
The WHY behind TDD/BDD and the HOW with RSpecThe WHY behind TDD/BDD and the HOW with RSpec
The WHY behind TDD/BDD and the HOW with RSpec
 

Recently uploaded

Generative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxGenerative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxfnnc6jmgwh
 
Transcript: New from BookNet Canada for 2024: BNC SalesData and LibraryData -...
Transcript: New from BookNet Canada for 2024: BNC SalesData and LibraryData -...Transcript: New from BookNet Canada for 2024: BNC SalesData and LibraryData -...
Transcript: New from BookNet Canada for 2024: BNC SalesData and LibraryData -...BookNet Canada
 
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesMuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesManik S Magar
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersNicole Novielli
 
Decarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityDecarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityIES VE
 
React JS; all concepts. Contains React Features, JSX, functional & Class comp...
React JS; all concepts. Contains React Features, JSX, functional & Class comp...React JS; all concepts. Contains React Features, JSX, functional & Class comp...
React JS; all concepts. Contains React Features, JSX, functional & Class comp...Karmanjay Verma
 
Connecting the Dots for Information Discovery.pdf
Connecting the Dots for Information Discovery.pdfConnecting the Dots for Information Discovery.pdf
Connecting the Dots for Information Discovery.pdfNeo4j
 
2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch TuesdayIvanti
 
Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...itnewsafrica
 
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical InfrastructureVarsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructureitnewsafrica
 
A Glance At The Java Performance Toolbox
A Glance At The Java Performance ToolboxA Glance At The Java Performance Toolbox
A Glance At The Java Performance ToolboxAna-Maria Mihalceanu
 
Potential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsPotential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsRavi Sanghani
 
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...itnewsafrica
 
QCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architecturesQCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architecturesBernd Ruecker
 
React Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App FrameworkReact Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App FrameworkPixlogix Infotech
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesKari Kakkonen
 
All These Sophisticated Attacks, Can We Really Detect Them - PDF
All These Sophisticated Attacks, Can We Really Detect Them - PDFAll These Sophisticated Attacks, Can We Really Detect Them - PDF
All These Sophisticated Attacks, Can We Really Detect Them - PDFMichael Gough
 
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...panagenda
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfIngrid Airi González
 
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesAssure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesThousandEyes
 

Recently uploaded (20)

Generative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptxGenerative AI - Gitex v1Generative AI - Gitex v1.pptx
Generative AI - Gitex v1Generative AI - Gitex v1.pptx
 
Transcript: New from BookNet Canada for 2024: BNC SalesData and LibraryData -...
Transcript: New from BookNet Canada for 2024: BNC SalesData and LibraryData -...Transcript: New from BookNet Canada for 2024: BNC SalesData and LibraryData -...
Transcript: New from BookNet Canada for 2024: BNC SalesData and LibraryData -...
 
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotesMuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
MuleSoft Online Meetup Group - B2B Crash Course: Release SparkNotes
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software Developers
 
Decarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityDecarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a reality
 
React JS; all concepts. Contains React Features, JSX, functional & Class comp...
React JS; all concepts. Contains React Features, JSX, functional & Class comp...React JS; all concepts. Contains React Features, JSX, functional & Class comp...
React JS; all concepts. Contains React Features, JSX, functional & Class comp...
 
Connecting the Dots for Information Discovery.pdf
Connecting the Dots for Information Discovery.pdfConnecting the Dots for Information Discovery.pdf
Connecting the Dots for Information Discovery.pdf
 
2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch Tuesday
 
Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...
 
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical InfrastructureVarsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
 
A Glance At The Java Performance Toolbox
A Glance At The Java Performance ToolboxA Glance At The Java Performance Toolbox
A Glance At The Java Performance Toolbox
 
Potential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and InsightsPotential of AI (Generative AI) in Business: Learnings and Insights
Potential of AI (Generative AI) in Business: Learnings and Insights
 
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
Irene Moetsana-Moeng: Stakeholders in Cybersecurity: Collaborative Defence fo...
 
QCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architecturesQCon London: Mastering long-running processes in modern architectures
QCon London: Mastering long-running processes in modern architectures
 
React Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App FrameworkReact Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App Framework
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examples
 
All These Sophisticated Attacks, Can We Really Detect Them - PDF
All These Sophisticated Attacks, Can We Really Detect Them - PDFAll These Sophisticated Attacks, Can We Really Detect Them - PDF
All These Sophisticated Attacks, Can We Really Detect Them - PDF
 
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdf
 
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesAssure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
 

Writing Software not Code with Cucumber

  • 1. Writing Software not code With Ben Mabey
  • 2. Writing Software not code With Ben Mabey
  • 3. Writing Software not code With Behaviour Driven Development Ben Mabey
  • 4. ?
  • 5.
  • 6. Tweet in the blanks... "most software projects are like _ _ _ _ _ _ _ _" #rubyhoedown #cucumber
  • 7. "most software projects are like _ _ _ _ _ _ _ _" #rubyhoedown #cucumber
  • 8.
  • 9.
  • 10. So... why are software projects like “The Homer”?
  • 11.
  • 12.
  • 13.
  • 14. Feature Devotion Text Placing emphasis on features instead of overall outcome
  • 15.
  • 16. Shingeo Shingo of Toyota says...
  • 18. "Inspection to find defects is waste." "Inspection to prevent defects is essential."
  • 19. 56% of all bugs are introduced in requirements. (CHAOS Report)
  • 21. Popping the Why Stack...
  • 22.
  • 24.
  • 25. * not executed * documentation value Feature: title * variant of contextra * business value up front In order to [Business Value] As a [Role] I want to [Some Action] (feature)
  • 26. There is no template. What is important to have in narrative: * business value * stakeholder role * user role * action to be taken by user
  • 28. Writing Software not code With Behaviour Driven Development Ben Mabey
  • 33. “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
  • 34. BDD is a mindset not a tool set
  • 36. * not executed * documentation value Feature: title * variant of contextra * business value up front In order to [Business Value] As a [Role] I want to [Some Action] (feature)
  • 37. Scenario: title Given [Context] When I do [Action] Then I should see [Outcome]
  • 38. Scenario: title Given [Context] And [More Context] When I do [Action] And [Other Action] Then I should see [Outcome] But I should not see [Outcome]
  • 40. project_root/ | `-- features |-- awesomeness.feature |-- greatest_ever.feature
  • 41. project_root/ | `-- features |-- awesomeness.feature |-- greatest_ever.feature `-- support |-- env.rb `-- other_helpers.rb
  • 42. project_root/ | `-- features |-- awesomeness.feature |-- greatest_ever.feature `-- support |-- env.rb `-- other_helpers.rb |-- step_definitions | |-- domain_concept_A.rb | `-- domain_concept_B.rb
  • 44. Step Definition Given /^a widget$/ do Given a widget #codes go here end
  • 45. Step Definition Step Mother Given /^a widget$/ do Given a widget #codes go here end
  • 46. Step Definition Step Mother Given /^a widget$/ do Given a widget #codes go here end
  • 47.
  • 48.
  • 49.
  • 52. 28+ Languages RSpec, Test::Unit, etc
  • 53. 28+ Languages Your Code RSpec, Test::Unit, etc
  • 54. Not Just for Rails
  • 56.
  • 57.
  • 61. Go Down A Gear
  • 63. Write Code Example (Unit Test)
  • 68. REFACTOR and REPEAT
  • 69. 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
  • 70. features/manage_my_wishes.feature Feature: manage my wishes In order to get more stuff Work In Progress 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 @proposed Scenario: remove wish @proposed Scenario: tweet wish
  • 72. Workflow git branch -b add_wish_tracker#
  • 73. Workflow git branch -b add_wish_tracker# Tag Scenario or Feature with @wip
  • 74. Workflow git branch -b add_wish_tracker# Tag Scenario or Feature with @wip cucumber --wip --tags @wip
  • 75. Workflow git branch -b add_wish_tracker# Tag Scenario or Feature with @wip cucumber --wip --tags @wip Develop it Outside-In
  • 76. Workflow git branch -b add_wish_tracker# Tag Scenario or Feature with @wip cucumber --wip --tags @wip Develop it Outside-In git rebase ---interactive; git merge
  • 77. Workflow git branch -b add_wish_tracker# Tag Scenario or Feature with @wip cucumber --wip --tags @wip Develop it Outside-In git rebase ---interactive; git merge Repeat!
  • 79. @wip on master? $ rake -T cucumber
  • 80. @wip on master? $ rake -T cucumber rake cucumber:ok OR rake cucumber
  • 81. @wip on master? $ rake -T cucumber rake cucumber:ok OR rake cucumber cucumber --tags ~@wip --strict
  • 82. @wip on master? $ rake -T cucumber Tag Exclusion rake cucumber:ok OR rake cucumber cucumber --tags ~@wip --strict
  • 83. @wip on master? $ rake -T cucumber
  • 84. @wip on master? $ rake -T cucumber rake cucumber:wip
  • 85. @wip on master? $ rake -T cucumber rake cucumber:wip cucumber --tags @wip:2 --wip
  • 86. @wip on master? $ rake -T cucumber in flow Limit tags rake cucumber:wip cucumber --tags @wip:2 --wip
  • 87. @wip on master? $ rake -T cucumber in flow Limit tags rake cucumber:wip cucumber --tags @wip:2 --wip Expect failure - Success == Failure
  • 88. @wip on master? $ rake -T cucumber rake cucumber:all Runs both ok and wip -- great for CI
  • 89. 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 @proposed Scenario: remove wish @proposed Scenario: tweet wish
  • 90.
  • 91. Line # of scenario
  • 92.
  • 93. Look Ma! backtraces! Given I am logged in #features/manage_my_wishes.feature:8
  • 94.
  • 95.
  • 96. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) end
  • 97. 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
  • 98. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) end Fixture Replacement, Fixjour, Factory Girl, etc 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' ) end end
  • 99. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) end
  • 100. 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 end
  • 101. features/step_definitions/user_steps.rb Webrat / Awesomeness 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
  • 102. features/step_definitions/user_steps.rb Webrat / Awesomeness 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
  • 103. features/step_definitions/user_steps.rb Webrat / Awesomeness 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/support/env.rb require 'webrat' Webrat.configure do |config| config.mode = :rails end
  • 104. features/step_definitions/user_steps.rb Webrat / Awesomeness 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/support/env.rb require 'webrat' Webrat.configure do |config| config.mode = :rails end Adapter
  • 105. features/step_definitions/user_steps.rb Webrat / Awesomeness 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/webrat_steps.rb When /^I press "(.*)"$/ do |button| click_button(button) end 20+ Steps Out-of-box When /^I follow "(.*)"$/ do |link| click_link(link) end When /^I fill in "(.*)" with "(.*)"$/ do |field, value| fill_in(field, :with => value) end
  • 106. 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 end
  • 107. 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 session[:user_id].should == @current_user.id end
  • 108. 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 session[:user_id].should == @current_user.id controller.current_user.should == @current_user end
  • 109. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) visit new_session_path Specify outcome, not implementation. 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 session[:user_id].should == @current_user.id controller.current_user.should == @current_user response.should contain("Signed in successfully") end
  • 110. 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
  • 111.
  • 112. No route matches “/sessions/create” with {:method=>:post} (ActionController::RoutingError)
  • 113. I’m going to cheat...
  • 114. I’m going to cheat... $ gem install thoughtbot-clearance $ ./script generate clearance $ ./script generate clearance_features
  • 116.
  • 117. features/step_definitions/wish_steps.rb When /^I make a "(.+)" wish$/ do |wish| end Then /^(.+) should appear on my wish list$/ do |wish| end
  • 118. 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
  • 119. 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_button end Then /^(.+) should appear on my wish list$/ do |wish| end
  • 120. 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_button end Then /^(.+) should appear on my wish list$/ do |wish| response.should contain("Your wish has been added!") response.should contain(wish) end
  • 121. 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_button end No route matches “/wishes” with {:method=>:get} appear on my wish list$/ do |wish| Then /^(.+) should (ActionController::RoutingError) response.should contain("Your wish has been added!") response.should contain(wish) end
  • 123. config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :wishes When I make a “New car” wish uninitialized constant WishesController (NameError)
  • 124. config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :wishes $./script generate rspec_controller new create
  • 125. config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :wishes When I make a “New car” wish Could not find link with text or title or id “Make a wish” (Webrat::NotFoundError)
  • 127. 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)
  • 128. 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_button end
  • 129. 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_button end 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 %>
  • 130. 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_button end Location Strategy FTW! 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 %>
  • 133. 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 end end
  • 134. app/controllers/wishes_controller.rb class WishesController < ApplicationController def create current_user.wishes.create(params['wish']) end end
  • 135. spec/controllers/wishes_controller_spec.rb describe WishesController do describe "POST / (#create)" do before(:each) do ..... it "redirects the user to their wish list" do do_post response.should redirect_to(wishes_path) end end end
  • 136. app/controllers/wishes_controller.rb def create current_user.wishes.create(params['wish']) redirect_to :action => :index end
  • 138. app/controllers/wishes_controller.rb def create current_user.wishes.create(params['wish']) redirect_to :action => :index end When I make a “New car” wish undefined method `wishes` for #<User:0x268e898> (NoMethodError)
  • 139. 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
  • 140. app/models/wish.rb class Wish < ActiveRecord::Base belongs_to :user end app/models/user.rb class User < ActiveRecord::Base include Clearance::App::Models::User has_many :wishes end
  • 141. app/models/wish.rb class Wish < ActiveRecord::Base belongs_to :user end app/models/user.rb When I make a “New car” wish Then “New <car” should appear on my wish class User ActiveRecord::Base include Clearance::App::Models::User has_many the following element’s content to include expected:wishes “Your wish has been added!” end
  • 142. 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
  • 143. 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 app/controllers/wishes_controller.rb def create current_user.wishes.create(params['wish']) flash[:success] = "Your wish has been added!" redirect_to :action => :index end
  • 144. 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 app/controllers/wishes_controller.rb Then “New car” should appear on my wish expected the following element’s content to include def create “New car” current_user.wishes.create(params['wish']) flash[:success] = "Your wish has been added!" redirect_to :action => :index end
  • 145. app/views/wishes/index.html.erb <ul> <% @wishes.each do |wish| %> <li><%= wish.name %></li> <% end %> </ul>
  • 146. 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
  • 147. app/controllers/wishes_controller.rb def index @wishes = current_user.wishes end
  • 148.
  • 149. How do I test JS and AJAX? FAQ
  • 150.
  • 152. Slow Integrated Fast Isolated
  • 153. Slow Integrated Fast Isolated
  • 154. Slow Integrated Fast Isolated
  • 155. Slow Integrated Fast Isolated
  • 156. Slow Integrated Fast Isolated
  • 158. Slow Fast Joyful
  • 159. Slow Painful Fast Joyful
  • 160. Slow Painful Celerity Fast Joyful
  • 162. Celerity HtmlUnit
  • 163. Celerity HtmlUnit
  • 164. Celerity HtmlUnit
  • 165. Celerity HtmlUnit
  • 166. 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'
  • 167. What if I use MRI?
  • 169. 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'
  • 170. Celerity + http://github.com/dstrelau/webrat
  • 171. HtmlUnit + http://github.com/johnnyt/webrat
  • 173. Feature: CLI Server For example of how to In order to save me time and headaches test CLI tools take a look As a presenter of code at CodeNote on github. I create a presentation in plaintext RSpec and Cucumber also have good examples of a'la Slidedown (from Pat Nakajima) how to do this. 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
  • 174. 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
  • 175. 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
  • 176. 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
  • 177. @wip Scenario: 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. """
  • 178. @wip Scenario: 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. """
  • 179. @wip 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
  • 180. Given the following presentation """ blah, blah """ Given /the following presentation$/ do |presentation| end
  • 181. Given the following presentation """ blah, blah """ Given /the following presentation$/ do |presentation| end Yields the multi-line string
  • 182. Given the following presentation """ blah, blah """ Given /the following presentation$/ do |presentation| CodeNote::PresentationLoader.setup(presentation) end
  • 184. And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search
  • 185. How do I test web services? FAQ
  • 187. 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
  • 188. And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search Given %r{no tweets have been tweeted that match the '([']*)' search$} do |query| end
  • 189. And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search 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
  • 190. 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
  • 191. 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
  • 192. 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 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
  • 193. 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
  • 194. 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
  • 195. 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 “Every time you monkeypatch def search_url_for(query) Object, a kitten dies.” "http://search.twitter.com/search.json?q=#{CGI.escape(query)}" end def canned_response_for(query) .... return file_path end
  • 196. 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
  • 197. 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)
  • 198. 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)
  • 199. When the presenter goes to the 3rd slide
  • 200. 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 end end
  • 201. 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 end end Presenter has own browser, multiple sessions!
  • 202. And I go to the 3rd slide Then I should see "And the winner is..."
  • 203. 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
  • 204. 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
  • 205. And I should see an ajax spinner
  • 206. And I should see an ajax spinner Then /I should see an ajax spinner$/ do browser.image(:id, 'spinner').exists?.should be_true end
  • 208. 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
  • 209. 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
  • 210. @wip Scenario: winner is displayed Given the following presentation ... And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search
  • 211. @wip Scenario: winner is displayed Given the following presentation ... And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search Duplication of context!
  • 212. 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 Extract to ‘Background’ 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
  • 213. @wip 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
  • 214. 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 |
  • 215. 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 | When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table| end
  • 216. 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 | When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table| Cucumber::AST::Table end
  • 217. 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 | 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
  • 218. 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 | 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)) Umm... that won’t work. end
  • 219. 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 | 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)) What I would really like is a test data builder/factory for end twitter searches...
  • 220. http://github.com/bmabey/faketwitter require '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">]
  • 221. 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 | When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table| FakeTwitter.register_search(query, { :results => tweet_table.hashes}) end
  • 222. 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 | When %r{the following tweets are tweeted that match the '([']*)' search$} do |query, tweet_table| FakeTwitter.register_search(query, { :results => tweet_table.hashes}) Our headers and columns aren’t compatible with API. end
  • 223. 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 | 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
  • 224. 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 | 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
  • 225. @wip 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
  • 226. 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
  • 227. 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 end end
  • 228. 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 end end Keep trying after sleeping until it times out
  • 230. 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
  • 231. Demo!
  • 233.
  • 234. 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 |
  • 235. 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| end end
  • 236. 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| 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"]) end end
  • 238. 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 |
  • 239. 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 |
  • 240. 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 |
  • 241. 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 |
  • 242. 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 |
  • 243. Steps Within Steps When /^I view the wish list for "(.+)"$/ do |user_name| Given "I am logged in" visit "/wishes/#{user_name}" end
  • 244. Steps Within Steps When /^I view the wish list for "(.+)"$/ do |user_name| Given "I am logged in" visit "/wishes/#{user_name}" end
  • 245. Hooks Before do end After do |scenario| end World do end World(MyModule) World(HerModule)
  • 246. Tagged Hooks Before('@im_special', '@me_too') do @icecream = true end @me_too Feature: Sit Feature: Lorem @im_special Scenario: Ipsum Scenario: Amet Scenario: Dolor Scenario: Consec
  • 247. 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 ser ver that forks.. hence Spork. :) http://github.com/timcharper/spork
  • 248. Drinking the Cucumber Kool-Aid?
  • 249. Integration tests are a scam J. B. Rainsberger http://www.jbrains.ca/permalink/239 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.
  • 251. Cucumber is a good hammer Not everything is a nail
  • 252. I can skp teh unit testz?
  • 253. Acceptance Tests Unit Tests Application Level Object Level- Isolated! For Customers For developers Slow FAST! (should be at least) Good confidence - Tighter Feedback Loop Prevent against More about design!!!!!!!!!!!! regression Will need both gears! Some things are easier to test at the application level and vice-versa.
  • 255.
  • 256.
  • 257.
  • 258. W
  • 259. M
  • 260. More tests == More Maintenance
  • 261. Test Value = Design + Documentation + Defence (regression)
  • 262. if test.value > test.cost Suite.add(test) end
  • 263. KTHXBYE! BenMabey.com github.com/bmabey Twitter: bmabey IRC: mabes