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.
25. * not executed
* documentation value
Feature: title * variant of contextra
* business value up front
In order to [Business Value]
As a [Role]
I want to [Some Action] (feature)
26. There is no template.
What is important to
have in narrative:
* business value
* stakeholder role
* user role
* action to be taken by
user
33. “All of these tools are great...
but, in the end, tools are tools.
While RSpec and Cucumber are
optimized for BDD, using them
doesn’t automatically mean
you’re doing BDD"
The RSpec Book
36. * not executed
* documentation value
Feature: title * variant of contextra
* business value up front
In order to [Business Value]
As a [Role]
I want to [Some Action] (feature)
38. Scenario: title
Given [Context]
And [More Context]
When I do [Action]
And [Other Action]
Then I should see [Outcome]
But I should not see [Outcome]
69. features/manage_my_wishes.feature
Feature: manage my wishes
In order to get more stuff
As a greedy person
I want to manage my wish list for my family members to view
@proposed
Scenario: add wish
@proposed
Scenario: remove wish
@proposed
Scenario: tweet wish
70. features/manage_my_wishes.feature
Feature: manage my wishes
In order to get more stuff
Work In Progress
As a greedy person
I want to manage my wish list for my family members to view
@wip
Scenario: add wish
Given I am logged in
When I make a "New car" wish
Then "New car" should appear on my wish list
@proposed
Scenario: remove wish
@proposed
Scenario: tweet wish
73. Workflow
git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
74. Workflow
git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
cucumber --wip --tags @wip
75. Workflow
git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
cucumber --wip --tags @wip
Develop it Outside-In
76. Workflow
git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
cucumber --wip --tags @wip
Develop it Outside-In
git rebase ---interactive; git merge
77. Workflow
git branch -b add_wish_tracker#
Tag Scenario or Feature with @wip
cucumber --wip --tags @wip
Develop it Outside-In
git rebase ---interactive; git merge
Repeat!
88. @wip on master?
$ rake -T cucumber
rake cucumber:all
Runs both ok and wip -- great for CI
89. features/manage_my_wishes.feature
Feature: manage my wishes
In order to get more stuff
As a greedy person
I want to manage my wish list for my family members to view
@wip
Scenario: add wish
Given I am logged in
When I make a "New car" wish
Then "New car" should appear on my wish list
@proposed
Scenario: remove wish
@proposed
Scenario: tweet wish
98. features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
end
Fixture Replacement, Fixjour, Factory Girl, etc
spec/fixjour_builders.rb
Fixjour do
define_builder(User) do |klass, overrides|
klass.new(
:email => "user#{counter(:user)}@email.com",
:password => 'password',
:password_confirmation => 'password'
)
end
end
100. features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
101. features/step_definitions/user_steps.rb
Webrat / Awesomeness
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
102. features/step_definitions/user_steps.rb
Webrat / Awesomeness
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
103. features/step_definitions/user_steps.rb
Webrat / Awesomeness
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
features/support/env.rb
require 'webrat'
Webrat.configure do |config|
config.mode = :rails
end
104. features/step_definitions/user_steps.rb
Webrat / Awesomeness
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
features/support/env.rb
require 'webrat'
Webrat.configure do |config|
config.mode = :rails
end
Adapter
105. features/step_definitions/user_steps.rb
Webrat / Awesomeness
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
features/step_definitions/webrat_steps.rb
When /^I press "(.*)"$/ do |button|
click_button(button)
end
20+ Steps Out-of-box
When /^I follow "(.*)"$/ do |link|
click_link(link)
end
When /^I fill in "(.*)" with "(.*)"$/ do |field, value|
fill_in(field, :with => value)
end
106. features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
end
107. features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
# make sure we have actually logged in- so we fail fast if not
session[:user_id].should == @current_user.id
end
108. features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
# make sure we have actually logged in- so we fail fast if not
session[:user_id].should == @current_user.id
controller.current_user.should == @current_user
end
109. features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
Specify outcome, not implementation.
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
# make sure we have actually logged in- so we fail fast if not
session[:user_id].should == @current_user.id
controller.current_user.should == @current_user
response.should contain("Signed in successfully")
end
110. features/step_definitions/user_steps.rb
Given /^I am logged in$/ do
@current_user = create_user(:email_confirmed => true)
visit new_session_path
fill_in "Email", :with => @current_user.email
fill_in "Password", :with => valid_user_attributes["password"]
click_button
# make sure we have actually logged in- so we fail fast if not
response.should contain("Signed in successfully")
end
112. No route matches “/sessions/create” with
{:method=>:post} (ActionController::RoutingError)
119. features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
visit "/wishes"
click_link "Make a wish"
fill_in "Wish", :with => wish
click_button
end
Then /^(.+) should appear on my wish list$/ do |wish|
end
120. features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
visit "/wishes"
click_link "Make a wish"
fill_in "Wish", :with => wish
click_button
end
Then /^(.+) should appear on my wish list$/ do |wish|
response.should contain("Your wish has been added!")
response.should contain(wish)
end
121. features/step_definitions/wish_steps.rb
When /^I make a "(.+)" wish$/ do |wish|
visit "/wishes"
click_link "Make a wish"
fill_in "Wish", :with => wish
click_button
end
No route matches “/wishes” with
{:method=>:get} appear on my wish list$/ do |wish|
Then /^(.+) should (ActionController::RoutingError)
response.should contain("Your wish has been added!")
response.should contain(wish)
end
133. spec/controllers/wishes_controller_spec.rb
describe WishesController do
describe "POST / (#create)" do
it "creates a new wish for the user with the params" do
user = mock_model(User, :wishes => mock("wishes association"))
controller.stub!(:current_user).and_return(user)
user.wishes.should_receive(:create).with(wish_params)
post :create, 'wish' => {'name' => 'Dog'}
end
end
end
138. app/controllers/wishes_controller.rb
def create
current_user.wishes.create(params['wish'])
redirect_to :action => :index
end
When I make a “New car” wish
undefined method `wishes` for #<User:0x268e898>
(NoMethodError)
140. app/models/wish.rb
class Wish < ActiveRecord::Base
belongs_to :user
end
app/models/user.rb
class User < ActiveRecord::Base
include Clearance::App::Models::User
has_many :wishes
end
141. app/models/wish.rb
class Wish < ActiveRecord::Base
belongs_to :user
end
app/models/user.rb
When I make a “New car” wish
Then “New <car” should appear on my wish
class User ActiveRecord::Base
include Clearance::App::Models::User
has_many the following element’s content to include
expected:wishes
“Your wish has been added!”
end
143. spec/controllers/wishes_controller_spec.rb
it "notifies the user of creation via the flash" do
do_post
flash[:success].should == "Your wish has been added!"
end
app/controllers/wishes_controller.rb
def create
current_user.wishes.create(params['wish'])
flash[:success] = "Your wish has been added!"
redirect_to :action => :index
end
144. spec/controllers/wishes_controller_spec.rb
it "should notifies the user of creation via the flash" do
do_post
flash[:success].should == "Your wish has been added!"
end
app/controllers/wishes_controller.rb
Then “New car” should appear on my wish
expected the following element’s content to include
def create
“New car”
current_user.wishes.create(params['wish'])
flash[:success] = "Your wish has been added!"
redirect_to :action => :index
end
173. Feature: CLI Server
For example of how to
In order to save me time and headaches test CLI tools take a look
As a presenter of code at CodeNote on github.
I create a presentation in plaintext RSpec and Cucumber also
have good examples of
a'la Slidedown (from Pat Nakajima) how to do this.
and have CodeNote serve it up for me
Scenario: basic presentation loading and viewing
Given that the codenote server is not running
And a file named "presentation.md" with:
"""
!TITLE My Presentation
!PRESENTER Ben Mabey
# This is the title slide
!SLIDE
# This is second slide...
"""
When I run "codenote_load presentation.md"
And I run "codenote"
And I visit the servers address
174. Feature: Twitter Quiz
In order to encourage audience participation where
90% of the audience is hacking on laptops
As a presenter I want audience members
To answer certain questions via twitter
175. Feature: Twitter Quiz
In order to encourage audience participation where
90% of the audience is hacking on laptops
As a presenter I want audience members
To answer certain questions via twitter
@proposed
Scenario: waiting for an answer
@proposed
Scenario: winner is displayed
@proposed
Scenario: fail whale
@proposed
Scenario: network timeout
176. Feature: Twitter Quiz
In order to encourage audience participation where
90% of the audience is hacking on laptops
As a presenter I want audience members
To answer certain questions via twitter
@wip
Scenario: waiting for an answer
177. @wip
Scenario: waiting for an answer
Given the following presentation
"""
!TITLE American History
!PRESENTER David McCullough
# Wanna win a prize?
### You'll have to answer a question...
### in a tweet! First correct tweet wins!
!SLIDE
# Who shot Alexander Hamilton?
## You must use #free_stuff in your tweet.
!DYNAMIC-SLIDE TwitterQuiz '#free_stuff "aaron burr"'
!SLIDE
Okay, that was fun.
Lets actually start now.
"""
178. @wip
Scenario: waiting for an answer
Given the following presentation
"""
!TITLE American History
!PRESENTER David McCullough
# Wanna win a prize?
### You'll have to answer a question...
### in a tweet! First correct tweet wins!
!SLIDE
# Who shot Alexander Hamilton?
## You must use #free_stuff in your tweet.
!DYNAMIC-SLIDE TwitterQuiz '#free_stuff "aaron burr"'
!SLIDE
Okay, that was fun.
Lets actually start now.
"""
179. @wip
Scenario: waiting for an answer
Given the following presentation ...
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
When the presenter goes to the 3rd slide
And I go to the 3rd slide
Then I should see "And the winner is..."
And I should see an ajax spinner
180. Given the following presentation
"""
blah, blah
"""
Given /the following presentation$/ do |presentation|
end
181. Given the following presentation
"""
blah, blah
"""
Given /the following presentation$/ do |presentation|
end
Yields the multi-line string
182. Given the following presentation
"""
blah, blah
"""
Given /the following presentation$/ do |presentation|
CodeNote::PresentationLoader.setup(presentation)
end
187. http://github.com/chrisk/fakeweb
page = `curl -is http://www.google.com/`
FakeWeb.register_uri(:get, "http://www.google.com/",
:response => page)
Net::HTTP.get(URI.parse("http://www.google.com/"))
# => Full response, including headers
188. And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
end
189. And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
190. Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
191. Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
Helpers
192. Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
Helpers
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
193. Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
194. Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
195. Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
“Every time you monkeypatch
def search_url_for(query)
Object, a kitten dies.”
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
196. Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
module TwitterHelpers
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
end
197. Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
module TwitterHelpers
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
end
World(TwitterHelpers)
198. Given %r{no tweets have been tweeted that match
the '([']*)' search$} do |query|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
module TwitterHelpers
def search_url_for(query)
"http://search.twitter.com/search.json?q=#{CGI.escape(query)}"
end
def canned_response_for(query)
....
return file_path
end
end
World(TwitterHelpers)
200. When the presenter goes to the 3rd slide
When /the presenter goes to the
(d+)(?:st|nd|rd|th) slide$/ do |slide_number|
presenter_browser.goto path('/')
(slide_number.to_i - 1).times do
presenter_browser.link(:text, "Next").click
end
end
201. When the presenter goes to the 3rd slide
When /the presenter goes to the
(d+)(?:st|nd|rd|th) slide$/ do |slide_number|
presenter_browser.goto path('/')
(slide_number.to_i - 1).times do
presenter_browser.link(:text, "Next").click
end
end
Presenter has own browser,
multiple sessions!
202. And I go to the 3rd slide
Then I should see "And the winner is..."
203. And I go to the 3rd slide
Then I should see "And the winner is..."
When /I go to the (d+)(?:st|nd|rd|th) slide$/ do |slide_number|
browser.goto path("/slides/#{slide_number}")
end
204. And I go to the 3rd slide
Then I should see "And the winner is..."
When /I go to the (d+)(?:st|nd|rd|th) slide$/ do |slide_number|
browser.goto path("/slides/#{slide_number}")
end
Then /I should see "(["]*)"$/ do |text|
browser.should contain(text)
end
208. Scenario: waiting for an answer
Given the following presentation ...
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
When the presenter goes to the 3rd slide
And I go to the 3rd slide
Then I should see "And the winner is..."
And I should see an ajax spinner
209. Feature: Twitter Quiz
In order to encourage audience participation where
90% of the audience is hacking on laptops
As a presenter I want audience members
To answer certain questions via twitter
Scenario: waiting for an answer
.....
@wip
Scenario: winner is displayed
210. @wip
Scenario: winner is displayed
Given the following presentation ...
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
211. @wip
Scenario: winner is displayed
Given the following presentation ...
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
Duplication of context!
212. Feature: Twitter Quiz
...
Background: A presentation with a Twitter Quiz
Given the following presentation
"""
blah, blah
"""
And no tweets have been tweeted that match
the '#free_stuff "aaron burr"' search
Scenario: waiting for an answer
Extract to ‘Background’
When the presenter goes to the 3rd slide
And I go to the 3rd slide
Then I should see "And the winner is..."
And I should see an ajax spinner
@wip
Scenario: winner is displayed
213. @wip
Scenario: winner is displayed
When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
And the presenter goes to the 3rd slide
And I go to the 3rd slide
Then I should see @jefferson's tweet along with his avatar
214. When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
215. When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
end
216. When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
Cucumber::AST::Table
end
217. When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
end
218. When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
Umm... that won’t work.
end
219. When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
FakeWeb.register_uri(:get, search_url_for(query),
:body => canned_response_for(query))
What I would really like is a
test data builder/factory for
end
twitter searches...
221. When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
FakeTwitter.register_search(query, {
:results => tweet_table.hashes})
end
222. When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
FakeTwitter.register_search(query, {
:results => tweet_table.hashes})
Our headers and columns
aren’t compatible with API.
end
223. When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
tweet_table.map_headers! do |header|
header.downcase.gsub(' ','_')
end
FakeTwitter.register_search(query, {
:results => tweet_table.hashes})
end
224. When the following tweets are tweeted that match
the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
When %r{the following tweets are tweeted that match
the '([']*)' search$} do |query, tweet_table|
tweet_table.map_headers! do |header|
header.downcase.gsub(' ','_')
end
tweet_table.map_column!('created_at') do |relative_time|
interpret_time(relative_time)
end
FakeTwitter.register_search(query, {
:results => tweet_table.hashes})
end
225. @wip
Scenario: winner is displayed
When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
And the presenter goes to the 3rd slide
And I go to the 3rd slide
Then I should see @jefferson's tweet along with his avatar
226. Then %r{I should see @([']+)'s tweet along with
(?:his|her) avatar$} do |user|
tweet = FakeTwitter.tweets_from(user).first
browser.should contain(tweet['text'], :wait => 10)
browser.should have_image(:src, tweet['profile_image_url'])
end
Timeout
227. Then %r{I should see @([']+)'s tweet along with
(?:his|her) avatar$} do |user|
tweet = FakeTwitter.tweets_from(user).first
browser.should contain(tweet['text'], :wait => 10)
browser.should have_image(:src, tweet['profile_image_url'])
end
Spec::Matchers.define :contain do |text, options|
match do |browser|
options[:wait] ||= 0
browser.wait_until(options[:wait]) do
browser.text.include?(text)
end
end
end
228. Then %r{I should see @([']+)'s tweet along with
(?:his|her) avatar$} do |user|
tweet = FakeTwitter.tweets_from(user).first
browser.should contain(tweet['text'], :wait => 10)
browser.should have_image(:src, tweet['profile_image_url'])
end
Spec::Matchers.define :contain do |text, options|
match do |browser|
options[:wait] ||= 0
browser.wait_until(options[:wait]) do
browser.text.include?(text)
end
end
end
Keep trying after sleeping
until it times out
230. Scenario: winner is displayed
When the following tweets are tweeted that match the '#free_stuff "aaron burr"' search
| From User | Text | Created At |
| @adams | Aaron Burr shot Alexander Hamilton #free_stuff | 1 minute ago |
| @jefferson | Aaron Burr shot Alexander Hamilton #free_stuff | 2 minutes ago |
And the presenter goes to the 3rd slide
And I go to the 3rd slide
Then I should see @jefferson's tweet along with his avatar
234. Scenario: view members list
Given the following wishes exist
| Wish | Family Member |
| Laptop | Thomas |
| Nintendo Wii | Candace |
| CHEEZBURGER | FuzzBuzz |
When I view the wish list for "Candace"
Then I should see the following wishes
| Wish |
| Nintendo Wii |
235. Given the following wishes exist
| Wish | Family Member |
| Laptop | Thomas |
| Nintendo Wii | Candace |
| CHEEZBURGER | FuzzBuzz |
features/step_definitions/wish_steps.rb
Given /^the following wishes exist$/ do |table|
end
end
236. Given the following wishes exist
| Wish | Family Member |
| Laptop | Thomas |
| Nintendo Wii | Candace |
| CHEEZBURGER | FuzzBuzz |
features/step_definitions/wish_steps.rb
Given /^the following wishes exist$/ do |table|
table.hashes.each do |row|
member = User.find_by_name(row["Family Member"]) ||
create_user(:name => row["Family Member"])
member.wishes.create!(:name => row["Wish"])
end
end
238. Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario Outline: Add two numbers
Given I have entered <input_1> into the calculator
And I have entered <input_2> into the calculator
When I press <button>
Then the result should be <output> on the screen
Scenarios:
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
| 0 | 40 | add | 40 |
239. Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario Outline: Add two numbers
Given I have entered <input_1> into the calculator
And I have entered <input_2> into the calculator
When I press <button>
Then the result should be <output> on the screen
Scenarios: addition
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
Scenarios: subtraction
| 0 | 40 | minus | -40 |
240. Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario Outline: Add two numbers
Given I have entered <input_1> into the calculator
And I have entered <input_2> into the calculator
When I press <button>
Then the result should be <output> on the screen
Scenarios:
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
| 0 | 40 | add | 40 |
241. Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario Outline: Add two numbers
Given I have entered <input_1> into the calculator
And I have entered <input_2> into the calculator
When I press <button>
Then the result should be <output> on the screen
Scenarios:
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
| 0 | 40 | add | 40 |
242. Feature: Addition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers
Scenario Outline: Add two numbers
Given I have entered <input_1> into the calculator
And I have entered <input_2> into the calculator
When I press <button>
Then the result should be <output> on the screen
Scenarios:
| input_1 | input_2 | button | output |
| 20 | 30 | add | 50 |
| 2 | 5 | add | 7 |
| 0 | 40 | add | 40 |
243. Steps Within Steps
When /^I view the wish list for "(.+)"$/ do |user_name|
Given "I am logged in"
visit "/wishes/#{user_name}"
end
244. Steps Within Steps
When /^I view the wish list for "(.+)"$/ do |user_name|
Given "I am logged in"
visit "/wishes/#{user_name}"
end
246. Tagged Hooks
Before('@im_special', '@me_too') do
@icecream = true
end
@me_too Feature: Sit
Feature: Lorem @im_special
Scenario: Ipsum Scenario: Amet
Scenario: Dolor Scenario: Consec
247. Spork
Sick of slow loading times? Spork
will load your main environment
once. It then runs a DRB server so
cucumber (or RSpec) can run
against it with the --drb flag. For
each test run Spork forks a child
process to run them in a clean
memory state. So.. it is a DRb
ser ver that forks.. hence Spork. :)
http://github.com/timcharper/spork
249. Integration tests
are a scam
J. B. Rainsberger
http://www.jbrains.ca/permalink/239
Obviously, I don’t agree with this
100%. But he has some valid points.
Integrations tests are not a
replacement for good unit tests. Use
cucumber for happy paths. Use lower
level tests for design and to isolate
object behavior.
253. Acceptance Tests Unit Tests
Application Level Object Level- Isolated!
For Customers For developers
Slow FAST! (should be at least)
Good confidence - Tighter Feedback Loop
Prevent against More about design!!!!!!!!!!!!
regression
Will need both gears!
Some things are easier to
test at the application
level and vice-versa.