TDD is fun, right? Rails enthusiasts and agile evangelists alike agree. Waiting for your tests to run, however, makes for a frustrating experience. When the time between test iterations is magnified by bloated tests it can be hard to maintain a purist’s test-driven approach.
After looking into autotest, parallel testing, and in-memory databases, I have found the simplest solution to this problem in Spork and Guard, two great gems that help speed up your tests.
With proper setup and understanding of how these two tools work, you can start down the path to a more responsive test environment and reignite your love for TDD.
This article assumes you are on a Mac Platform, running Rails 3 on Ruby 1.9.2. The instructions should be the same or similar for other enviroments, but I have not tested that assumption. Please, leave a comment if you have trouble with your environment and I will be glad to update the article to reflect the changes.
At the time of writing, the gems referenced are versioned as follows:
Full instructions for installing Spork can be found on RailsTutorial.org
Guard is a modular filesystem event monitor utility written in Ruby. We will utilize a plugin for Guard that allows us to monitor changes to Rails files and restart Spork when necessary:
group :test do gem 'spork' gem 'rb-fsevent' gem 'guard-spork' # ... end
$ bundle install
In order to get rspec to utilize the prefork functionality of spork, we need to tell it to use a drb server. In the rails root directory, create or edit the .rspec file and add the following line:
--drb
You can start spork for rspec manually by running the following command in your rails root path:
$ bundle exec spork
You should see the following in your terminal if it starts successfully:
Using RSpec Loading Spork.prefork block... Spork is ready and listening on 8989!
The preferred way to start spork, however, is via Guard. This will help make sure that the spork server is restarted when we change files that it relies on.
The first thing we need to do is to tell Guard about spork:
$ guard init spork
This will create a file, named ‘Guardfile’ with the following contents:
guard 'spork' do watch('^config/application.rb$') watch('^config/environment.rb$') watch('^config/environments/.*.rb$') watch('^config/initializers/.*.rb$') watch('^spec/spec_helper.rb') end
Basically, the ‘watch’ directives specify paths to observe for changes. If any changes occur on those files, Spork will be restarted.
Now let’s go ahead and start spork:
$ guard start
You should see the following in your terminal if it starts successfully:
Guard is now watching at '/dev/rails_app' Starting Spork for RSpec Spork for RSpec successfully started
Spork speeds up TDD significantly, but I have seen an occasional false positive due to human error. I have found the following approach to be a surefire way to balance the test speed with ensuring test stability:
$ rspec /path/to/my/file:22
$ rspec /path/to/file
If you find that you are working on a feature with Spork running and Rspec does not pick up your changes on each run, stop spork and try running your spec without it. If that fixes it, take a look at the spec/spec_helper.rb file. There are two blocks here, one labeled Spork.prefork do and one labeled Spork.each_run do. You can use the each_run block to load requirements for each test pass instead of prefork, which loads once before the entire suite is run.
To get Spork to work with rails, there are a couple workarounds implemented in the installation step. Our CI environment does not run Spork, so these workarounds are unnecessary. Let’s tell Rails to use these workarounds only if Spork is Running:
if Spork.using_spork? ActiveSupport::Dependencies.clear ActiveRecord::Base.instantiate_observers end
### Part of a Spork hack. See http://bit.ly/arY19y if Rails.env.test? && defined?(Spork) && Spork.using_spork? initializer :after => :initialize_dependency_mechanism do # Work around initializer in railties/lib/rails/application/bootstrap.rb ActiveSupport::Dependencies.mechanism = :load end end
The Spork “hacks” (really just configurations for the prefork server) clear all dependencies during the Spork.prefork block. This causes an issue with ActiveRecord Observers. Add this line to your prefork block to fix:
Spork.prefork do #... the rest of your prefork here ActiveSupport::Dependencies.clear ActiveRecord::Base.instantiate_observers end
I’ve discovered a couple useful snippets to add to your Spork each_run block. Here we go:
# in spec/spec_helper.rb Spork.each_run do Factory.factories.clear Dir[Rails.root.join("spec/factories/**/*.rb")].each{|f| load f} end
# in spec/spec_helper.rb Spork.each_run do YourApplicationName::Application.reload_routes! end
# in spec/spec_helper.rb Spork.each_run do load "Sporkfile.rb" if File.exists?("Sporkfile.rb") end
Now you can manipulate the spec environment as you’re working and discard changes when you’re done.
# in Sporkfile # Reload some tricky controller that's acting up with Spork load "app/controllers/my_tricky_controller.rb"
I have found Spork to significantly, if not drastically decrease iterative test time. Statistics I’ve gathered follow:
Spec Type | # Tests | Time: Spork + Rspec | Time: Rspec | Savings |
---|---|---|---|---|
Model | 63 | 3s | 16s | 13s |
Controller | 47 | 6s | 19s | 13s |
All Models | 384 total | 5s | 26s | 21s |
All Controllers | 321 total | 25s | 44s | 19s |