homeASCIIcasts

285: Spork 

(view original Railscast)

Other translations: Es Ja

As well as being an eating utensil, Spork is a Ruby gem that will help your applications’ test suites to load up more quickly. In this episode we’ll show you how you can use it to speed up the running of an application’s tests.

Usually when we run the test suite for a Rails application there’s a few seconds’ pause before the tests begin to run. We can see how long this pause is for our app’s tests with the time command.

$ time rspec .
.........
Finished in 1.98 seconds
9 examples, 0 failures
real	0m11.090s
user	0m9.735s
sys	0m1.306s

Even though RSpec says that the tests only took a couple of seconds to run the time between us running rspec and being returned to the command prompt is much longer, just over 11 seconds. The extra time is the time it takes to load up the Rails application and, as we’ve seen here, this can take longer than running the tests. Spork helps solve this problem.

Installing Spork

Spork is installed in the same way any other gem, although as it’s only used for testing we add it to the test group in the Gemfile. As we’re installing it for a Rails 3 application we’ll need to install the prerelease version of the gem, which is currently version 0.9.0.rc.

/Gemfile

gem "rspec-rails", :group => [:test, :development]
group :test do
  gem "factory_girl_rails"
  gem "capybara"
  gem "guard-rspec"
  gem "spork", "> 0.9.0.rc"
end

As always, we need to run bundle once we’ve changed the Gemfile so that all of the gems in it are installed.

Next we’ll need to run Spork’s bootstrap command to prepare the helper file.

$ spork --bootstrap
Using RSpec
Bootstrapping /Users/eifion/auth/spec/spec_helper.rb.
Done. Edit /Users/eifion/auth/spec/spec_helper.rb now with your favorite text editor and follow the instructions.

We can see from the output above that Spork has detected that we’re using RSpec and modified the spec_helper file. Let’s take a look at what’s changed.

/spec/spec_helper.rb

require 'spork'
Spork.prefork do
  # Loading more in this block will cause your tests to run faster. However,
  # if you change any configuration or code from libraries loaded here, you'll
  # need to restart spork for it take effect.
end
Spork.each_run do
  # This code will be run each time you run your specs.
end

Spork has added two methods to the spec_helper, each of which takes a block. The first is prefork and runs when the Spork server starts up. The other, each_run, is executed each time we run our test suite. It’s a good idea to move as much as possible of the spec_helper’s code into prefork so that it’s only run once. We’ll move all of the file’s code into it and see if the tests still work.

/spec/spec_helper.rb

require 'rubygems'
require 'spork'
Spork.prefork do
  # This file is copied to spec/ when you run 'rails generate ↵
  rspec:install'
  ENV["RAILS_ENV"] ||= 'test'
  require File.expand_path("../../config/environment", __FILE__)
  require 'rspec/rails'
  require 'capybara/rspec'
  # Requires supporting ruby files with custom matchers and ↵
    macros, etc,
  # in spec/support/ and its subdirectories.
  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| ↵
    require f}
  RSpec.configure do |config|
    config.mock_with :rspec
    config.use_transactional_fixtures = true
    config.include(MailerMacros)
    config.before(:each) { reset_email }
  end
end
Spork.each_run do
  # This code will be run each time you run your specs.
end

We use Spork by running the spork command to start up its server.

$ spork
Using RSpec
Preloading Rails environment
Loading Spork.prefork block...
Spork is ready and listening on 8989!

As the server starts up it runs the code in the prefork method so that this code only needs to be run once. We can now open up another terminal window and run our test suite. We’ll need to run the tests through the Spork server, which we do by passing in the --drb option.

When we run the tests through Spork the spec suite runs more quickly and if we run the them under the time command again we’ll see that this now takes much less time to complete. This time gain will increase the bigger our Rails application becomes.

$ time rspec . --drb
.........
Finished in 2.21 seconds
9 examples, 0 failures
real	0m4.125s
user	0m0.342s
sys	0m0.097s

Spork is really useful here as it starts up the application under test once and lets us run its tests as may times as we like without having to reload the whole application each time.

Using Guard With Spork

Guard is a gem that reruns an application’s test suite each time a change is made to its code. We covered it in detail back in episode 264 [watch, read]. Guard can be used with Spork with a gem called guard-spork. This is installed the same way we installed Spork, by adding the gem to the test group in the Gemfile and running bundle.

/Gemfile

gem "rspec-rails", :group => [:test, :development]
group :test do
  gem "factory_girl_rails"
  gem "capybara"
  gem "guard-rspec"
  gem "spork", "> 0.9.0.rc"
  gem "guard-spork"
end

Once the gem has installed we need to run guard init spork to add Spork to our Guardfile. If we then look at the Guardfile we’ll see the Spork section of the file below the RSpec section.

/Guardfile

guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do
  watch('config/application.rb')
  watch('config/environment.rb')
  watch(%r{^config/environments/.+\.rb$})
  watch(%r{^config/initializers/.+\.rb$})
  watch('spec/spec_helper.rb')
end

We’ll need to move this section up above the RSpec section as it needs to run before any other test-related guards. We’ll also need to modify the RSpec guard and add a :cli option with a value of --drb. This ensures that the specs are run through Spork and it applies to any other guards we might have,for example for Cucumber.

guard 'rspec', :version => 2, :cli => '--drb' do
  # Content omitted.
end

