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 failures
real 0m4.592s user 0m3.990s sys 0m0.536s
Wait, maybe we can just run
% time ruby -Ispec spec/models/publish_spec.rb .
Finished in 0.71331 seconds 1 example, 0 failures
real 0m4.468s user 0m3.953s sys 0m0.512s
F I V E SECONDS.
One Mississippi, Two Mississippi, Three Mississippi, Four Mississippi, Five Mississippi.
Let’s take a look at the admittedly ridiculous and contrived spec:
Maybe there is a
sleep hiding inside
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
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
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.064s
Finished in 0.17153 seconds 1 example, 0 failures
One quarter of a second.
Oh point two five seconds.
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 failures
real 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.)