After recently switching to a lower-performance laptop for my regular development tasks, I decided to spend a bit of time cleaning up some long-running rspec examples. I found that some misunderstanding of rspec’s features had led me to write highly inefficient specs.
I’m normally among the first to complain about optimisation chores, but this time the optimisation wasn’t premature: I had a compelling reason to fix these tests, and since I’m not under any pressure at the moment to knock out features, it seemed like a good time to do some learning.
RSpec setting for measuring performance
The first step in my optimisation approach was to modify my
spec/spec.opts to use a feature of rspec I didn’t normally use:
--format profile shows a list of the slowest examples. You might also want to look at the output of
spec --help, especially
Fixing scope specs
The specs that were causing the most delay were those that could not make use of stubbing and mocking. That is, the specs for database operations. I was mainly confronted by ActiveRecord scope specs taking up to 20 or so seconds per group. Here is an example of the sort of thing I was doing:
Notice the use of the subject method, which allows the use of the very terse
it statements. I’m also using the Machinist object factory for each object creation call (Machinist provides
There are two big problems here:
beforeblock (which is an implicit
before(:each)) will run five times (once per example), when it could be run just once
- The call to
Invoice.arrearswill be run five times (once per example), when it could be run just once, after the initial set up
So, before attacking this slow running spec group, I had nine factory calls. Just five database inserts, right? Wrong: each
Tutorial.make call also created a chain of further objects, so the database write operations were pretty heavy.
Here’s the fixed version:
In this version, I’ve created a container group called “scopes” that allows me to reuse the subject declaration for other scope specs. The subject declaration is now almost like documentation: “In this group I am always testing a collection of invoices”.
before block is now a
before(:all) block, which means that the block will be run just once for the group of specs. At the end of the block I make the call to
Invoice.arrears, so the database read operation is also only performed once.