Simple Test Parallelization

May 20, 2013 Hunter Gillane

Let’s look at a simple approach to parallelizing a test suite for a Ruby app. Parallelizing your specs can be a good strategy to get a speedup on an existing slow suite. It can also be employed early on a greenfield project as part of a commitment to fast tests. The same caveats that Andrew mentions in that article post here as well, namely that parallelization might mask more important design changes you need to make in you suite.

While you could use a gem like parallel_tests, let’s look at what it would take to achieve this without needing to pull in another dependency.

The only requirement to employ this approach is that the parts of your build that you want to parallelize do not share a database, or if they do, that it will not cause test pollution if your specs run at the same time. These parallelizable portions of your build could be Rails engines, a library in lib, an unbuilt gem, or any other isolated piece of your app. If your app doesn’t meet that requirement, something like parallel_tests will likely be more useful.

Let’s assume that you are using engines to organize functionality in your app. In this case you likely are already using a separate database for each engine’s test suite, so let’s use that as a basis for our example. Assuming that you have two engines (engine1 and engine2) and they are both in the engines directory, you could write a rake task that parallelizes your build that looks something like this:

task :build do
  build_pids = []

  %w{engine1 engine2}.each do |engine_name|
    build_pids << fork { exec "cd engines/#{engine_name} && rspec spec" }

  trap(:INT) do
    build_pids.each do |pid|
        Process.kill(:INT, pid)
      rescue Errno::ESRCH

  Process.waitall.each do |pid, status|
    unless status.success?
      puts "Build failed"
      exit 1

  puts "Build successful"
  exit 0

This rake task does three things:

  • Uses fork+exec to kick off child processes to run each engine’s build
  • Collects the child processes after they have completed and exits non-zero if any of the child build processes were unsuccessful
  • Captures INT so that all child build processes will be killed when you Ctrl-C in the terminal

The output can be ugly but may be worth the time savings, especially if you only are going to be running this task as a last check before CI.

About the Author


Finding Pivotal
Finding Pivotal

The year is 2005. I’m one year out of school, and a year into a job doing PHP web development at a small de...

Debugging in RubyMine
Debugging in RubyMine

Before using RubyMine, my debugging workflow went something like this: Why isn’t this test passing?. It s...

SpringOne 2021

Register Now