Quick Hack: Cutting RSpec test time…
I spent last couple hours trying to cut feedback time for the RSpec test suite for the Rails project that I work on….
I did fairly general things… and managed to cut the feedback time to almost 40% (approximately from 174 seconds to 72 seconds, on a MacBook Pro-Intel). Which is quite significant….
So, here are the two major things i did….
Nailing the worst few( -24 seconds out of 174 ):
I used spec ./spec -f profile to get me the 10 most time taking specs. And changed before(:each) to before(:all). In RSpec world, theoretically, each spec is supposed to validate one behavior. So specs on there own, were pretty lean, doing things like asserting sensible defaulting, correct options for SQL query, or verifying result of a query etc… The data setup was happening in the before block, and everyone was using the same data. Nested describe(s) were adding to the before(s)…
On these low performing specs, before(s) were pretty bloated… so making them before(:all) reduced the execution time by (before block time)*(number of specs)…..
Practicality beats purity…. I should not have done this… but it gave me some 24 seconds of boost…(when done for 40 specs)… so i took it….. BUT it left the specs vulnerable to side effects(hence not infinitely scalable…. more you do it… lesser the benefit, and more the miseries…..). I had to put some extra effort to fix the side effects caused by the same entities getting shared across multiple specs… Not the best solution in the galaxy…. but whatever works…..
Parallelization….. The bad boy( -78 seconds out of 174 ):
The thought process behind this one was, that a lot of time is wasted on a synchronized process, when it gets IO bound. Writing to the db socket, reading from it… reading files…(ruby files, haml templates, spec files themselves… rails code-base files….) you name it…
But wait… bringing Ruby Threads in, is gonna be NO better either…. because ruby doesn’t have native threads…
Old friend to rescue….. I used fork….
The app has specs segregated in sub-directories…(spec -> controllers, spec -> views, spec -> models, spec -> helpers, spec -> lib) etc…. I spawned one process per sub-directory….
I wrote this crude rake task(http://pastie.org/266452), which did the trick….
I defined more environments for each sub-spec set in the database.yml file… which had different database names(to avoid deadlocks @ the DB level)…. now the database.yml looks like this…. (http://pastie.org/266456)
Thats it….. now i just run rake foo, which does the trick….
Reporting is pretty insane… because it has to be inspected it visually…. but thats fairly ok… because we are planning to use this only for developers doing quick sanity check before pushing to the source control…. continuous integration box is still using the conventional single process thing(rake spec).
Im sure reporting can be tweaked to figure out passing or failing of specs…. and then the parallelized version can be used on the continuous integration box as well…… Wether im gonna do it or not… depends on how much time i get tomorrow… ;-)
Hope this was useful……