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.
* not executed
* documentation value
Feature: title * variant of contextra
* business value up front
In order to [Business Value]
As a [Role]
I want to [Some Action] (feature)
There is no template.
What is important to
have in narrative:
* business value
* stakeholder role
* user role
* action to be taken by
user
“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
* 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)
features/manage_my_wishes.feature
Feature: manage my wishes
In order to get more stuff
As a greedy person
I want to manage my wish list for my family members to view
@proposed
Scenario: add wish
@proposed
Scenario: remove wish
@proposed
Scenario: tweet wish
features/manage_my_wishes.feature
Feature: manage my wishes
In order to get more stuff
Work In Progress
As a greedy person
I want to manage my wish list for my family members to view
@wip
Scenario: add wish
Given I am logged in
When I make a "New car" wish
Then "New car" should appear on my wish list
@proposed
Scenario: remove wish
@proposed
Scenario: tweet wish
Workflow
git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
cucumber --wip --tags @wip
Workflow
git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
cucumber --wip --tags @wip
Develop it Outside-In
Workflow
git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
cucumber --wip --tags @wip
Develop it Outside-In
git rebase ---interactive; git merge
Workflow
git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
cucumber --wip --tags @wip
Develop it Outside-In
git rebase ---interactive; git merge
Repeat!
@wip on master?
$ rake -T cucumber
rake cucumber:all
Runs both ok and wip -- great for CI
features/manage_my_wishes.feature
Feature: manage my wishes
In order to get more stuff
As a greedy person
I want to manage my wish list for my family members to view
@wip
Scenario: add wish
Given I am logged in
When I make a "New car" wish
Then "New car" should appear on my wish list
@proposed
Scenario: remove wish
@proposed
Scenario: tweet wish
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
end
Fixture Replacement, Fixjour, Factory Girl, etc
spec/fixjour_builders.rb
Fixjour do
define_builder(User) do |klass, overrides|
klass.new(
:email => "user#{counter(:user)}@email.com",
:password => 'password',
:password_confirmation => 'password'
)
end
end
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
features/step_definitions/user_steps.rb
Webrat / Awesomeness
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
features/step_definitions/user_steps.rb
Webrat / Awesomeness
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
features/step_definitions/user_steps.rb
Webrat / Awesomeness
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
features/support/env.rb
require 'webrat'
Webrat.configure do |config|
config.mode = :rails
end
features/step_definitions/user_steps.rb
Webrat / Awesomeness
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
features/support/env.rb
require 'webrat'
Webrat.configure do |config|
config.mode = :rails
end
Adapter
features/step_definitions/user_steps.rb
Webrat / Awesomeness
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
features/step_definitions/webrat_steps.rb
When /^I press "(.*)"$/ do |button|
click_button(button)
end
20+ Steps Out-of-box
When /^I follow "(.*)"$/ do |link|
click_link(link)
end
When /^I fill in "(.*)" with "(.*)"$/ do |field, value|
fill_in(field, :with => value)
end
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
# make sure we have actually logged in- so we fail fast if not
session[:user_id].should == @current_user.id
end
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
# make sure we have actually logged in- so we fail fast if not
session[:user_id].should == @current_user.id
controller.current_user.should == @current_user
end
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
Specify outcome, not implementation.
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
# make sure we have actually logged in- so we fail fast if not
session[:user_id].should == @current_user.id
controller.current_user.should == @current_user
response.should contain("Signed in successfully")
end
features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
# make sure we have actually logged in- so we fail fast if not
response.should contain("Signed in successfully")
end
No route matches “/sessions/create” with
{:method=>:post} (ActionController::RoutingError)
features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
visit "/wishes"
click_link "Make a wish"
fill_in "Wish", :with => wish
click_button
end
Then /^(.+) should appear on my wish list$/ do |wish|
end
features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
visit "/wishes"
click_link "Make a wish"
fill_in "Wish", :with => wish
click_button
end
Then /^(.+) should appear on my wish list$/ do |wish|
response.should contain("Your wish has been added!")
response.should contain(wish)
end
features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
visit "/wishes"
click_link "Make a wish"
fill_in "Wish", :with => wish
click_button
end
No route matches “/wishes” with
{:method=>:get} appear on my wish list$/ do |wish|
Then /^(.+) should (ActionController::RoutingError)
response.should contain("Your wish has been added!")
response.should contain(wish)
end
spec/controllers/wishes_controller_spec.rb
describe WishesController do
describe "POST / (#create)" do
it "creates a new wish for the user with the params" do
user = mock_model(User, :wishes => mock("wishes association"))
controller.stub!(:current_user).and_return(user)
user.wishes.should_receive(:create).with(wish_params)
post :create, 'wish' => {'name' => 'Dog'}
end
end
end
app/controllers/wishes_controller.rb
def create
current_user.wishes.create(params['wish'])
redirect_to :action => :index
end
When I make a “New car” wish
undefined method `wishes` for #<User:0x268e898>
(NoMethodError)
app/models/wish.rb
class Wish < ActiveRecord::Base
belongs_to :user
end
app/models/user.rb
class User < ActiveRecord::Base
include Clearance::App::Models::User
has_many :wishes
end
app/models/wish.rb
class Wish < ActiveRecord::Base
belongs_to :user
end
app/models/user.rb
When I make a “New car” wish
Then “New <car” should appear on my wish
class User ActiveRecord::Base
include Clearance::App::Models::User
has_many the following element’s content to include
expected:wishes
“Your wish has been added!”
end
spec/controllers/wishes_controller_spec.rb
it "notifies the user of creation via the flash" do
do_post
flash[:success].should == "Your wish has been added!"
end
app/controllers/wishes_controller.rb
def create
current_user.wishes.create(params['wish'])
flash[:success] = "Your wish has been added!"
redirect_to :action => :index
end
spec/controllers/wishes_controller_spec.rb
it "should notifies the user of creation via the flash" do
do_post
flash[:success].should == "Your wish has been added!"
end
app/controllers/wishes_controller.rb
Then “New car” should appear on my wish
expected the following element’s content to include
def create
“New car”
current_user.wishes.create(params['wish'])
flash[:success] = "Your wish has been added!"
redirect_to :action => :index
end
Feature: CLI Server
For example of how to
In order to save me time and headaches test CLI tools take a look
As a presenter of code at CodeNote on github.
I create a presentation in plaintext RSpec and Cucumber also
have good examples of
a'la Slidedown (from Pat Nakajima) how to do this.
and have CodeNote serve it up for me
Scenario: basic presentation loading and viewing
Given that the codenote server is not running
And a file named "presentation.md" with:
"""
!TITLE My Presentation
!PRESENTER Ben Mabey
# This is the title slide
!SLIDE
# This is second slide...
"""
When I run "codenote_load presentation.md"
And I run "codenote"
And I visit the servers address
Feature: Twitter Quiz
In order to encourage audience participation where
90% of the audience is hacking on laptops
As a presenter I want audience members
To answer certain questions via twitter
Feature: Twitter Quiz
In order to encourage audience participation where
90% of the audience is hacking on laptops
As a presenter I want audience members
To answer certain questions via twitter
@proposed
Scenario: waiting for an answer
@proposed
Scenario: winner is displayed
@proposed
Scenario: fail whale
@proposed
Scenario: network timeout
Feature: Twitter Quiz
In order to encourage audience participation where
90% of the audience is hacking on laptops
As a presenter I want audience members
To answer certain questions via twitter
@wip
Scenario: waiting for an answer
@wip
Scenario: waiting for an answer
Given the following presentation
"""
!TITLE American History
!PRESENTER David McCullough
# Wanna win a prize?
### You'll have to answer a question...
### in a tweet! First correct tweet wins!
!SLIDE
# Who shot Alexander Hamilton?
## You must use #free_stuff in your tweet.
!DYNAMIC-SLIDE TwitterQuiz '#free_stuff "aaron burr"'
!SLIDE
Okay, that was fun.
Lets actually start now.
"""
@wip
Scenario: waiting for an answer
Given the following presentation
"""
!TITLE American History
!PRESENTER David McCullough
# Wanna win a prize?
### You'll have to answer a question...
### in a tweet! First correct tweet wins!
!SLIDE
# Who shot Alexander Hamilton?
## You must use #free_stuff in your tweet.
!DYNAMIC-SLIDE TwitterQuiz '#free_stuff "aaron burr"'
!SLIDE
Okay, that was fun.
Lets actually start now.
"""
@wip
Scenario: waiting for an answer
Given the following presentation ...
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
When the presenter goes to the 3rd slide
And I go to the 3rd slide
Then I should see "And the winner is..."
And I should see an ajax spinner
Given the following presentation
"""
blah, blah
"""
Given /the following presentation$/ do |presentation|
end
Given the following presentation
"""
blah, blah
"""
Given /the following presentation$/ do |presentation|
end
Yields the multi-line string
Given the following presentation
"""
blah, blah
"""
Given /the following presentation$/ do |presentation|
CodeNote::PresentationLoader.setup(presentation)
end
http://github.com/chrisk/fakeweb
page = `curl -is http://www.google.com/`
FakeWeb.register_uri(:get, "http://www.google.com/",
:response => page)
Net::HTTP.get(URI.parse("http://www.google.com/"))
# => Full response, including headers
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
end
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
Helpers
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
Helpers
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
“Every time you monkeypatch
def search_url_for(query)
Object, a kitten dies.”
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
module TwitterHelpers
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
end
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
module TwitterHelpers
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
end
World(TwitterHelpers)
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
module TwitterHelpers
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
end
World(TwitterHelpers)
When the presenter goes to the 3rd slide
When /the presenter goes to the
(d+)(?:st|nd|rd|th) slide$/ do |slide_number|
presenter_browser.goto path('/')
(slide_number.to_i - 1).times do
presenter_browser.link(:text, "Next").click
end
end
When the presenter goes to the 3rd slide
When /the presenter goes to the
(d+)(?:st|nd|rd|th) slide$/ do |slide_number|
presenter_browser.goto path('/')
(slide_number.to_i - 1).times do
presenter_browser.link(:text, "Next").click
end
end
Presenter has own browser,
multiple sessions!
And I go to the 3rd slide
Then I should see "And the winner is..."
And I go to the 3rd slide
Then I should see "And the winner is..."
When /I go to the (d+)(?:st|nd|rd|th) slide$/ do |slide_number|
browser.goto path("/slides/#{slide_number}")
end
And I go to the 3rd slide
Then I should see "And the winner is..."
When /I go to the (d+)(?:st|nd|rd|th) slide$/ do |slide_number|
browser.goto path("/slides/#{slide_number}")
end
Then /I should see "(["]*)"$/ do |text|
browser.should contain(text)
end
Scenario: waiting for an answer
Given the following presentation ...
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
When the presenter goes to the 3rd slide
And I go to the 3rd slide
Then I should see "And the winner is..."
And I should see an ajax spinner
Feature: Twitter Quiz
In order to encourage audience participation where
90% of the audience is hacking on laptops
As a presenter I want audience members
To answer certain questions via twitter
Scenario: waiting for an answer
.....
@wip
Scenario: winner is displayed
@wip
Scenario: winner is displayed
Given the following presentation ...
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
@wip
Scenario: winner is displayed
Given the following presentation ...
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
Duplication of context!
Feature: Twitter Quiz
...
Background: A presentation with a Twitter Quiz
Given the following presentation
"""
blah, blah
"""
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
Scenario: waiting for an answer
Extract to ‘Background’
When the presenter goes to the 3rd slide
And I go to the 3rd slide
Then I should see "And the winner is..."
And I should see an ajax spinner
@wip
Scenario: winner is displayed
@wip
Scenario: winner is displayed
When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
And the presenter goes to the 3rd slide
And I go to the 3rd slide
Then I should see @jefferson's tweet along with his avatar
When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
end
When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
Cucumber::AST::Table
end
When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
Umm... that won’t work.
end
When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
What I would really like is a
test data builder/factory for
end
twitter searches...
When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
FakeTwitter.register_search(query, {
:results => tweet_table.hashes})
end
When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
FakeTwitter.register_search(query, {
:results => tweet_table.hashes})
Our headers and columns
aren’t compatible with API.
end
When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
tweet_table.map_headers! do |header|
header.downcase.gsub(' ','_')
end
FakeTwitter.register_search(query, {
:results => tweet_table.hashes})
end
When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
tweet_table.map_headers! do |header|
header.downcase.gsub(' ','_')
end
tweet_table.map_column!('created_at') do |relative_time|
interpret_time(relative_time)
end
FakeTwitter.register_search(query, {
:results => tweet_table.hashes})
end
@wip
Scenario: winner is displayed
When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
And the presenter goes to the 3rd slide
And I go to the 3rd slide
Then I should see @jefferson's tweet along with his avatar
Then %r{I should see @([']+)'s tweet along with
(?:his|her) avatar$} do |user|
tweet = FakeTwitter.tweets_from(user).first
browser.should contain(tweet['text'], :wait => 10)
browser.should have_image(:src, tweet['profile_image_url'])
end
Timeout
Then %r{I should see @([']+)'s tweet along with
(?:his|her) avatar$} do |user|
tweet = FakeTwitter.tweets_from(user).first
browser.should contain(tweet['text'], :wait => 10)
browser.should have_image(:src, tweet['profile_image_url'])
end
Spec::Matchers.define :contain do |text, options|
match do |browser|
options[:wait] ||= 0
browser.wait_until(options[:wait]) do
browser.text.include?(text)
end
end
end
Then %r{I should see @([']+)'s tweet along with
(?:his|her) avatar$} do |user|
tweet = FakeTwitter.tweets_from(user).first
browser.should contain(tweet['text'], :wait => 10)
browser.should have_image(:src, tweet['profile_image_url'])
end
Spec::Matchers.define :contain do |text, options|
match do |browser|
options[:wait] ||= 0
browser.wait_until(options[:wait]) do
browser.text.include?(text)
end
end
end
Keep trying after sleeping
until it times out
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
Scenario: view members list
Given the following wishes exist
| Wish | Family Member |
| Laptop | Thomas |
| Nintendo Wii | Candace |
| CHEEZBURGER | FuzzBuzz |
When I view the wish list for "Candace"
Then I should see the following wishes
| Wish |
| Nintendo Wii |
Given the following wishes exist
| Wish | Family Member |
| Laptop | Thomas |
| Nintendo Wii | Candace |
| CHEEZBURGER | FuzzBuzz |
features/step_definitions/wish_steps.rb
Given /^the following wishes exist$/ do |table|
end
end
Given the following wishes exist
| Wish | Family Member |
| Laptop | Thomas |
| Nintendo Wii | Candace |
| CHEEZBURGER | FuzzBuzz |
features/step_definitions/wish_steps.rb
Given /^the following wishes exist$/ do |table|
table.hashes.each do |row|
member = User.find_by_name(row["Family Member"]) ||
create_user(:name => row["Family Member"])
member.wishes.create!(:name => row["Wish"])
end
end
Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario Outline: Add two numbers
Given I have entered <input_1> into the calculator
And I have entered <input_2> into the calculator
When I press <button>
Then the result should be <output> on the screen
Scenarios:
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
| 0 | 40 | add | 40 |
Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario Outline: Add two numbers
Given I have entered <input_1> into the calculator
And I have entered <input_2> into the calculator
When I press <button>
Then the result should be <output> on the screen
Scenarios: addition
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
Scenarios: subtraction
| 0 | 40 | minus | -40 |
Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario Outline: Add two numbers
Given I have entered <input_1> into the calculator
And I have entered <input_2> into the calculator
When I press <button>
Then the result should be <output> on the screen
Scenarios:
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
| 0 | 40 | add | 40 |
Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario Outline: Add two numbers
Given I have entered <input_1> into the calculator
And I have entered <input_2> into the calculator
When I press <button>
Then the result should be <output> on the screen
Scenarios:
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
| 0 | 40 | add | 40 |
Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario Outline: Add two numbers
Given I have entered <input_1> into the calculator
And I have entered <input_2> into the calculator
When I press <button>
Then the result should be <output> on the screen
Scenarios:
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
| 0 | 40 | add | 40 |
Steps Within Steps
When /^I view the wish list for "(.+)"$/ do |user_name|
Given "I am logged in"
visit "/wishes/#{user_name}"
end
Steps Within Steps
When /^I view the wish list for "(.+)"$/ do |user_name|
Given "I am logged in"
visit "/wishes/#{user_name}"
end
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
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.
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.