Getting started
TestDetta compares your branch with its base, works out which test projects and which test classes can observe the change, and runs just those. When it cannot be sure, it runs more, never less.
Requirements
- The .NET 10 SDK to run TestDetta itself. Your tests keep the SDK and runner your repository already uses.
- Full git history. TestDetta compares with the merge base of your branch and walks history to find coverage maps. Shallow clones cannot do either.
- The target branch fetched, so a ref such as
origin/mainexists. - A test runner as
dotnet testsees it. Both runners are supported; the runner is read fromglobal.json("test": { "runner": "Microsoft.Testing.Platform" }), the default being VSTest.
Quick start
GitHub Actions
The GitHub Action builds TestDetta for you; there is nothing to install. Add two steps to your workflow: one records a coverage map on pushes to your default branch, the other runs only the affected tests on pull requests.
- 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
The full workflow, the permissions it needs and every input are in GitHub Actions.
Other CI systems
GitLab CI, Jenkins, Azure DevOps, Bitbucket and on-premises setups run the same two commands, with a folder of per-commit coverage maps (--maps-dir) kept on a shared disk or in object storage. Install TestDetta as a .NET tool:
dotnet tool install --global <package>
The package name is set at launch. Until then, build the tool from source with dotnet build src/TestDetta.Cli -c Release -o testdetta-bin and call dotnet testdetta-bin/TestDetta.Cli.dll wherever td appears.
Pipelines for each system are in Other CI systems.
First commands
Run these from anywhere inside your repository. --base defaults to origin/main.
# What does my branch affect, and why?
td affected --base origin/main
# Run only the affected tests (add --dry-run to print the dotnet test commands)
td test --base origin/main
# Run every test with coverage and write one map per test project to .testdetta/coverage
td record
Without a coverage map, td test still works: it decides by name only, which is safe but selects more. Once td record has run at the base of your branch, selection becomes precise.
Add .testdetta/ to your .gitignore. It holds coverage maps and caches, never anything to commit.
Commands
| Command | What it does |
|---|---|
td affected [--format text|json|paths|commands] | Print what a change affects and why. --tests-only lists only affected test projects. |
td test [--dry-run] [--summary-file <path>] [-- <dotnet test args>] | Run only the affected tests. --summary-file appends a Markdown summary, for example to $GITHUB_STEP_SUMMARY. Arguments after -- go to every dotnet test, for example -- -c Release. |
td record [--project <csproj>]... [-c <configuration>] [-f <tfm>] [--parallel <n>] [--no-build] | Run all tests with coverage and write one map per test project to .testdetta/coverage. --parallel spreads each project's test classes over n test processes; --no-build records the output of an earlier build with the same configuration. |
td license status | Check the license and show what it covers. |
td license install <license|file> | Store a license in ~/.testdetta/license, for machines where no CI secret can hold it. |
Shared options
| Option | Meaning |
|---|---|
--base <ref> | Git ref to compare against. Default: origin/main. |
--repo <path> | Any path inside the repository. Default: the current directory. |
--max-map-age <n> | Use coverage maps recorded up to n commits before the merge base, bridging the changes in between. Default: 50; 0 uses only exact maps. See How it decides. |
--maps-dir <dir> | Coverage maps kept outside the repository, one folder per commit: record saves there, affected and test restore the newest one within --max-map-age. For CI systems other than GitHub Actions. |
--drift-fallback <all|name> | When the changes since an older map include a build file, configuration or a global using: run the affected test projects completely (all, the default) or decide them by name (name). |
-v, --verbose | Write debug logs to stderr: every decision a user might question, with its reason. |
--log-level <level> | trace, debug, information, warning (default), error or none. |
Logs go to stderr; stdout carries the command's output and stays machine readable.
Exit codes
| Code | Meaning |
|---|---|
0 | Success. |
1 | The analysis failed. |
2 | Invalid command line. |
3 | Tests failed. |