32
Roro 10 09 2008 http://twitter.com/cjwoodward http://delicious.com/cjwoodward Rails Testing CARL WOODWARD

Rails Testing Debate

Embed Size (px)

Citation preview

Roro 10 09 2008http://twitter.com/cjwoodwardhttp://delicious.com/cjwoodward

Rails TestingCARL WOODWARD

DEBATE!

Why a Debate?

• I am lazy

• RORO has a wealth of knowledge

• Good fun

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

MODELS

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

Validations

• 20 to 1 test code ratio

• Testing code that you didn’t write

• A lot to maintain

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?

Fixtures

• Plain old fixtures

• Helper modules and Factories

• Factory girl

Newb Corner

• script/autospec - run your specs all the time

• Not this doesn’t run rake db:test:prepare, what are solutions to this?

Your own methods

• Do you write to the database

• Mock/stub?

Stories for models

• I love stories

• Great to go through with client

• Used with a model, found less duplication of code

CONTROLLERS

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?

Newb Corner

• rake spec::rcov - know your test coverage, cmon I’m almost at 7000:1

VIEWS

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”)...

Views

• 57 to 70 test ratio

• First piece of code where the test was smaller then the code

STORIES

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?

Stories

• Would you use stories for all of your specs

• Do you use stories for integration testing

WHAT DOES IT MEAN

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?