Overview
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.
Assumptions
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:
- rails 3.0.1
- rspec-rails 2.0.1
- rspec 2.0.0
- spork 0.8.4
- guard 0.2.2
- guard-spork 0.1.3
- rb-fsevent 0.3.9
Install Spork
Full instructions for installing Spork can be found on RailsTutorial.org
Install Guard
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:
- In your Gemfile:
group :test do gem 'spork' gem 'rb-fsevent' gem 'guard-spork' # ... end
- Install via bundle:
$ bundle install
Configure Rspec to use Spork for testing
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
Start Spork for RSpec Manually
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!
Start Spork via Guard
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
Guidelines for Testing with Spork
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:
- Start by running a full rake
- Before you start coding start Spork via Guard
- Ensure Spork starts successfully (guard will show an error if it cannot start spork)
- Start writing your tests
- Each time you run your test, specify the line number of the innermost describe or context which you are working in:
$ rspec /path/to/my/file:22
- When you complete a test for a single task for your feature, run rspec on the whole file:
$ rspec /path/to/file
- When your entire spec is passing, kill spork and re-run the spec without Spork
- Commit your changes, rinse and repeat
Troubleshooting Spork
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.
Use Spork workarounds only when running Spork
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:
- In spec/spec_helper.rb:
if Spork.using_spork? ActiveSupport::Dependencies.clear ActiveRecord::Base.instantiate_observers end - In your config/application.rb:
### 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
Fix ActiveRecord Observers not loading
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
Tips for your Spork each_run Block
I’ve discovered a couple useful snippets to add to your Spork each_run block. Here we go:
- Reload FactoryGirl’s Factories:
# in spec/spec_helper.rb Spork.each_run do Factory.factories.clear Dir[Rails.root.join("spec/factories/**/*.rb")].each{|f| load f} end - Reload Routes:
# in spec/spec_helper.rb Spork.each_run do YourApplicationName::Application.reload_routes! end
- Use a .gitignore’d file to allow developers to dynamically modify the each_run block:
# 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"
Statistics and Summary
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 |
Pingback: Crank Your Specs | Carbon Five Community
Pingback: Rails3+RSpec2+Spork+Guard(guard-rspec,guard-cucumber)で最速のBDD(振舞駆動開発)環境を作る | Curiosity Drives Me
Pingback: Rails3+RSpec+Spork+Guard環境のworkaround、Tips | Curiosity Drives Me
Pingback: RubyMine, Spork, RSpec, Cucumber | Virtuous Code
Pingback: Short testing feedback in rails 3 « SyGen development blog