Sunday, June 2, 2013

Faster Tests With factory_grabber

I use factories instead of fixtures when testing. If you haven’t already discovered factories, check out this Railscast: Factories not Fixtures.


Now that you’re up to speed: I often find when writing tests that I simply want any record. Here’s a quick example scenario from a controller spec:





describe CommentsController do

describe "POST /posts/1/comments" do

before do
@post = Factory :post
@comment_attributes = Factory.attributes_for(:comment, :post => @post)
end

def do_post
post :create, :comment => @comment_attributes, :post_id => @post.id
end

it "should create a new comment for @post " do
lambda { do_post }.should change {@post.comments.count}.by(1)
end

it "should redirect to @post" do
do_post
response.should redirect_to(post_path(@post))
end

end

end




In this example, we’re creating a new post record even although there may already be several posts in the database. Since this test is only asserting that a new comment has been created for @post it doesn’t really matter what the post’s specific attributes are. All we care about is that it’s a valid post we can create comments for.


Inserting new records to the databse is usually slower than simply retrieving an existing record. As a result, constantly creating factories when we already have appropriate records means our tests are slower and more inefficient than they should be.


Factory Grabber


I recently published a gem which addresses this issue. To check it out, simply run:





sudo gem install git://github.com/GavinM/factory_grabber.git




Factory Grabber is intended to be used with Factory Girl by Thoughtbot.


To use factory grabber in your tests/specs simply call the number of records and the model name as a method on Grab like so:





# return 47 individual comment records
@comments = Grab.forty_seven_comments
# will return 9 user records each with last_name "Smith"
@smiths = Grab.nine_users(:last_name => "Smith")
# return one record
@user = Grab.a_user
@article = Grab.an_article
@post = Grab.one_post




If there are appropriate records already in the database, Grab finds them. If there are not appropriate records, Grab will create them using the factories you’ve already defined.


For a practical example:


[ruby] describe “GET /posts/1″ do integrate_views before do @post = Grab.one_post :title => “This is the post title”, :body => “This is the post’s body” end def do_get get :show, :id => @post end it “should show the post title” do do_get response.should include_text(/This is the post title/) end it “should show the post body” do do_get response.should include_text(/This the post’s body/) end end # test pagination describe “GET /posts?page=1″ do integrate_views before do # ensures there are at least eleven Post records # if there are less than eleven, new posts are created # if there are eleven or more no posts are created Grab.eleven_posts end def do_get get :index, :page => 1 end it “should find the latest 10 posts” do do_get assigns[:posts].should == Post.find(:all, :o rder => “created_at DESC”, :limit => 10) end end [/ruby]


Here’s example of the performance boots you can achieve:





 user system total real(secs)
Create 50 new factories 0.100000 0.200000 0.300000 ( 6.354282)
Grab 50 separate factories 0.300000 0.000000 0.300000 ( 0.310373)
Grab 50 factories at once 0.020000 0.000000 0.020000 ( 0.011400)




In this case, grabbing 50 existing records is almost 20 times faster than creating 50 new factories!


Just make sure you turn off transactional fixtures in your test_helper.rb orspec_helper.rb files:





Spec::Runner.configure do |config|
config.use_transactional_fixtures = false
config.use_instantiated_fixtures = false
config.fixture_path = RAILS_ROOT + '/spec/fixtures/'
end




This gem is still in it’s infancy, I’d welcome any feedback/suggestions.



Faster Tests With factory_grabber

No comments:

Post a Comment