2011-02-03 la rubyconf rails3 tdd workshop
DESCRIPTION
Rails 3 with TDD workshop taught at LA RubyConf 2011TRANSCRIPT
![Page 1: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/1.jpg)
TDD with Rails 3TDD with Rails 3
Wolfram ArnoldWolfram Arnold@wolframarnold@wolframarnold
www.rubyfocus.bizwww.rubyfocus.biz
In collaboration with:In collaboration with:LA Ruby ConferenceLA Ruby Conference
![Page 2: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/2.jpg)
IntroductionIntroduction
![Page 3: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/3.jpg)
What?
![Page 4: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/4.jpg)
What?What?
● Why TDD?● Rails 3 & TDD
– what's changed?
– RSpec 2
● Testing in Layers● TDD'ing model development● Factories, mocks, stubs...● Controllers & Views
![Page 5: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/5.jpg)
How?
![Page 6: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/6.jpg)
How?How?
● Presentation● Live coding demos● In-class exercises
– Pair programming
● Material from current development practice● Fun
![Page 7: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/7.jpg)
It works best, when...It works best, when...
Active participation
Try something new
Team Effort
Pairing
![Page 8: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/8.jpg)
Efficient RailsTest-Driven
Development
![Page 9: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/9.jpg)
Why “efficient” and “testing”?Why “efficient” and “testing”?
“Testing takes too much time.”
“It's more efficient to test later.”
“Testing is the responsibility of QA, not developers.”
“It's not practical to test X.”
“Tests keep breaking too often.”
When data changes.
When UI design changes.
![Page 10: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/10.jpg)
The Role of TestingThe Role of Testing
Development without tests...
fails to empower developers to efficiently take responsibility for quality of the code delivered
makes collaboration harder
build narrow silos of expertise
instills fear & resistance to change
makes documentation a chore
stops being efficient very soon
![Page 11: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/11.jpg)
TDD: Keeping cost of change lowTDD: Keeping cost of change low
Cost per change
Time
withTDD
withoutTDD
![Page 12: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/12.jpg)
Why?Why?
Non-TDD
Accumulates “technical debt” unchecked
Removal of technical debt carries riskThe more technical debt, the higher the risk
Existing technical debt attracts more technical debtLike compound interest
People are most likely to do what others did before them
To break the pattern heroic discipline & coordination required
![Page 13: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/13.jpg)
Testing in LayersTesting in Layers
Model RSpecModel
Controller RSpecController
Views RSpecHelpers
Helpers RSpecViews
Routes
Test::Unit
Test::Unit FunctionalRSpecRoutes
Application, Server
Application, Browser UI
RSpec Request, CapybaraCucumber, Webrat
Selenium 1, 2
Test::Unit Integration
![Page 14: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/14.jpg)
Cost of TestingCost of Testing
Model
Controller
Views Helpers
Routes
Application, Server
Application, Browser UI
Relationship to data
Cost
mostremoved
closest
![Page 15: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/15.jpg)
Best ROI for TestingBest ROI for Testing
Model
Controller
Views Helpers
Routes
Application, Server
Application, Browser UI
Layers
Impact/Line of Test Code
![Page 16: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/16.jpg)
TDD & Design PatternsTDD & Design Patterns
Skinny Controller—Fat Model
DRY
Scopes
Proxy Associations
Validations
...
➢ Designed to move logic from higher to lower application layers
➢ Following design patterns makes testing easier
➢ Code written following TDD economics will naturally converge on these design patterns!
![Page 17: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/17.jpg)
Rails 3 – what's new?Rails 3 – what's new?
● gem management with bundler
● scripts: rails g, s, ...
● constants: RAILS_ENV → Rails.env...
● errors.on(:key) → errors[:key], always Array now
● routes: match '/' => 'welcome#index'
● configuration in application.rb
● ActiveRecord: Scopes, Relations, Validations
● Controllers: no more verify
● ActionMailer: API overhaul
● Views: auto-escaped, unobtrusive JS
![Page 18: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/18.jpg)
RSpec 2RSpec 2
● Filters to run select tests– RSpec.configure do |c|
c.filter_run :focus => trueend
● Model specs:– be_a_new(Array)
● Controller specs:– integrate_views → render_views
– assigns[:key]=val → assigns(:key,val)(deprecated)
![Page 19: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/19.jpg)
RSpec 2 cont'dRSpec 2 cont'd
● View specs:– response → rendered
– assigns[:key]=val → assign(:key, val) (Req)
● Routing specs:– route_for is gone
– route_to, be_routable (also in Rspec 1.3)
![Page 20: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/20.jpg)
Know YourKnow Your
ToolsTools
![Page 21: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/21.jpg)
RVMRVM
● multiple, isolated Rubies● can have different gemsets each
Install: http://rvm.beginrescueend.com/rvm/install/
As User or System-Wide
> rvm install ruby-1.8.7
> rvm gemset create rails3
> rvm ruby-1.8.7@rails3
> rvm info
![Page 22: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/22.jpg)
RVM SettingsRVM Settings
● System: /etc/rvmrc● User: ~/.rvmrc● Project: .rvmrc in project(s) root
> mkdir workspace
> cd workspace
> echo “ruby-1.8.7@rails3” > .rvmrc
> cd ../workspace
> rvm info
> gem list
![Page 23: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/23.jpg)
Installing gemsInstalling gems
● Do NOT use sudo with RVM!!!● gems are specific to the Ruby and the gemset
> rvm info → make sure we're on gemset “rails3”
> gem install rails
> gem install rspec-rails
> gem list
![Page 24: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/24.jpg)
Rails 3: rails commandRails 3: rails command
● Replaces script/*– new
– console
– dbconsole
– generate
– server
![Page 25: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/25.jpg)
Let's do some codingLet's do some coding
Demo
![Page 26: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/26.jpg)
> rails generate rspec:install
> rails generate model User first_name:string last_name:string email:string
![Page 27: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/27.jpg)
TDD CycleTDD Cycle
● Start user story● Experiment● Write test● Write code● Refactor● Finish user story
![Page 28: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/28.jpg)
Structure of TestsStructure of Tests
Setup
Expected value
Actual value
Verification: actual == expected?
Teardown
![Page 29: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/29.jpg)
Good Tests are...Good Tests are...
Compact
Responsible for testing one concern only
Fast
DRY
![Page 30: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/30.jpg)
RSpec VerificationsRSpec Verifications
should respond_to
should be_nil
→ works with any ? method (so-called “predicates”)
should be_valid
should_not be_nil; should_not be_valid
lambda {...}.should change(), {}, .from().to(), .by()
should ==, equal, eq, be
![Page 31: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/31.jpg)
RSpec StructureRSpec Structure
before, before(:each), before(:all)
after, after(:each), after(:all)
describe do...end, nested
it do... end
![Page 32: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/32.jpg)
RSpec SubjectRSpec Subject
describe Address do
it “must have a street” doa = Address.newa.should_not be_valida.errors.on(:street).should_not be_nil
end
#subject { Address.new } # Can be omitted if .new # on same class as in describe
it “must have a street” doshould_not be_valid # should is called on
# subject by defaultsubject.errors.on(:street).should_not be_nil
end
end
![Page 33: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/33.jpg)
RSpec2RSpec2
● https://github.com/rspec/rspec-rails● http://blog.davidchelimsky.net/● http://relishapp.com/rspec● More modular, some API changesGemspec file, for Rails 3:
group :development, :test do
gem 'rspec-rails', "~> 2.0.1"
end
![Page 34: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/34.jpg)
Models: What to test?Models: What to test?
Validation Rules
Associations
Any custom method
Association Proxy Methods
![Page 35: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/35.jpg)
Let's do some codingLet's do some coding
Exercise...
![Page 36: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/36.jpg)
Story Exercise #1Story Exercise #1
A User object must have a first and last name.
A User object can construct a full name from the first and last name.
A User object has an optional middle name.
A User object returns a full name including, if present, the middle name.
![Page 37: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/37.jpg)
RSpec ==, eql, equalRSpec ==, eql, equal
obj.should == 5
obj.should eq(5)
obj.should equal(5)
obj.should be(5)
5 == 5
5.equal 5
Object Equality vs. Identity
eql, == compare values
equal, === compare objects,classes
Warning! Do not use != with RSpec.Use should_not instead.
Use == or eqUnless you know you need something else
![Page 38: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/38.jpg)
RSpec should changeRSpec should change
lambda {…}.should change...expect {…}.to change...
expect {Person.create
}.to change(Person, :count).from(0).to(1)
lambda {@bob.addresses.create(:street => “...”)
}.should change{@bob.addresses.count}.by(1)
![Page 39: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/39.jpg)
ModelsModelsWhat to test?What to test?
![Page 40: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/40.jpg)
Test Models for...Test Models for...
● validation● side-effects before/after saving● associations● association proxy methods● scopes, custom finders● nested attributes● observers● custom methods
![Page 41: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/41.jpg)
valid?
![Page 42: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/42.jpg)
How to Test for Validations?How to Test for Validations?
it 'requires X' do
n = Model.new
n.should_not be_valid
n.errors[:x].should_not be_empty
end
● Instantiate object with invalid property● Check for not valid?● Check for error on right attribute
![Page 43: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/43.jpg)
Check for Side EffectsCheck for Side Effects
![Page 44: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/44.jpg)
Model CallbacksModel Callbacks
Requirement:
Default a value before saving
Send an email after saving
Post to a URL on delete
...
Callbacks:
before_save
after_save
after_destroy
...
![Page 45: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/45.jpg)
How to test Callbacks?How to test Callbacks?
Through their Side Effects:● Set up object in state before callback● Trigger callback● Check for side effect
it 'encrypts password on save' do
n = User.new
n.should_not be_valid
n.errors.on(:x).should_not be_nil
end
![Page 46: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/46.jpg)
How are Callbacks triggered?How are Callbacks triggered?
Callbackbefore_validation
after_validation
before_save
after_save
before_create
after_create
before_destroy
after_destroy
after_find (see docs)
after_initialize (see docs)
Trigger eventvalid?
valid?
save, create
save, create
create
create
destroy
destroy
find
new
![Page 47: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/47.jpg)
AssociationsAssociations
![Page 48: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/48.jpg)
Model AssociationsModel Associations
Requirement:
Entities have relationships
Given an object, I want to find all related objects
has_many
has_one
belongs_to
has_many :through
![Page 49: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/49.jpg)
Tables and AssociationsTables and Associations
Source: Rails Guides, http://guides.rubyonrails.org/association_basics.html
class Customer < AR::Base
has_many :orders
...
end
class Order < AR::Base
belongs_to :customer
...
end
![Page 50: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/50.jpg)
Migrations and AssociationsMigrations and Associations
class Address < AR::Base
belongs_to :person
...
end
class Person < AR::Base
has_many :addresses
...
end
create_table :addresses do |t|
t.belongs_to :person
# same as:# t.integer :person_id...
end
create_table :people do |t|...
end
![Page 51: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/51.jpg)
Association MethodsAssociation Methods
has_many :assets
.assets
.assets <<
.assets = [...]
.assets.delete(obj,..)
.assets.clear
.assets.empty?
.assets.create(...)
.assets.build(...)
.assets.find(...)
belongs_to :person
.person
.person =
.build_person()
.create_person()
Source: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
![Page 52: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/52.jpg)
has_manyhas_many:through:through
many-to-many
relationships
![Page 53: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/53.jpg)
Indices for AssociationsIndices for Associations
Rule: Any database column that can occur in a WHERE clause should have an index
create_table :addresses do |t|
t.belongs_to :person
# same as:# t.integer :person_id...
end
add_index :addresses, :person_id
![Page 54: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/54.jpg)
How to test for Associations?How to test for Associations?
● Are the association methods present?● Checking for one is enough.● No need to “test Rails” unless using
associations with options● Check that method runs, if options used
it “has many addresses” do
p = Person.new
p.should respond_to(:addresses)
end
![Page 55: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/55.jpg)
Association OptionsAssociation Options
Ordering
has_many :people, :order => “last_name ASC”
Class Name
belongs_to :customer, :class_name => “Person”
Foreign Key
has_many :messages, :foreign_key => “recipient_id”
Conditions
has_many :unread_messages,:class_name => “Message”,:conditions => {:read_at => nil}
![Page 56: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/56.jpg)
How to test Assn's with Options?How to test Assn's with Options?
● Set up a non-trivial data set.● Verify that it's non-trival.● Run association method having options● Verify resultit “sorts addresses by zip” do
p = Factory(:person)# Factory for addrs with zip 23456, 12345Address.all.should == [addr1, addr2]p.addresses.should == [addr2, addr1]p.should respond_to(:addresses)
end
![Page 57: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/57.jpg)
More Association OptionsMore Association Options
Joins
has_many :popular_items,:class_name => “Item”,:include => :orders,:group => “orders.customer_id”,:order => “count(orders.customer_id) DESC”
![Page 58: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/58.jpg)
ExerciseExercise
A User can have 0 or more Addresses.
A User's Address must have a street, city, state and zip.
A User's Address can have an optional 2-letter country code.
If the country is left blank, it should default to “US” prior to saving.
Extra Credit:
State is required only if country is “US” or “CA”
Zip must be numerical if country is “US”
![Page 59: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/59.jpg)
ControllersControllers
![Page 60: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/60.jpg)
ControllersControllers
Controllers are pass-through entities
Mostly boilerplate—biz logic belongs in the model
Controllers are “dumb” or “skinny”
They follow a run-of-the mill pattern:
the Controller Formula
![Page 61: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/61.jpg)
Controller RESTful ActionsController RESTful Actions
Display methods (“Read”)
GET: index, show, new, edit
Update method
PUT
Create method
POST
Delete method
DELETE
![Page 62: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/62.jpg)
REST?REST?
Representational State Transfer
All resource-based applications & API's need to do similar things, namely:
create, read, update, delete
It's a convention:
no configuration, no ceremony
superior to CORBA, SOAP, etc.
![Page 63: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/63.jpg)
RESTful rsources in RailsRESTful rsources in Rails
map.resources :people (in config/routes.rb)
people_path, people_url “named route methods”
GET /people → “index” action
POST /people → “create” action
new_person_path, new_person_url
GET /people/new → “new” action
edit_person_path, edit_person_url
GET /people/:id/edit → “edit” action with ID
person_path, person_url
GET /people/:id → “show” action with ID
PUT /people/:id → “update” action with ID
DELETE /people/:id → “destroy” action with ID
![Page 64: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/64.jpg)
Read FormulaRead Formula
Find data, based on parameters
Assign variables
Render
![Page 65: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/65.jpg)
Reads Test PatternReads Test Pattern
Make request (with id of record if a single record)
Check Rendering
correct template
redirect
status code
content type (HTML, JSON, XML,...)
Verify Variable Assignments
required by view
![Page 66: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/66.jpg)
Create/Update FormulaCreate/Update Formula
Update: Find record from parameters
Create: Instantiate new model object
Assign form fields parameters to model object
This should be a single line
It is a pattern, the “Controller Formula”
Save
Handle success—typically a redirect
Handle failure—typically a render
![Page 67: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/67.jpg)
Create/Update Test PatternCreate/Update Test Pattern
Make request with form fields to be created/upd'd
Verify Variable Assignments
Verify Check Success
Rendering
Verify Failure/Error Case
Rendering
Variables
Verify HTTP Verb protection
![Page 68: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/68.jpg)
How much test is too much?How much test is too much?
Test anything where the code deviates from defaults, e.g. redirect vs. straight up render
These tests are not strictly necessary:
response.should be_success
response.should render_template('new')
Test anything required for the application to proceed without error
Speficially variable assignments
Do test error handling code!
![Page 69: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/69.jpg)
How much is enough?How much is enough?
Notice: No view testing so far.
Emphasize behavior over display.
Check that the application handles errors correctly
Test views only for things that could go wrong badly
incorrect form URL
incorrect names on complicated forms, because they impact parameter representation
![Page 70: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/70.jpg)
View TestingView Testing
RSpec controllers do not render views (by default)
Test form urls, any logic and input names
Understand CSS selector syntax
View test requires set up of variables
another reason why there should only be very few variables between controller and view
some mocks here are OK
![Page 71: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/71.jpg)
RSpec 2 View UpdateRSpec 2 View Update
● should have_tag is gone● Use webrat matchers:
– Add “webrat” to Gemfile
– Add require 'webrat/core/matchers' to spec_helper.rb
– matcher is should have_selector(“css3”)
● response is now rendered● rendered.should have_selector(“css3”)
![Page 72: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/72.jpg)
Mocks,Mocks,Doubles,Doubles,Stubs, ...Stubs, ...
![Page 73: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/73.jpg)
Object levelObject level
All three create a “mock” object.
mock(), stub(), double() at the Object level are synonymous
Name for error reporting
m = mock(“A Mock”)
m = stub(“A Mock”)
m = double(“A Mock”)
![Page 74: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/74.jpg)
Using MocksUsing Mocks
Mocks can have method stubs.
They can be called like methods.
Method stubs can return values.
Mocks can be set up with built-in method stubs.
m = mock(“A Mock”)
m.stub(:foo)
m.foo => nil
m.stub(:foo).and_return(“hello”)
m.foo => “hello”
m = mock(“A Mock”, :foo => “hello”)
![Page 75: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/75.jpg)
Message ExpectationsMessage Expectations
Mocks can carry message expectations.
should_receive expects a single call by default
Message expectations can return values.
Can expect multiple calls.
m = mock(“A Mock”)
m.should_receive(:foo)
m.should_receive(:foo).and_return(“hello”)
m.should_receive(:foo).twice
m.should_receive(:foo).exactly(5).times
![Page 76: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/76.jpg)
Argument ExpectationsArgument Expectations
Regular expressions
Hash keys
Block
m = mock(“A Mock”)
m.should_receive(:foo).with(/ello/)
with(hash_including(:name => 'joe'))
with { |arg1, arg2|arg1.should == 'abc'arg2.should == 2
}
![Page 77: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/77.jpg)
Partial MocksPartial Mocks
Replace a method on an existing class.
Add a method to an existing class.
jan1 = Time.civil(2010)
Time.stub!(:now).and_return(jan1)
Time.stub!(:jan1).and_return(jan1)
![Page 78: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/78.jpg)
Dangersof
Mocks
![Page 79: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/79.jpg)
ProblemsProblems
Non-DRY
Simulated API vs. actual API
Maintenance
Simulated API gets out of sync with actual API
Tedious to remove after “outside-in” phase
Leads to testing implementation, not effect
Demands on integration and exploratory testing higher with mocks.
Less value per line of test code!
![Page 80: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/80.jpg)
So what are they good for?So what are they good for?
External services
API's
System services
Time
I/O, Files, ...
Sufficiently mature (!) internal API's
Slow queries
Queries with complicated data setup
![Page 81: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/81.jpg)
TDD withTDD with
WebservicesWebservicesAmazon RSS FeedAmazon RSS Feed
SimpleRSS gemSimpleRSS gemNokogiri XML parser gemNokogiri XML parser gem
FakeWeb mocksFakeWeb mocks
![Page 82: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/82.jpg)
Step 1: Experiment
![Page 83: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/83.jpg)
Step 2:Proof of Concept
![Page 84: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/84.jpg)
Step 3:Specs & Refactor
![Page 85: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/85.jpg)
Exercise: Step 3Exercise: Step 3
● Using TDD techniques with– FakeWeb
– mocks
● Build up a Product model with:– a fetch class method returning an array of
Product instances
– instance methods for:● title, description, link● image_url (extracted from description)
● Refactor controller & view to use Product model
![Page 86: 2011-02-03 LA RubyConf Rails3 TDD Workshop](https://reader033.vdocuments.net/reader033/viewer/2022052321/554a03aab4c905e56c8b5388/html5/thumbnails/86.jpg)
ReferenceReference
● https://github.com/wolframarnold/Efficient-TDD-Rails3
● Class Videos: http://goo.gl/Pe6jE● Rspec Book● https://github.com/rspec/rspec-rails● http://blog.davidchelimsky.net/● http://relishapp.com/rspec