Upload
cjwoodward
View
1.766
Download
0
Embed Size (px)
Citation preview
Roro 10 09 2008http://twitter.com/cjwoodwardhttp://delicious.com/cjwoodward
Rails TestingCARL WOODWARD
What are we debating about
The best way to test rails applicationsBe warned, I will ask dumb questions
Newb Corner
• Rspec is the de facto standard test suite for ruby/rails
• A stub is something that your test uses
• A mock is a something that you test against
Validationclass Company < ActiveRecord::Base validates_presense_of :nameend
describe Company do before(:each) do @company = Company.new end it “should be valid” do @company.attributes = {:name => ‘Test’} @company.should be_valid end it “should require name” do @company.should have(1).error_on(:name) end it “should have error on name (alternate)” do @company.attributes = {} @company.should have(1).error_on(:name) end it “should have error on name (alternate with nil)” do @company.attributes = {:name => nil} @company.should have(1).error_on(:name) endend
Associationsclass Pen < ActiveRecord::Base belongs_to :company def where_do_i_live company.address endend
describe Pen do before(:each) do @company = mock_model Company, :id => 1, :name => ‘Test’ @company.stub!(:address).and_return(‘Yellow brick road’) @pen = Pen.new :company => @company end it “should live somewhere” do @company.should_receive(:address).and_return(‘Yellow brick road’) @pen.where_do_i_live.should_equal(‘Yellow brick road’) endend
Associations
• 7 to 3 test code ratio (fudging this number)
• Repeated example data 3 times
• Does this prevent change on the model?
Newb Corner
• script/autospec - run your specs all the time
• Not this doesn’t run rake db:test:prepare, what are solutions to this?
Stories for models
• I love stories
• Great to go through with client
• Used with a model, found less duplication of code
Mocking
• Do this to keep controllers quick
• I struggle because I am working on an app with a massive object graph
Controllersclass PurchaseOrdersController < ApplicationController ... def attach_vendor_invoice @project = Project.find params[:project_id] @purchase_order = PurchaseOrder.find params[:purchase_order_id] @attachments = AttachmentSummary.for_project @project.id end ...end
describe PurchaseOrdersController, “ handling GET /projects/1/purchase_orders/1/attach_vendor_invoice” do before do @project = mock_model Project, :id => 1 @purchase_order = mock_model PurchaseOrder, :id => 1 @attachment = mock_model(Attachment, :id => 1, :file_name => ‘test.pdf’, :content_type => ‘application/pdf’, :include_in_file_list => true) Project.stub!(:find).and_return(@project) PurchaseOrder.stub!(:find).and_return(@purchase_order) AttachmentSummary.stub!(:for_project).and_return([@attachment]) end def do_get get :attach_vendor_invoice, {:project_id => @project.to_param, :id => @purchase_order.to_param}, user_session end it “should find project” do Project.should_receive(:find).and_return(@project) do_get end it “should find purchase order” do PurchaseOrder.should_receive(:find).with(@purchase_order.to_param).and_return(@purchase_order) do_get end it “should get attachments” do AttachmentSummary.should_receive(:for_project).with(@project.id).and_return(@attachments) do_get end it “should be successful” do do_get response.should be_success end it “should render attach_vendor_invoice template” do do_get response.should render_template(‘attach_vendor_invoice’) endend
Controllers close upclass PurchaseOrdersController < ApplicationController ... def attach_vendor_invoice @project = Project.find params[:project_id] @purchase_order = PurchaseOrder.find params[:purchase_order_id] @attachments = AttachmentSummary.for_project @project.id end ...end
• 6 lines of code
• Most of the code is in before filters
describe PurchaseOrdersController, “ handling GET /projects/1/purchase_orders/1/attach_vendor_invoice” do before do @project = mock_model Project, :id => 1 @purchase_order = mock_model PurchaseOrder, :id => 1 @attachment = mock_model(Attachment, :id => 1, :file_name => ‘test.pdf’, :content_type => ‘application/pdf’, :include_in_file_list => true) Project.stub!(:find).and_return(@project) PurchaseOrder.stub!(:find).and_return(@purchase_order) AttachmentSummary.stub!(:for_project).and_return([@attachment]) end def do_get get :attach_vendor_invoice, {:project_id => @project.to_param, :id => @purchase_order.to_param}, user_session end it “should find project” do Project.should_receive(:find).and_return(@project) do_get end it “should find purchase order” do PurchaseOrder.should_receive(:find).with(@purchase_order.to_param).and_return(@purchase_order) do_get end it “should get attachments” do AttachmentSummary.should_receive(:for_project).with(@project.id).and_return(@attachments) do_get end it “should be successful” do do_get response.should be_success end it “should render attach_vendor_invoice template” do do_get response.should render_template(‘attach_vendor_invoice’) endend
Controllers
• 20 to 3 code ratio
• I could take out 8 lines by removing the find for Project and Purchase Order
• Does that solve the issue?
Assigns and Flash, etc
• Do you test the assigns?
• Do you test the flash?
• Do you test for redirects and response code?
Views<p> <b>Zipcode:</b> <%=h @weather.zipcode %></p>
<p> <b>City:</b> <%=h @weather.city %></p>
<p> <b>Region:</b> <%=h @weather.region %></p>
...
before do @forecast = mock_model(Forecast, { :forecast_on => Date.today, :temperature_high => ‘33’, :temperature_low => ‘22’ }) @weather = mock_model(Weather, :forecasts => [@forecast]) @weather.stub!(:zipcode).and_return(“MyString”) @weather.stub!(:city).and_return(“MyString”)...
Newb Corner
• Stories are a recent addition to rspec
• They let you write plain text to describe a system
• And then use that documentation in your specs
• Don’t run under autospec, what are the ways around this?
Time
• Coders have spent a long time making their code concise
• Not as much time has been spent on making testing concise
• Because testing code is newer then doing code
• What would you like to see happen with testing?
FINALLY
• What percentage of effort do you think should go into the different layers of testing?
• MODEL?
• CONTROLLER?
• VIEW?
• INTEGRATION?
References
• peepcode.com - great rspec tutorials
• http://rubyhoedown2008.confreaks.com/02-joe-obrien-and-jim-weirich-mock-dialogue.html - great talk on testing and mocking
• http://blog.davidchelimsky.net/2008/7/1/new-controller-examples - great controller examples
• http://www.brynary.com/2007/12/8/webrat-0-1-0-released - good shit