As a Rails developer you'll be running a lot of specs. We often use guard to automate the process during development or use --only-failures to quickly fix a few failing specs. However, in our CI flows we need to run the full suite to ensure we're green before merging or deploying.
In all of our Rails projects we use GitHub Actions to run all of our CI checks. We typically run Pronto to check if our code is up to Rubocop standards and RSpec to ensure we haven't broken anything. We run these actions for every push to a Pull Request and every merge to our develop and master branches.
For new projects this is typically a pretty fast process, maybe a couple of minutes, however, some of our older projects might take upwards of 10 minutes to run the full test suite.
Last year I joined one of our longest running projects and immediately tasked myself with reducing the duration of our test suite. When I joined, the suite would take 19+ minutes to run. The first thing I did was run the suite with the profile flag resulting in the 10 slowest examples. Any that we're taking more than a couple of seconds got some attention to see if I could reduce them. Most of these specs used a lot of setup and teardown logic so I switched to stubbing which dramatically reduced a lot of the run times of these examples. At the same time, a colleague switched our mechanism for matching json responses which reduced our duration even further. We managed to get the suite down to around 8 minutes. A really impressive win.
The logic is pretty simple. Most computers have more than one CPU core but when we run RSpec we only use one of them. Parallel Tests checks how many cores we have available on the current machine and then splits the test suite up into that many groups. Each group is run in its own core with its own database and other processes to isolate the tests to their own core.
parallel_tests will expose an ENV var to let your code know which test group is currently running so you can modify the logic for starting services based on this variable. e.g. if we use PORT 9250 for elasticsearch in our specs we can change this to:
9250 + ENV['TEST_ENV_NUMBER'].to_i
We also need to do the same for our database name:
Now if we run RSpec manually our database will be hello_world but if we run it using parallel_tests we'll have multiple databases e.g. hello_world, hello_world2, hello_world3, hello_world4
Before we run our tests we need to set up these databases with the task parallel_tests provides.
To run our specs in parallel we simply change the invocation from rspec to parallel_tests
# bundle exec rspec spec
That's it! Now we're running our specs in parallel pre-Rails-6. With this simple change our 8 minute test suite went down to 4 minutes on GitHub which uses 2 core machines to run their actions. On 4 core machines we can run the specs in 2-3 minutes. That's a massive improvement over the original 19+ minutes we had before.