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
                  * busin...
There is no template.
What is important to
have in narrative:

 * business value
 * stakeholder role
 * user role
 * actio...
<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 t...
BDD is a
  mindset
not a tool set
</rant>
* not executed
                  * documentation value

Feature: title    * variant of contextra
                  * busin...
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...
project_root/
|
`-- features
project_root/
|
`-- features
    |-- awesomeness.feature
    |-- greatest_ever.feature
project_root/
|
`-- features
    |-- awesomeness.feature
    |-- greatest_ever.feature
    `-- support
        |-- env.rb
...
project_root/
|
`-- features
    |-- awesomeness.feature
    |-- greatest_ever.feature
    `-- support
        |-- env.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...
Step           Definition
             Step Mother

                   Given /^a widget$/ do
Given a widget       #codes go...
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 ma...
features/manage_my_wishes.feature
Feature: manage my wishes

 In order to get more stuff
              Work In Progress
 A...
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 i...
Workflow
 git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
    cucumber --wip --tags @wip
        Develop ...
Workflow
 git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
    cucumber --wip --tags @wip
        Develop ...
@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 -...
@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



...
@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 ma...
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)...
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)...
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)...
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)...
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)...
features/step_definitions/user_steps.rb
       Webrat / Awesomeness
Given /^I am logged in$/ do
  @current_user = create_us...
features/step_definitions/user_steps.rb
       Webrat / Awesomeness
Given /^I am logged in$/ do
  @current_user = create_us...
features/step_definitions/user_steps.rb
       Webrat / Awesomeness
Given /^I am logged in$/ do
  @current_user = create_us...
features/step_definitions/user_steps.rb
       Webrat / Awesomeness
Given /^I am logged in$/ do
  @current_user = create_us...
features/step_definitions/user_steps.rb
       Webrat / Awesomeness
Given /^I am logged in$/ do
  @current_user = create_us...
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)...
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)...
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)...
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)...
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
  @current_user = create_user(:email_confirmed => true)...
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_featu...
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 ...
features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|

end

Then /^(.+) should appear on my wish ...
features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
  visit "/wishes"
  click_link "Make a wish...
features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
  visit "/wishes"
  click_link "Make a wish...
features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
  visit "/wishes"
  click_link "Make a wish...
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
 ...
config/routes.rb
 ActionController::Routing::Routes.draw do |map|

  map.resources :wishes




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

 map.resources :wishes




When I make a “New car” wish
 ...
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 fi...
features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
  visit "/wishes"
  click_link "Make a wish...
features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
  visit "/wishes"
  click_link "Make a wish...
features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
  visit "/wishes"
  click_link "Make a wish...
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 ...
app/controllers/wishes_controller.rb
class WishesController < ApplicationController

  def create
    current_user.wishes....
spec/controllers/wishes_controller_spec.rb
describe WishesController do
  describe "POST / (#create)" do
    before(:each)...
app/controllers/wishes_controller.rb
 def create
   current_user.wishes.create(params['wish'])
   redirect_to :action => :...
View

Controller

  Model
app/controllers/wishes_controller.rb
  def create
    current_user.wishes.create(params['wish'])
    redirect_to :action =...
app/controllers/wishes_controller.rb
  def create
    current_user.wishes.create(params['wish'])
    redirect_to :action =...
app/models/wish.rb
class Wish < ActiveRecord::Base
  belongs_to :user
end



app/models/user.rb
class User < ActiveRecord:...
app/models/wish.rb
class Wish < ActiveRecord::Base
  belongs_to :user
end



app/models/user.rb
When I make a “New car” wi...
spec/controllers/wishes_controller_spec.rb
it "notifies the user of creation via the flash" do
  do_post
  flash[:success]...
spec/controllers/wishes_controller_spec.rb
it "notifies the user of creation via the flash" do
  do_post
  flash[:success]...
