Nothing sucks the joy out of writing your Rails app like having an incredibly slow test suite. Even running one file with a single spec on my new MacBookPro takes almost five seconds!
% time rspec spec/models/publish_spec.rb .Finished in 4.48 seconds 1 example, 0 failuresreal 0m4.592s user 0m3.990s sys 0m0.536s
Wait, maybe we can just run ruby?
% time ruby -Ispec spec/models/publish_spec.rb .Finished in 0.71331 seconds 1 example, 0 failuresreal 0m4.468s user 0m3.953s sys 0m0.512s
Five.
Seconds.
F I V E SECONDS.
One Mississippi, Two Mississippi, Three Mississippi, Four Mississippi, Five Mississippi.
An eternity.
Let’s take a look at the admittedly ridiculous and contrived spec:
Maybe there is a sleep hiding inside #publish_to?
Nope.
Very little of the time spent has to do with either the code under test, or the testing code. The majority of the time is just getting the test to run.
The source of most of this is the default spec_helper.rb that RSpec generates to load up our test environment.
Asking around about this on Twitter (you should follow me @leshill :) yielded no examples on how you might write your specs to get some of that time back. So I wrote the fast_specs app (it is available on github) to demonstrate how to make your own specs faster.
The fast_specs app has two spec suites, one a normal RSpec suite that can be invoked as a whole with:
% rake spec
Or invoked with individual specs with:
% rspec spec/models/publish_spec.rb
% ruby -Ispec spec/models/publish_spec.rb
And a Fast Spec suite that can be invoked as a whole with:
% rake fast
Or invoked with individual specs with:
% rspec -Ifast_specs fast_specs/models/publish_spec.rb
In order to use the Fast Spec suite, we put our fast specs under fast_specs much like we do with normal RSpec specs. For example, the spec for the Publish model would be located at fast_specs/models/publish_spec.rb.
At the top of our simple spec, with no changes to the implementation or the contents of the describe block, we require the fast_spec_helper, and the Publish model:
fast_spec_helper adds a tiny bit of sugar by providing app_require which just wraps loading files from your app.
Now when we run it:
% time rspec -Ifast_specs fast_specs/models/publish_spec.rb .real 0m0.249s user 0m0.183s sys 0m0.064sFinished in 0.17153 seconds 1 example, 0 failures
One quarter of a second.
Oh point two five seconds.
Much faster.
GO! FAST FASTER FASTEST GO!
Sometimes our code has some coupling to other parts of the system, and in those cases, we can just require the parts that we need during our spec. For example, if our Post depends on Publish our requires would look like:
Not all specs are written completely with mocks (although more should be), and sometimes we need additional setup. For example, lets say that we are moving an existing classic TDD model spec (again, completely contrived) for our Post that looks like this:
This spec requires accessing the database and ensuring some sort of transactional fixture support. We can write these support files, place them in them in fast_specs/support and then require them using another tiny bit of sugar with support_require to load our support files:
Timing this spec (which is using a transaction on the database) yields:
% time rspec -Ifast_specs fast_specs/models/post_spec.rb .Finished in 0.80608 seconds 1 example, 0 failuresreal 0m0.890s user 0m0.701s sys 0m0.170s
Not bad. And most definitely faster than our previous isolated spec was :)
This is only the beginning of making your specs faster. Yes, shaving 4 seconds off when running an individual spec is fantastic. If you have suggestions for more techniques or code that helps even further, make a pull request!
The approach outlined here is something that I just made up out of sheer frustration with the ridiculousness of waiting 5 seconds for a simple spec.
The idea of replacing the spec_helper.rb was first mentioned to me by Gary Bernhardt over dinner a while back. Corey Haines has also been advocating this approach in some of his talks (and which I would love to see one of these days :)
And I know there was a blog post that suggested some serious stubbing for ActionController a while back (I cannot find it now, if you know it, please ping me.)