A shard is a deterministic subset of your test suite. Test sharding means splitting one long test run into multiple smaller runs, and executing them in parallel in CI. Instead of waiting for one job that runs every test, you wait for the slowest shard. This often reduces CI wall-clock time and gives faster feedback.
Meziantou.ShardedTest is a .NET global tool that creates deterministic shards and runs only the tests assigned to the current shard.
- Lists tests with
dotnet test --list-tests (you can still use the --filter argument to narrow down the test list) - Sorts tests deterministically
- Selects the tests for the shard identified by
--shard-index and --total-shards - Runs the selected tests with
dotnet test using appropriate filters, and forwards any additional arguments.
When running many tests, the command line length can become a concern, so the tool automatically splits long test filters into multiple dotnet test invocations.
This means the test list must be deterministic and stable across runs. This is guaranteed by dotnet test --list-tests as long as the test assembly and framework versions remain unchanged.
#Sharding vs test framework parallelization
These two concepts are related, but not the same:
- Test framework parallelization (xUnit, NUnit, MSTest, TUnit) runs tests concurrently inside one
dotnet test process on one machine. - Test sharding runs different subsets of tests in separate CI jobs, often on multiple machines.
You can combine both. For example, run 3 CI shards, and also let each shard run tests in parallel internally.
#Should you use sharding?
Sharding is a good fit when:
- Your CI test stage is slow and blocks pull request feedback
- A single machine cannot run all tests efficiently
- You can run multiple CI jobs in parallel
Sharding is less useful when:
- Your test suite is already small
- Most time is spent in setup/build instead of test execution
- Your tests are mostly IO-bound and already benefit from in-process parallelization
Measure before and after to confirm that sharding improves end-to-end CI duration.
#Trade-offs and limitations
The main trade-off is that parallel shards may require more CI jobs or machines, which can increase cost. Balance shard count with available concurrency and budget.
Also, if you have IO-bound tests, sharding may not provide as much benefit as it does for CPU-bound tests. Measure the test suite to confirm whether sharding delivers a meaningful speedup. For IO-bound tests, parallelization within the test framework (such as xUnit's parallel test execution) may be more effective than sharding at the CI level.
#How to use Meziantou.ShardedTest
Install the tool:
Shell
dotnet tool install --global Meziantou.ShardedTest
Run a shard locally or in CI:
Shell
sharded-test --shard-index 1 --total-shards 4 tests/MyTests.csproj --configuration Release
#GitHub Actions example
Use a matrix to run multiple shards in parallel:
YAML
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard-index: [1, 2, 3]
steps:
- uses: actions/checkout@v4
- name: Install sharded-test
run: dotnet tool install --global Meziantou.ShardedTest
- name: Run tests (shard ${{ matrix.shard-index }}/3)
run: sharded-test --shard-index ${{ matrix.shard-index }} --total-shards 3 tests/MyTests.csproj
#GitLab CI example
Use parallel: 3 to run one shard per job. GitLab sets CI_NODE_INDEX and CI_NODE_TOTAL automatically, and sharded-test uses them as fallback values so you do not need to pass shard arguments explicitly.
YAML
stages:
- test
test:
stage: test
parallel: 3
before_script:
- dotnet tool install --global Meziantou.ShardedTest
- export PATH="$PATH:$HOME/.dotnet/tools"
script:
- sharded-test tests/MyTests.csproj
#Additional resources
Do you have a question or a suggestion about this post? Contact me!