parallel_tests: splitting RSpec, Minitest and Cucumber suites across CPU cores
Ruby: 2 CPUs = 2x Testing Speed for RSpec, Test::Unit and Cucumber
At a glance
- What is it?
- parallel_tests is a Ruby gem that divides a test suite into balanced groups and runs each group in its own process with its own database. It is a practical fit for Rails projects whose suite time has outgrown a single core, and a poor fit for suites that share global state.
- Who is it for?
- Adopt parallel_tests if your Ruby or Rails suite is CPU-bound and your tests can tolerate one database per process; the README's own numbers show the payoff shrinking as the group count rises, so the win is real but not linear. Do not adopt it if your tests write to shared external state (a single search index, a shared cache, a message broker) or if you cannot provision extra test databases, because the gem's model assumes process-level isolation that those tests break.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 15 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The suite time problem parallel_tests was built for
A Ruby test suite is single-threaded by default. RSpec, Minitest, Cucumber and Spinach each walk their files in one process, so a suite that takes 86 seconds on one core still takes 86 seconds on a 16-core build machine. The gem's answer is process-level parallelism: it splits the file list into groups and starts one Ruby process per group. The README frames the target audience plainly with the RailsCasts episode link and the Gemfile line that puts the gem in the development and test groups, which tells you this is a tool for application developers, not for library authors who ship a small gem with a handful of specs. If your suite finishes in a few seconds, the setup cost of extra databases and a split step will exceed the time you save.
How the split and the per-process database work
The mechanism has two halves. First, file assignment: the gem divides test files into balanced groups, either by number of lines or by recorded runtime from a previous run. Second, database isolation: each process gets its own database, keyed by the TEST_ENV_NUMBER environment variable. The README's table is the clearest statement of the contract. Process 1 sees an empty TEST_ENV_NUMBER, process 2 sees '2', process 3 sees '3'. Your config/database.yml is expected to interpolate that value, so process 2 connects to yourproject_test2 while process 1 uses yourproject_test. Without that interpolation, every process hits the same database and the run becomes a race. The group count defaults to the number of CPU cores, and the README shows the shape of the trade-off with its own measurements: forcing one CPU gives 86 seconds, two CPUs give 47, four give 26. Those figures come from the project's documentation, not from an independent run, and the pattern shows diminishing returns rather than a clean division by core count.
The gem also supports running arbitrary Rake tasks in parallel, which matters for setup work that is not a test suite at all:
RAILS_ENV=test parallel_test -e "rake my:custom:task"That command starts the named task in one process per core. A second form passes a task and an explicit process limit through Rake:
rake "parallel:rake[my:custom:task,2]"The trailing 2 caps parallelism, which is useful when the task touches a resource that cannot take the full core count.
Installing parallel_tests and running a first parallel spec suite
The README gives the install as a Gemfile entry in the development and test groups, then a database.yml change, then a Rake task to create the extra databases. Start with the Gemfile:
gem 'parallel_tests', group: [:development, :test]Run bundle install after that. Next, make the test database name depend on the process number. The README's example uses ERB inside database.yml:
test:
database: yourproject_test<%= ENV['TEST_ENV_NUMBER'] %>With that in place, create the databases the gem will use. The README lists rake parallel:create for the default case and a suffix form for multi-database setups:
rake parallel:create
rake parallel:create:secondaryThen copy the development schema into every test database. The README notes this must be repeated after migrations:
rake parallel:prepareFinally run the suite. The task name selects the framework, so RSpec uses parallel:spec and Minitest uses parallel:test:
rake parallel:specThe README's example output shows what to expect on the console: a line reporting the process count and the split, for example 2 processes for 210 specs at roughly 105 specs per process, followed by the normal RSpec summary and a total duration. If you see every spec running in one process, TEST_ENV_NUMBER is not reaching database.yml. For CI, the README points at rake parallel:setup, which creates the databases and loads the schema in one step.
Uneven groups, runtime logs and the loggers that fix them
Line-count splitting is a guess. A group of ten fast unit files can finish long before a group holding three slow integration files, and the whole run waits on the slowest process. The gem addresses this with runtime logs. You add a logger to your RSpec configuration, or require the Minitest logger in test_helper.rb, and the next run reads the recorded times to build better groups. For RSpec the README's snippet goes into .rspec_parallel:
--format progress
--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.logFor Minitest, the logger is required conditionally in test_helper.rb and writes only when RECORD_RUNTIME is set:
if ENV['RECORD_RUNTIME']
require 'minitest'
require 'parallel_tests/test/runtime_logger'
endThe README is explicit that this is a two-run process: record first, then let the next run consume the log, with --runtime-log pointing at a non-default path if you moved the file. On CI the log has to survive between runs, and the README links an example workflow where chunks are combined before upload. That is real operational work, not a flag you flip once. The same section documents three loggers that solve a different problem, interleaved output. SummaryLogger collects spec output into one file instead of letting processes overwrite each other, FailuresLogger writes pasteable rerun lines such as rspec /path/to/my_spec.rb:123, and VerboseLogger prints a line per example start and finish with PID and process number so you can see what each process is doing. Cucumber gets its own FailuresLogger, whose output file can be passed back to Cucumber with an @ prefix to rerun failures.
Setup once, teardown once, and the race the README admits
Some work must happen exactly once per run, not once per process. The README's pattern uses ParallelTests.first_process? to pick a single process, writes a marker file to /tmp, and has the other processes sleep until the marker appears. It then uses at_exit with last_process? for cleanup. The comment in that snippet is worth reading twice: the first process may boot slower than the second, so the marker-file wait is a race-condition workaround, and the README says so. The same snippet notes that last_process? means the last process to start, not the last to finish, which is why cleanup calls ParallelTests.wait_for_other_processes_to_finish before undoing anything. If your setup step is idempotent and cheap, skip this complexity. If it is expensive and must run once, this is the shape you are signing up for, including the sleep loop.
Where parallel_tests is the wrong tool
The gem's isolation model is one process and one database. Tests that reach outside that boundary do not parallelise safely. A test that writes to a shared Redis instance, a single Elasticsearch index, a fixed port, or a shared S3 bucket will collide across processes, and the gem offers no coordination for those resources beyond the first_process? hook. Suites with heavy global state, such as ones that mutate class-level configuration, will produce failures that vanish when you force one CPU. There is also a floor on the payoff. The README's own numbers show 86 seconds at one CPU and 26 seconds at four, so the marginal gain per core drops sharply, and the fixed costs (extra database creation, schema loading, runtime-log bookkeeping) do not shrink with the suite. On a laptop with two cores, the setup can cost more than it saves. Finally, the gem is Ruby-specific: it knows how to invoke RSpec, Minitest, Cucumber and Spinach, and nothing else.
Alternatives and the difference in approach
The nearest alternative inside the Ruby world is not a different gem but a different axis: running the suite in a single process with a parallel-capable runner, or splitting the suite across CI machines rather than cores. Parallel_tests stays inside one machine and one Rake invocation, which keeps the failure output in one place and needs no CI matrix. A CI matrix trades that simplicity for horizontal scale and for the ability to use different Ruby versions per shard, at the cost of merging artifacts and duplicating setup per job. Outside Ruby, the related searches people type about this project include parallel tests in pytest, parallel tests junit, parallel tests golang and parallel tests playwright, and the comparison is instructive: pytest-xdist distributes tests across workers with its own scheduling and no database convention, while parallel_tests assumes a Rails-style database.yml and a Rake task surface. If your project is not Rails-shaped, the gem's conventions work against you rather than for you.
Maintenance, licence and upgrade cost
The repository is not archived, and its last push was on 2026-09-15, so there is recent activity on the default branch. The licence is MIT, which permits commercial use and modification; the repository ships a LICENSE file at the top level. The gemspec, Gemfile and Gemfile.lock are all present, so the dependency surface is inspectable before you adopt it. On upgrades, the cost is concentrated in two places. The database.yml interpolation and the parallel:prepare step must be repeated after migrations, and the README says so directly, which means every schema change touches your test setup. The runtime log format is a file the gem writes and reads, so a version bump that changes it will cost you one recording run before grouping returns to normal. The CHANGELOG.md at the repository root is the place to check before bumping the gem version; the README does not document rollback, so plan a pinned version in your lockfile if the upgrade matters to a release.
Editorial conclusion
Adopt parallel_tests if your Ruby or Rails suite is CPU-bound and your tests can tolerate one database per process; the README's own numbers show the payoff shrinking as the group count rises, so the win is real but not linear. Do not adopt it if your tests write to shared external state (a single search index, a shared cache, a message broker) or if you cannot provision extra test databases, because the gem's model assumes process-level isolation that those tests break. Verify three things before committing: that your config/database.yml actually interpolates ENV['TEST_ENV_NUMBER'], that a full parallel:setup run creates and loads every database on your CI machine, and that your suite passes with the same file count when you drop to one CPU via rake "parallel:test[1]". If the one-CPU run is not clean, the parallel run only hides the failure.
Frequently asked questions
What does the parallel_tests gem do?
It splits a Ruby test suite into balanced groups and runs each group in its own process, with its own database keyed by TEST_ENV_NUMBER. It supports Minitest, RSpec, Turnip, Cucumber and Spinach.
How do I install parallel_tests in a Rails project?
Add gem 'parallel_tests' to the development and test groups in your Gemfile, then make config/database.yml interpolate ENV['TEST_ENV_NUMBER'] into the test database name. After that, run rake parallel:create and rake parallel:prepare before the first parallel run.
How do I run RSpec in parallel with parallel_tests?
Use the rake parallel:spec task. The README's example output shows the gem reporting the process count and the per-process spec split before the normal RSpec summary.
Why are my test groups finishing at different times?
Line-based splitting does not know which files are slow. The README's fix is to record runtime with ParallelTests::RSpec::RuntimeLogger or the Minitest runtime logger, then let the next run use those recorded times to build the groups.
Can parallel_tests run a Rake task in parallel?
Yes. The README gives RAILS_ENV=test parallel_test -e "rake my:custom:task" and the Rake form rake "parallel:rake[my:custom:task,2]", where the trailing number caps parallelism.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/grosser-parallel-tests)