Published on
← Blog——

Run RSpec specs in parallel (with the parallel_rspec gem)

Authors

In this article, I explain how to run your RSpec tests in parallel — and bank a 2x-4x speedup in the process — using the parallel_rspec gem

parallel_rspec is a great gem that makes it easy to run your specs in parallel — it handles cloning your database and running tests across N parallel workers. Further down I also benchmark the optimal number of workers and... turns out the defaults are pretty perfect 😅

Table of Contents

Overview

RSpec runs specs serially in a single CPU process. That is, one-by-one, using a single lonesome CPU core from your beefy dev machine. This is in contrast to Minitest + Rails, which natively supports parallel testing.

Given modern development machines typically have 8, 16, even 32+ powerful CPU cores, and plenty of RAM to boot, it would be great if we could actually make use of that hardware to run our specs in parallel (and hence much faster!).

We'd expect a sizeable speedup, and that's exactly what we see, if we run our RSpec specs with a gem like parallel_rspec. This gem makes it easy to run specs across N parallel workers (default N=4), leading to a 2x-4x speedup (approx).

I've tried a few different parallel RSpec gems and by far prefer parallel_rspec. Another popular option is parallel_tests, but I've always found it fiddly and annoying to deal with. In contrast, parallel_rspec has been rock-solid.

Speed comparison

Here's a speed comparison between vanilla rspec and parallel_rspec (using the default N=4 workers) running the AttendList test suite on my base M2 MacBook Air.

It contains 279 examples across a variety of spec types:

Vanilla RSpec

Terminal
❯ rspec ........................................................................ .................................................  Finished in 32.39 seconds 🐌 

Parallel RSpec

Terminal
❯ bin/parallel:rspec ........................................................................ .................................................  Finished in 10 seconds 🤯 

Running specs in parallel leads to a ~3.2x speedup, saving 22.39 seconds.

Getting started with parallel_rspec

Setting up the parallel_rspec gem is very straightforward (that's a big part of why I like this gem!)

Just add it to your Gemfile:

Gemfile
group :development, :test do  gem 'parallel_rspec' end 

then install it with bundle.

The parallel_rspec gem runs each thread against its own database instance (to avoid deadlocks), so prepare the test databases & run your specs with:

Terminal
# 1. prepare test databases bundle exec rake db:parallel:create db:parallel:prepare  # 2. run specs in parallel bundle exec prspec spec/ 

That's it! Your specs should run substantially faster than just running rspec.

Keep reading to explore my benchmarks and handy bin/parallel: scripts.

By default, prspec will use 4 workers to run your tests.

You can tweak this by adjusting the $WORKERS environment variable. However, I've found that 4 workers is pretty perfect, so be careful when tweaking this. If you do adjust WORKERS, ensure you re-run the :create and :prepare commands.

Each prspec thread runs against a separate instance of your database (created when running db:parallel:create and migrated with db:parallel:prepare). It automatically handles creating and naming your additional database instances.

Fine-tuning the number of workers

I've benchmarked prspec using 1-8 workers. The (rough and only vaguely scientific!) data I've collected is below:

tldr; the default WORKERS value of 4 seems optimal, at least for my machine.

N workerstime
1 (rspec, no parallel)21.7 seconds
212.4 seconds
39.4 seconds
4*7.5 seconds
58.9 seconds
612.2 seconds
716.6 seconds
817.1 seconds

Here's that data plotted:

In my testing, the default WORKERS value of 4 was optimal and led to specs running the quickest.
In my testing, the default WORKERS value of 4 was optimal and led to specs running the quickest.

I've run similar benchmarks on more powerful machines and got comparable results. With a beefy enough computer, though (and/or fewer other programs running), you may find N=5 or even N=6 to be optimal.

However, for most people, N=4 will deliver speedup enough without the need to re-run these benchmarks.

Note: Keen observers will note that rspec ran significantly faster here than (21.7 seconds) compared to the previous section (32.39 seconds). I ran these benchmarks against an earlier test suite of AttendList with fewer specs. The speedup in this section is similar, though — around 2.9x

Handy bin/parallel: scripts

I've put together a couple of scripts to encapsulate the 2 key things you'll do with parallel_rspec:

These scripts are modelled after the same bin/ pattern as bin/dev

bin/parallel:prepare

A short wrapper script to create & prepare test databases for each parallel thread:

bin/parallel:prepare
#!/usr/bin/env sh  # Prepare copies of DB for parallel_rspec # https://github.com/willbryant/parallel_rspec # bundle exec rake db:parallel:create db:parallel:prepare 

bin/parallel:rspec

Wraps the actual spec running aspect of parallel_rspec. You call it like so:

Terminal
bin/parallel:rspec # all specs bin/parallel:rspec spec/models # subset of specs 
bin/parallel:rspec
#!/usr/bin/env sh  # Runs specs in parallel with parallel_rspec. # By default runs in 4 groups (determined by WORKERS env) # If no args are provided, run the whole suite; otherwise, run the given path(s). # # https://github.com/willbryant/parallel_rspec # set -e  if [ "$#" -eq 0 ]; then  exec bundle exec prspec spec/ # run all specs if no path provided else  echo "Running parallel specs in $*..." # prints "running parallel specs in spec/path/to/specs"  exec bundle exec prspec "$@" fi 

Conclusion

I've been using the parallel_rspec gem a ton lately in my side projects, and it's worked so well that I've recently introduced it at my workplace too.

The speed gains from running specs in parallel are no-joke — ~3x in this article — and parallel_rspec so far has been rock-solid, without any of the troubles I had dealing with parallel_tests.