February 01, 2011

Speed Testing with Spork

Test-Driven and Behaviour-Driven Development (TDD and BDD) have seen massive rates of adoption among the Ruby community, chiefly thanks to its close integration in the Rails framework and the fast pace of tool development. The benefits of these methodologies are plainly obvious to many experienced Software Developers and Project Managers.

One set of tools has established itself as the 'preferred' set among the Ruby community; Rspec, which tests object specifications, and Cucumber, which tests user interface behaviour. You may be using other tools in addition to these. Today I'm going to give you a tip to help you optimise your workflow by making testing faster, using another tool called Spork.

Spork is a small server application which essentially eliminates the long delay in starting up your test suite. Typically, running rake spec or rake features results in a delay of several seconds before testing actually begins. The reason for this is the application environment is being loaded from scratch each time. Spork eliminates this delay by loading it once and then 'forking' itself each time you run your tests. The result: tests begin immediately.

Installing Spork in your Application

This tutorial is based on a Rails 3 application. First, edit your Gemfile to include the Spork dependancy:

gem 'spork', '>= 0.9.0.rc2'

Note: we require at least version 0.9.0.rc2 because it addresses an issue when using Shoulda. If you're not using Shoulda, you can drop the version requirement.

Then update your bundle:

$ bundle install

The final thing we need to do is alter the test environment's behaviour. Unlike in development mode, the test mode will cache your classes, essentially meaning that any changes you make during development will not be reflected automatically when running tests with Spork. So, in the config/environments/test.rb file, change the following line as shown:

config.cache_classes = false

Using Spork with an RSpec Test Suite

We need to configure Spork to tell it what parts of the test suite initialisation it can handle. Spork provides a convenient command to make this easy:

$ bundle exec spork --bootstrap

This will add some extra lines to the spec/spec_helper.rb file. Basically, what you need to do now is move the existing code underneath into the relevant blocks; those which need to happen only once on initialisation, and those which need to happen each time the tests are run. In many simple cases you will be able to copy everything into the Spork.prefork block. But you must be careful here not the break anything.

Now you're ready to start the Spork server:

$ bundle exec spork

Open another shell session and rather than rake spec, use the following command:

$ bundle exec rspec --drb spec

Your tests should now run instantly without delay! Note we don't run rake because it will force a load of the Rails application anyway, rendering the savings we've made worthless.

You may also add the --drb switch to your .rspec file—in which case, you can drop this from your command.

Spork with Cucumber

Cucumber comes with support for Spork as well. You will need to do some editing of your features/support/env.rb file to add the two Spork blocks, as per the spec/spec_helper.rb file. The same rules apply.

Bear in mind that all hooks (e.g. Before, After) need to be in the each_run block. You can refer to this gist and my example below for further guidelines:

require 'spork'

Spork.prefork do
  ENV["RAILS_ENV"] ||= "test"
  require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
  
  require 'cucumber'
  require 'cucumber/formatter/unicode'
  require 'cucumber/rails/rspec'
  require 'capybara/rails'
  require 'capybara/cucumber'
  require 'capybara/session'
  require 'factory_girl/step_definitions'

  Capybara.default_selector = :css
end

Spork.each_run do
  require 'cucumber/rails/world'
  require 'cucumber/rails/active_record'
  require 'cucumber/web/tableish'
  require 'simplecov'
  require Rails.root.join('db/seeds')

  SimpleCov.start 'rails'
  
  Before do
    Seeds.load
  end

  ActionController::Base.allow_rescue = false

  Cucumber::Rails::World.use_transactional_fixtures = false

  if defined?(ActiveRecord::Base)
    begin
      require 'database_cleaner'
      DatabaseCleaner.strategy = :truncation
    rescue LoadError => ignore_if_database_cleaner_not_present
    end
  end
end

We can now run the Spork server. However, the command is different to the Rspec example:

$ bundle exec spork cuc

You must use the cuc argument to run Spork with Cucumber. Now, in a new shell session you can run Cucumber as below:

$ bundle exec cucumber --drb

Again, we don't run rake features because Rake would initialise our Rails application on its own anyway. Your features should now be tested instantly!