Skip to content

Repository files navigation

split-test

split-test splits tests into multiple groups based on timing data to run tests in parallel.

Installation

Download binary from GitHub releases page:

https://github.com/mtsmfm/split-test/releases

Usage

split-test command outputs test groups to stdout depends on its executing time.

Let's say we have three test files spec/a_spec.rb, spec/b_spec.rb, spec/c_spec.rb and the following test report report/report.xml.

<testsuites>
<testsuiteclassname="all"name="all"time="18.0">
<testcaseclassname="a"name="a"file="./spec/a_spec.rb"time="5.0"></testcase>
<testcaseclassname="b"name="b"file="./spec/b_spec.rb"time="10.0"></testcase>
<testcaseclassname="c"name="c"file="./spec/c_spec.rb"time="3.0"></testcase>
</testsuite>
</testsuites>

If you run them in series, it'll take 18 seconds.

You can use split-test to split them into two groups and run in parallel.

For node 0:

$ split-test --junit-xml-report-dir report --node-index 0 --node-total 2 --tests-glob 'spec/**/*_spec.rb'

You'll get the following result on stdout:

/path/to/spec/a_spec.rb
/path/to/spec/c_spec.rb

For node 1:

$ split-test --junit-xml-report-dir . --node-index 1 --node-total 2 --tests-glob 'spec/**/*_spec.rb'

You'll get:

/path/to/spec/b_spec.rb

Please be sure to increment --node-index arg.

You can exclude specific test files from being split using the --tests-exclude-glob option:

$ split-test --junit-xml-report-dir report --node-index 0 --node-total 2 --tests-glob 'spec/**/*_spec.rb' --tests-exclude-glob 'spec/system/**/*_spec.rb'

This will include all files matching spec/**/*_spec.rb but exclude any files matching spec/system/**/*_spec.rb. This is useful when you want to run system specs separately from unit tests, as system specs typically take longer and may require different setup.

You can also specify multiple exclude patterns by using the --tests-exclude-glob option multiple times:

$ split-test --junit-xml-report-dir report --node-index 0 --node-total 2 --tests-glob 'spec/**/*_spec.rb' --tests-exclude-glob 'spec/system/**/*_spec.rb' --tests-exclude-glob 'spec/integration/**/*_spec.rb'

You can use --debug option to make sure how it's grouped:

$ split-test --junit-xml-report-dir . --node-index 1 --node-total 2 --tests-glob 'spec/**/*_spec.rb' --debug
[2021-01-09T02:55:04Z DEBUG split_test] {"/path/to/spec/b_spec.rb": 10.0, "/path/to/spec/c_spec.rb": 3.0, "/path/to/spec/a_spec.rb": 5.0}
[2021-01-09T02:55:04Z DEBUG split_test] node 0: recorded_total_time: 8
[2021-01-09T02:55:04Z DEBUG split_test] /path/to/spec/a_spec.rb
[2021-01-09T02:55:04Z DEBUG split_test] /path/to/spec/c_spec.rb
[2021-01-09T02:55:04Z DEBUG split_test] node 1: recorded_total_time: 10
[2021-01-09T02:55:04Z DEBUG split_test] /path/to/spec/b_spec.rb
/path/to/spec/b_spec.rb

Pass the result to test command to run grouped tests:

$ rspec $(split-test --junit-xml-report-dir report --node-index 0 --node-total 2 --tests-glob 'spec/**/*_spec.rb' --debug)

GitHub Actions

on: pushjobs:
# Download test-report and save as tmp-test-report to use the exactly same test report across parallel jobs.download-test-report:
runs-on: ubuntu-lateststeps:
# Use dawidd6/action-download-artifact to download JUnit Format XML test report from another branch.# actions/download-artifact v4 has run-id option but doesn't provide straight forward way to download.
- uses: dawidd6/action-download-artifact@v9with:
branch: mainname: test-report-.*name_is_regexp: trueworkflow: ci.ymlpath: reportmerge_multiple: true# Use continue-on-error to run tests even if test-report is not uploadedcontinue-on-error: true
- uses: actions/upload-artifact@v4with:
name: tmp-test-reportpath: reporttest:
needs: download-test-reportruns-on: ubuntu-lateststrategy:
matrix:
node_index: [0, 1, 2]steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1with:
bundler-cache: trueruby-version: 3.4
- uses: actions/download-artifact@v4with:
name: tmp-test-reportpath: report-tmp# Use continue-on-error to run tests even if test-report is not uploadedcontinue-on-error: true
- run: | curl -L --output split-test https://github.com/mtsmfm/split-test/releases/download/v1.1.0/split-test-x86_64-unknown-linux-gnu chmod +x split-test - run: bin/rspec --format progress --format RspecJunitFormatter --out report/rspec-${{ matrix.node_index }}.xml $(./split-test --junit-xml-report-dir report-tmp --node-index ${{ matrix.node_index }} --node-total 3 --tests-glob 'spec/**/*_spec.rb' --debug)
- uses: actions/upload-artifact@v4with:
name: test-report-${{ matrix.node_index }}path: reportif-no-files-found: error# Upload test-report on main branch only to avoid conflicting test reportif: github.ref == 'refs/heads/main'

You can find working example on https://github.com/mtsmfm/split-test-example

Note

split-test is inspired by circleci tests split command.

split-test assumes test report has additional attribute file or filepath.

<testsuites>
<testsuite>
<testcaseclassname="spec.0_spec"name="0 is expected to eq 0"file="./spec/0_spec.rb"time="0.000373"></testcase>
</testsuite>
</testsuites>

To be exact it isn't JUnit standard.

About

Splits test files into multiple groups to run tests in parallel nodes

Resources

Stars

69 stars

Watchers

2 watching

Forks

Releases

Used by

Contributors

Languages