Now we no longer need to start the Spork server separately. When we start up Guard it will automatically launch Spork in the background and run the test suite when we change any of our application’s Ruby files.

$ guard
Please install rb-fsevent gem for Mac OSX FSEvents support
Using polling (Please help us to support your system better than that.)
Please install growl_notify or growl gem for Mac OS X notification support and add it to your Gemfile
Guard is now watching at '/Users/eifion/auth'
Starting Spork for RSpec 
Using RSpec
Preloading Rails environment
Loading Spork.prefork block...
Spork is ready and listening on 8989!
Spork server for RSpec successfully started
Guard::RSpec is running, with RSpec 2!
Running all specs
Running tests with args ["--color", "--format", "progress", "--format", "Guard::RSpec::Formatter::NotificationRSpec", "--out", "/dev/null", "--require", "/Users/eifion/.rvm/gems/ruby-1.9.2-p180@rails31/gems/guard-rspec-0.4.5/lib/guard/rspec/formatters/notification_rspec.rb", "spec"]...
.........
Finished in 4.29 seconds
9 examples, 0 failures
Done.

This is much easier than having to manage Spork separately. When we change one of our application’s files or specs and save it the relevant specs will be run again automatically a second or so later.

Configuring Spork

We’ll look now at configuring Spork. Sometimes when we change a file in our application Spork doesn’t pick up the change which means that we need to restart it. This happens because the file is loaded in the prefork block but not reloaded in each_run. This can often happen if we use Factory Girl. Its factories are loaded in prefork so any changes we make to them aren’t picked up when the test suite is rerun. We want any changes we make to be picked up automatically and so we’ll need to reload the factories in each_run. Recent versions of Factory Girl have a reload method that will do this so it’s simple to do this.

/spec/spec_helper.rb

Spork.each_run do
  FactoryGirl.reload
end

Any changes we make to the factories will now be picked up on the next test run.

A similar problem can happen with the files inside the /spec/support directory. Again these are loaded inside the prefork block, so any changes we make won’t be picked up when the tests are rerun. We could move the file-loading code into each_run, but the more code we put there the longer the delay will be before the tests begin to run. It would be better if we could keep the code inside the preload block and reload Spork automatically whenever one of the files inside /spec/support changes.

This is something that guard-spork can help us with. The Guardfile contains a list of file name patterns in a spork block which defines the files that are watched. If any matching file changes Spork will be reloaded. We can easily add the spec/support directory here.

/Guardfile

guard 'spork', :cucumber_env => { 'RAILS_ENV' => ↵
  'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do
  watch('config/application.rb')
  watch('config/environment.rb')
  watch(%r{^config/environments/.+\.rb$})
  watch(%r{^config/initializers/.+\.rb$})
  watch('spec/spec_helper.rb')
  watch(%r{^spec/support/.+\.rb$})
end

Any changes to the files in the spec/support directory will now be picked up by Guard and Spork will be reloaded.

While we’re looking at the Guardfile here’s a quick tip to deal with a slow-running test suite. If the tests take more than a minute to run we don’t want to run them quite as frequently and there are two options we can pass to the rspec guard that will help: all_on_start and all_after_pass. We set both of these to false.

/Guardfile

guard 'rspec', :version => 2, :cli => '--drb', ↵
  :all_on_start => false, :all_after_pass => false do
# watch commands omitted.
end

With these options set Guard won’t rerun all of the specs when a previously failing spec passes. This gives us more control over when all of the specs are run. We can hit the return key in the Guard terminal (if we’re using the latest version of Guard) to run all of the specs.

Another tip that’s unrelated to Spork but which is useful when dealing with large test suites is adding three lines to the RSpec configuration block (the bottom three lines in the block below).

/spec/spec_helper.rb

RSpec.configure do |config|
  config.mock_with :rspec
  config.use_transactional_fixtures = true
  config.include(MailerMacros)
  config.before(:each) { reset_email }
  config.treat_symbols_as_metadata_keys_with_true_values = true
  config.filter_run :focus => true
  config.run_all_when_everything_filtered = true
end

Now, whenever we add a :focus tag to a given spec only that spec will be run instead of all of them. For example we’ll add :focus to a spec that tests the User model.

/spec/models/user_spec.rb

require 'spec_helper'
describe User do
  describe "#send_password_reset" do
    let(:user) { Factory(:user) }
    it "generates a unique password_reset_token each time", ↵
      :focus do
      user.send_password_reset
      last_token = user.password_reset_token
      user.send_password_reset
      user.password_reset_token.should_not eq(last_token)
    end
	# Other specs omitted.
  end
end

When we save the file above, Guard will pick up the changes but because of the :focus tag only the one spec is run.

Running: spec/models/user_spec.rb
Running tests with args ...
Run filtered including {:focus=>true}
.
Finished in 1.93 seconds
1 example, 0 failures
Done.

There are some cases when a piece of code gets called in a Spork prefork block when we don’t want this to happen and there’s no easy way to move it to each_run. Spork provides a trap_method method to handle these scenarios. This method stops the trapped method from being run immediately and instead runs it after the process is forked. This is useful in certain cases such as when we use Mongoid or Devise. Both of these load things in the prefork block that we don’t really want to be loaded then. This is explained in more detail on Spork’s wiki pages.

That’s it for this episode on Spork. It’s a great tool for speeding up the TDD process and, when its combined with the tips we showed above, scales well even with large Rails projects.