Writing Software not Code with Cucumber

Loading...

Flash Player 9 (or above) is needed to view presentations.
We have detected that you do not have it on your computer. To install it, go here.

0 comments

Post a comment

    Post a comment
    Embed Video
    Edit your comment Cancel

    10 Favorites

    Writing Software not Code with Cucumber - Presentation Transcript

    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. Tweet in the blanks... "most software projects are like _ _ _ _ _ _ _ _" #rubyhoedown #cucumber
    6. "most software projects are like _ _ _ _ _ _ _ _" #rubyhoedown #cucumber
    7. So... why are software projects like “The Homer”?
    8. Feature Devotion Text Placing emphasis on features instead of overall outcome
    9. Shingeo Shingo of Toyota says...
    10. "Inspection to find defects is waste."
    11. "Inspection to find defects is waste." "Inspection to prevent defects is essential."
    12. 56% of all bugs are introduced in requirements. (CHAOS Report)
    13. Root Cause Analysis
    14. Popping the Why Stack...
    15. Protect Revenue Increase Revenue Manage Cost
    16. * 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)
    17. There is no template. What is important to have in narrative: * business value * stakeholder role * user role * action to be taken by user
    18. <rant>
    19. Writing Software not code With Behaviour Driven Development Ben Mabey
    20. != BDD
    21. != BDD
    22. RSpec != BDD
    23. RSpec != BDD
    24. “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
    25. BDD is a mindset not a tool set
    26. </rant>
    27. * 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)
    28. Scenario: title Given [Context] When I do [Action] Then I should see [Outcome]
    29. 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]
    30. project_root/ | `-- features
    31. project_root/ | `-- features |-- awesomeness.feature |-- greatest_ever.feature
    32. project_root/ | `-- features |-- awesomeness.feature |-- greatest_ever.feature `-- support |-- env.rb `-- other_helpers.rb
    33. project_root/ | `-- features |-- awesomeness.feature |-- greatest_ever.feature `-- support |-- env.rb `-- other_helpers.rb |-- step_definitions | |-- domain_concept_A.rb | `-- domain_concept_B.rb
    34. Step Given a widget
    35. Step Definition Given /^a widget$/ do Given a widget #codes go here end
    36. Step Definition Step Mother Given /^a widget$/ do Given a widget #codes go here end
    37. Step Definition Step Mother Given /^a widget$/ do Given a widget #codes go here end
    38. 28+ Languages
    39. 28+ Languages
    40. 28+ Languages RSpec, Test::Unit, etc
    41. 28+ Languages Your Code RSpec, Test::Unit, etc
    42. Not Just for Rails
    43. Outside-In
    44. Write Scenarios
    45. Steps are pending
    46. Write Step Definition
    47. Go Down A Gear
    48. RSpec, TestUnit, etc
    49. Write Code Example (Unit Test)
    50. Make Example Pass
    51. REFACTOR!!
    52. Where Are we?
    53. Continue until...
    54. REFACTOR and REPEAT
    55. 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
    56. 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
    57. Workflow
    58. Workflow git branch -b add_wish_tracker#
    59. Workflow git branch -b add_wish_tracker# Tag Scenario or Feature with @wip
    60. Workflow git branch -b add_wish_tracker# Tag Scenario or Feature with @wip cucumber --wip --tags @wip
    61. Workflow git branch -b add_wish_tracker# Tag Scenario or Feature with @wip cucumber --wip --tags @wip Develop it Outside-In
    62. 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
    63. 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!
    64. @wip on master?
    65. @wip on master? $ rake -T cucumber
    66. @wip on master? $ rake -T cucumber rake cucumber:ok OR rake cucumber
    67. @wip on master? $ rake -T cucumber rake cucumber:ok OR rake cucumber cucumber --tags ~@wip --strict
    68. @wip on master? $ rake -T cucumber Tag Exclusion rake cucumber:ok OR rake cucumber cucumber --tags ~@wip --strict
    69. @wip on master? $ rake -T cucumber
    70. @wip on master? $ rake -T cucumber rake cucumber:wip
    71. @wip on master? $ rake -T cucumber rake cucumber:wip cucumber --tags @wip:2 --wip
    72. @wip on master? $ rake -T cucumber in flow Limit tags rake cucumber:wip cucumber --tags @wip:2 --wip
    73. @wip on master? $ rake -T cucumber in flow Limit tags rake cucumber:wip cucumber --tags @wip:2 --wip Expect failure - Success == Failure
    74. @wip on master? $ rake -T cucumber rake cucumber:all Runs both ok and wip -- great for CI
    75. 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
    76. Line # of scenario
    77. Look Ma! backtraces! Given I am logged in #features/manage_my_wishes.feature:8
    78. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) end
    79. 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
    80. 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
    81. features/step_definitions/user_steps.rb Given /^I am logged in$/ do @current_user = create_user(:email_confirmed => true) end
    82. 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
    83. 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
    84. 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
    85. 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
    86. 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
    87. 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
    88. 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
    89. 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
    90. 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
    91. 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
    92. 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
    93. No route matches “/sessions/create” with {:method=>:post} (ActionController::RoutingError)
    94. I’m going to cheat...
    95. I’m going to cheat... $ gem install thoughtbot-clearance $ ./script generate clearance $ ./script generate clearance_features
    96. Authlogic? http://github.com/hectoregm/groundwork
    97. features/step_definitions/wish_steps.rb When /^I make a "(.+)" wish$/ do |wish| end Then /^(.+) should appear on my wish list$/ do |wish| end
    98. 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
    99. 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
    100. 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
    101. 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
    102. config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :wishes
    103. config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :wishes When I make a “New car” wish uninitialized constant WishesController (NameError)
    104. config/routes.rb ActionController::Routing::Routes.draw do |map| map.resources :wishes $./script generate rspec_controller new create
    105. 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)
    106. app/views/wishes/index.html.erb <%= link_to "Make a wish", new_wish_path %>
    107. 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)
    108. 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
    109. 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 %>
    110. 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 %>
    111. View Controller
    112. spec/controllers/wishes_controller_spec.rb describe WishesController do describe "POST / (#create)" do end end
    113. 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
    114. app/controllers/wishes_controller.rb class WishesController < ApplicationController def create current_user.wishes.create(params['wish']) end end
    115. 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
    116. app/controllers/wishes_controller.rb def create current_user.wishes.create(params['wish']) redirect_to :action => :index end
    117. View Controller Model
    118. 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)
    119. 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
    120. 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
    121. 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
    122. 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
    123. 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
    124. 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
    125. app/views/wishes/index.html.erb <ul> <% @wishes.each do |wish| %> <li><%= wish.name %></li> <% end %> </ul>
    126. 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
    127. app/controllers/wishes_controller.rb def index @wishes = current_user.wishes end
    128. How do I test JS and AJAX? FAQ
    129. Slow Fast
    130. Slow Integrated Fast Isolated
    131. Slow Integrated Fast Isolated
    132. Slow Integrated Fast Isolated
    133. Slow Integrated Fast Isolated
    134. Slow Integrated Fast Isolated
    135. Slow Fast
    136. Slow Fast Joyful
    137. Slow Painful Fast Joyful
    138. Slow Painful Celerity Fast Joyful
    139. Celerity
    140. Celerity HtmlUnit
    141. Celerity HtmlUnit
    142. Celerity HtmlUnit
    143. Celerity HtmlUnit
    144. 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'
    145. What if I use MRI?
    146. Culerity http://github.com/langalex/culerity
    147. 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'
    148. Celerity + http://github.com/dstrelau/webrat
    149. HtmlUnit + http://github.com/johnnyt/webrat
    150. CodeNote http://github.com/bmabey/codenote
    151. 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
    152. 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
    153. 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
    154. 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
    155. @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. """
    156. @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. """
    157. @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
    158. Given the following presentation """ blah, blah """ Given /the following presentation$/ do |presentation| end
    159. Given the following presentation """ blah, blah """ Given /the following presentation$/ do |presentation| end Yields the multi-line string
    160. Given the following presentation """ blah, blah """ Given /the following presentation$/ do |presentation| CodeNote::PresentationLoader.setup(presentation) end
    161. RSpec Cycle
    162. And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search
    163. How do I test web services? FAQ
    164. http://github.com/chrisk/fakeweb
    165. 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
    166. 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
    167. 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
    168. 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
    169. 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
    170. 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
    171. 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
    172. 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
    173. 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
    174. 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
    175. 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)
    176. 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)
    177. When the presenter goes to the 3rd slide
    178. 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
    179. 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!
    180. And I go to the 3rd slide Then I should see "And the winner is..."
    181. 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
    182. 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
    183. And I should see an ajax spinner
    184. And I should see an ajax spinner Then /I should see an ajax spinner$/ do browser.image(:id, 'spinner').exists?.should be_true end
    185. Brief RSpec cycle?
    186. 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
    187. 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
    188. @wip Scenario: winner is displayed Given the following presentation ... And no tweets have been tweeted that match the '#free_stuff "aaron burr"' search
    189. @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!
    190. 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
    191. @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
    192. 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 |
    193. 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
    194. 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
    195. 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
    196. 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
    197. 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...
    198. 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">]
    199. 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
    200. 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
    201. 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
    202. 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
    203. @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
    204. 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
    205. 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
    206. 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
    207. RSpec Cycle
    208. 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
    209. Demo!
    210. More tricks...
    211. 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 |
    212. 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
    213. 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
    214. Table Diffing http://wiki.github.com/aslakhellesoy/cucumber/multiline-step-arguments
    215. 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 |
    216. 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 |
    217. 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 |
    218. 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 |
    219. 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 |
    220. Steps Within Steps When /^I view the wish list for "(.+)"$/ do |user_name| Given "I am logged in" visit "/wishes/#{user_name}" end
    221. Steps Within Steps When /^I view the wish list for "(.+)"$/ do |user_name| Given "I am logged in" visit "/wishes/#{user_name}" end
    222. Hooks Before do end After do |scenario| end World do end World(MyModule) World(HerModule)
    223. 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
    224. 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
    225. Drinking the Cucumber Kool-Aid?
    226. 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.
    227. Cucumber is a good hammer
    228. Cucumber is a good hammer Not everything is a nail
    229. I can skp teh unit testz?
    230. 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.
    231. SRSLY? Model specs, Controller Specs, and view specs!?
    232. W
    233. M
    234. More tests == More Maintenance
    235. Test Value = Design + Documentation + Defence (regression)
    236. if test.value > test.cost Suite.add(test) end
    237. KTHXBYE! BenMabey.com github.com/bmabey Twitter: bmabey IRC: mabes

    + Ben MabeyBen Mabey, 3 months ago

    custom

    3566 views, 10 favs, 4 embeds more stats

    Describes Outside-In development and Behvaiour Driv more

    More info about this document

    © All Rights Reserved

    Go to text version

    • Total Views 3566
      • 3195 on SlideShare
      • 371 from embeds
    • Comments 0
    • Favorites 10
    • Downloads 47
    Most viewed embeds
    • 368 views on http://www.benmabey.com
    • 1 views on http://feeds2.feedburner.com
    • 1 views on http://railsonedge.com
    • 1 views on http://www.mefeedia.com

    more

    All embeds
    • 368 views on http://www.benmabey.com
    • 1 views on http://feeds2.feedburner.com
    • 1 views on http://railsonedge.com
    • 1 views on http://www.mefeedia.com

    less

    Flagged as inappropriate Flag as inappropriate
    Flag as inappropriate

    Select your reason for flagging this presentation as inappropriate. If needed, use the feedback form to let us know more details.

    Cancel
    File a copyright complaint
    Having problems? Go to our helpdesk?

    Categories