spec/controllers/wishes_controller_spec.rb
it "should notifies the user of creation via the flash" do
  do_post
  flash[:s...
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 "assi...
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...
What if I use MRI?
Culerity

http://github.com/langalex/culerity
require "rubygems"
require "culerity"

culerity_server = Culerity::run_server

browser = Culerity::RemoteBrowserProxy.new(...
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 h...
Feature: Twitter Quiz
  In order to encourage audience participation where
  90% of the audience is hacking on laptops
  A...
Feature: Twitter Quiz
  In order to encourage audience participation where
  90% of the audience is hacking on laptops
  A...
Feature: Twitter Quiz
  In order to encourage audience participation where
  90% of the audience is hacking on laptops
  A...
@wip
Scenario: waiting for an answer
  Given the following presentation
     """
     !TITLE American History
     !PRESEN...
@wip
Scenario: waiting for an answer
  Given the following presentation
     """
     !TITLE American History
     !PRESEN...
@wip
Scenario: waiting for an answer
  Given the following presentation ...
   And no tweets have been tweeted that match
...
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



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

Given /the following presentation$/ do |presentation|
  CodeNot...
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....
And no tweets have been tweeted that match
      the '#free_stuff "aaron burr"' search


Given %r{no tweets have been twee...
And no tweets have been tweeted that match
      the '#free_stuff "aaron burr"' search


Given %r{no tweets have been twee...
Given %r{no tweets have been tweeted that match
         the '([']*)' search$} do |query|
  FakeWeb.register_uri(:get, sea...
Given %r{no tweets have been tweeted that match
         the '([']*)' search$} do |query|
  FakeWeb.register_uri(:get, sea...
Given %r{no tweets have been tweeted that match
         the '([']*)' search$} do |query|
  FakeWeb.register_uri(:get, sea...
Given %r{no tweets have been tweeted that match
         the '([']*)' search$} do |query|
  FakeWeb.register_uri(:get, sea...
Given %r{no tweets have been tweeted that match
         the '([']*)' search$} do |query|
  FakeWeb.register_uri(:get, sea...
Given %r{no tweets have been tweeted that match
         the '([']*)' search$} do |query|
  FakeWeb.register_uri(:get, sea...
Given %r{no tweets have been tweeted that match
         the '([']*)' search$} do |query|
  FakeWeb.register_uri(:get, sea...
Given %r{no tweets have been tweeted that match
         the '([']*)' search$} do |query|
  FakeWeb.register_uri(:get, sea...
Given %r{no tweets have been tweeted that match
         the '([']*)' search$} do |query|
  FakeWeb.register_uri(:get, sea...
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 |sl...
When the presenter goes to the 3rd slide



When /the presenter goes to the
            (d+)(?:st|nd|rd|th) slide$/ do |sl...
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 |sl...
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 |sl...
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 ...
Brief RSpec cycle?
Scenario: waiting for an answer
  Given the following presentation ...
   And no tweets have been tweeted that match
     ...
Feature: Twitter Quiz
  In order to encourage audience participation where
  90% of the audience is hacking on laptops
  A...
@wip
Scenario: winner is displayed
  Given the following presentation ...
  And no tweets have been tweeted that match
   ...
@wip
Scenario: winner is displayed
  Given the following presentation ...
  And no tweets have been tweeted that match
   ...
Feature: Twitter Quiz
  ...
  Background: A presentation with a Twitter Quiz

   Given the following presentation
     """...
@wip
Scenario: winner is displayed
  When the following tweets are tweeted that match the '#free_stuff "aaron burr"' searc...
When the following tweets are tweeted that match
         the '#free_stuff "aaron burr"' search
| From User    | Text     ...
When the following tweets are tweeted that match
         the '#free_stuff "aaron burr"' search
| From User    | Text     ...
When the following tweets are tweeted that match
         the '#free_stuff "aaron burr"' search
| From User    | Text     ...
When the following tweets are tweeted that match
         the '#free_stuff "aaron burr"' search
| From User    | Text     ...
When the following tweets are tweeted that match
         the '#free_stuff "aaron burr"' search
| From User    | Text     ...
When the following tweets are tweeted that match
         the '#free_stuff "aaron burr"' search
| From User    | Text     ...
http://github.com/bmabey/faketwitter
require 'faketwitter'

FakeTwitter.register_search("#cheese",
          {:results => ...
When the following tweets are tweeted that match
         the '#free_stuff "aaron burr"' search
| From User    | Text     ...
When the following tweets are tweeted that match
         the '#free_stuff "aaron burr"' search
| From User    | Text     ...
When the following tweets are tweeted that match
         the '#free_stuff "aaron burr"' search
| From User    | Text     ...
When the following tweets are tweeted that match
         the '#free_stuff "aaron burr"' search
| From User    | Text     ...
@wip
Scenario: winner is displayed
   When the following tweets are tweeted that match the '#free_stuff   "aaron burr"' se...
Then %r{I should see @([']+)'s tweet along with
          (?:his|her) avatar$} do |user|
  tweet = FakeTwitter.tweets_from...
Then %r{I should see @([']+)'s tweet along with
          (?:his|her) avatar$} do |user|
  tweet = FakeTwitter.tweets_from...
Then %r{I should see @([']+)'s tweet along with
          (?:his|her) avatar$} do |user|
  tweet = FakeTwitter.tweets_from...
RSpec Cycle
Scenario: winner is displayed
   When the following tweets are tweeted that match the '#free_stuff   "aaron burr"' search
...
Demo!
More tricks...
Scenario: view members list
  Given the following wishes exist
    | Wish          | Family Member   |
    | Laptop       ...
Given the following      wishes exist
    | Wish          |      Family Member    |
    | Laptop        |      Thomas     ...
Given the following      wishes exist
    | Wish          |      Family Member    |
    | Laptop        |      Thomas     ...
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

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

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

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

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

  Scena...
Steps Within Steps
When /^I view the wish list for "(.+)"$/ do |user_name|
  Given "I am logged in"
  visit "/wishes/#{use...
Steps Within Steps
When /^I view the wish list for "(.+)"$/ do |user_name|
  Given "I am logged in"
  visit "/wishes/#{use...
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  ...
Spork


                           Sick of slow loading times? Spork
                           will load your main enviro...
Drinking the
 Cucumber
 Kool-Aid?
Integration tests
       are a scam
                                        J. B. Rainsberger
       http://www.jbrains.ca...
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           ...
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
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Writing Software not Code with Cucumber
Upcoming SlideShare
Loading in...5
×

Writing Software not Code with Cucumber

40,485

Published on

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

Published in: Technology, Self Improvement
1 Comment
68 Likes
Statistics
Notes
No Downloads
Views
Total Views
40,485
On Slideshare
0
From Embeds
0
Number of Embeds
16
Actions
Shares
0
Downloads
530
Comments
1
Likes
68
Embeds 0
No embeds

No notes for slide

Transcript of "Writing Software not Code with Cucumber"

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

    Clipping is a handy way to collect important slides you want to go back to later.

×