Speed Up .NET CI with Test Sharding

 
 
  • Gérald Barré

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.

#What the tool does

  • 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!

Follow me:
Enjoy this blog?