#285 Spork
- Download:
- source codeProject Files in Zip (94.6 KB)
- mp4Full Size H.264 Video (22.2 MB)
- m4vSmaller H.264 Video (11.5 MB)
- webmFull Size VP8 Video (13.7 MB)
- ogvFull Size Theora Video (27.8 MB)
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 as 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
.
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.
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.
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 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
.
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.
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.
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.
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_over_pass
. We set both of these to false
.
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).
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.
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.