In this lesson, you will learn how to configure test discovery settings to optimize build-time execution, ensuring your CI/CD pipelines remain fast as your test suite grows.
The Role of the Test Runner
While you write tests using frameworks like xUnit or NUnit, the Test Runner is the engine that actually discovers, orchestrates, and reports on them. In a large-scale project, running every single test on every commit becomes a bottleneck. To maintain developer velocity, you must move beyond running all tests and start treating test discovery as a configurable build-time strategy.
Optimizing Discovery via Configuration
Most test runners (like dotnet test) perform discovery by scanning your compiled assemblies for specific metadata (attributes like [Fact] or [Test]). You can drastically reduce build times by narrowing this scope using filter expressions and configuration files.
For example, instead of running everything, you can target specific subsets based on traits, categories, or naming patterns. If you use a .runsettings file, you can define specific discovery patterns to exclude slow integration tests or legacy suites that don't need to run on every PR:
<RunSettings>
<RunConfiguration>
<!-- Limit discovery to specific namespaces or categories -->
<TestAdaptersPaths>./adapters</TestAdaptersPaths>
</RunConfiguration>
<TestRunParameters>
<Parameter Name="ExcludeSlowTests" Value="true" />
</TestRunParameters>
</RunSettings>
When invoking the runner via the CLI, you can apply filters to further optimize the execution:
dotnet test --filter "Category!=Integration&Priority=High"
Why Discovery Matters
When your test suite reaches thousands of entries, the overhead of "discovering" tests can take longer than the actual execution. By using dotnet test arguments to limit discovery to only the files changed—or by partitioning tests across multiple parallel agents using the runner’s built-in filtering capabilities—you transform the test runner from a simple execution tool into a sophisticated build-time optimization engine.
By strategically limiting discovery, you ensure that the feedback loop remains tight, preventing the "test tax" from slowing down your deployment pipeline.
Key Takeaway: The Test Runner is not just an executor; by configuring discovery filters and using .runsettings, you shift from blindly running all tests to a surgical approach that minimizes build-time overhead.