Test impact analysis for .NET

Run only the tests your change can break.

TestDetta reads your diff, follows it through projects, symbols and recorded coverage, and runs just the test classes that can observe it. When it cannot be sure, it runs more, never less.

$ td test --base main -- -f net10.0td: 1 changed file(s), 1 affected test project(s).Tests to run:  filtered src/Spectre.Console.Tests/Spectre.Console.Tests.csproj    (14 of 141 test class(es) run or reference the changed code, 0.6 s of 3.0 s (80% saved))Recorded test time: 0.6 s of 3.4 s (83% saved).Passed!  - Failed: 0, Passed: 156, Total: 156 - Spectre.Console.Tests.dlltd summary:  passed  src/Spectre.Console.Tests/Spectre.Console.Tests.csproj  (14 class(es), 4.6 s)1 project(s) run, 0 failed, 0 skipped.# a real change to Spectre.Console: see the whole run

Measured on real code, not on demos

We change members of popular open-source .NET libraries one at a time and check that every test class that fails is one TestDetta selected.

41open-source repositories, each with its own test framework, runner and build quirks
2,941changes that broke at least one test
3 missed, all fixedevery miss gets a test that fails without the fix, and its repository runs again until clean

A change to one member ran a median of 28% of the test classes. See a real run, from diff to job summary →

How it decides

Each step narrows the selection only when it has evidence. Anything it cannot trace falls back to running more.

1

Projects

Changed files map to projects and follow ProjectReferences. Build-wide files such as Directory.Build.props affect everything below them.

2

Symbols

C# syntax trees are compared, so comment and whitespace edits select nothing, and TestDetta knows which members changed.

3

Coverage

A map recorded on your default branch says which test class ran which method, so changed methods select their classes directly.

4

Names

New code, fields and constants are traced through the source by name until they reach test classes or code the map knows.

5

Fallbacks

Configuration files, global usings, reflection-discovered types: when a change cannot be traced, the whole test project runs.

6

Self-checks

A filter that matches no test is treated as a bug in TestDetta, and the project runs again without it.

Two steps in GitHub Actions

Record a coverage map on every push to your default branch. Pull requests restore the newest map and run only the affected tests, with a job summary that says why each class ran or was skipped.

GitHub Actions guide →
GitLab CI, Jenkins, Azure DevOps, Bitbucket →

- if: github.event_name == 'push'
  uses: <owner>/testdetta@<sha>
  with:
    mode: record

- if: github.event_name == 'pull_request'
  uses: <owner>/testdetta@<sha>
  with:
    mode: test
    license: ${{ secrets.TESTDETTA_LICENSE }}

Built for teams that can't afford a missed test

Your code stays with you

TestDetta runs inside your CI. No source, coverage or test results leave your machines; committer emails are only counted locally.

Every framework you use

xUnit v2 and v3, NUnit, MSTest and TUnit, under both VSTest and Microsoft.Testing.Platform, including their own parallelization.

Never blocks a build

Without a valid license every test runs, as before. A license problem costs CI time, never a red build.

Free for public repositories. 30 days free for private ones.

Then $15 per active committer per month. No card for the trial.