skip to content

Contrast running tests via TestLauncher with running them via BuildLauncher.forTasks("test") plus the --tests filter. When does each make sense?

level: seniorimportance: should knowfreq 22%

answer

  1. TestLauncher = test-centric, task resolved
  2. BuildLauncher = task-centric + --tests filter
  3. debugTestsOn only on TestLauncher
  4. descriptors/re-run only on TestLauncher
  5. choose TestLauncher for IDE run/debug

basics

~20 s

TestLauncher selects tests declaratively and lets Gradle find the right task, with structured test events and per-test debug. BuildLauncher.forTasks("test") runs a named task and you narrow it with the --tests command-line filter. TestLauncher is purpose-built for IDE test execution.

solid answer

~40 s

Both can run a subset of tests, but they model the problem differently. With **BuildLauncher** you name a task (`forTasks(":app:test")`) and pass `--tests com.acme.FooTest.shouldWork` as a build argument; you must know which task to run and the filter is a string. With **TestLauncher** you say *which tests* (`withJvmTestMethods`) and Gradle resolves the matching task(s) for you; you also get first-class `debugTestsOn` and descriptor-based selection/re-run. For IDE 'run/debug single test', TestLauncher is the right tool: it's declarative, task-agnostic, integrates cleanly with TEST progress events, and supports re-run-from-descriptors. BuildLauncher+`--tests` is fine for scripted CI invocations where you already know the task and just want a filtered run, or where you want the full task's side effects. Net: TestLauncher = IDE-grade targeted test execution; BuildLauncher+`--tests` = task-centric, CLI-flavored filtering.

code

kotlin · 10 lines
kotlin
// Task-centric
connection.newBuild()
    .forTasks(":app:test")
    .withArguments("--tests", "com.acme.FooTest.shouldWork")
    .run()

// Test-centric (preferred for IDE run/debug)
connection.newTestLauncher()
    .withJvmTestMethods("com.acme.FooTest", "shouldWork")
    .run()

go deeper

for a junior

Know both can run a subset of tests; TestLauncher is the test-focused one.

for a middle

Contrast naming a task + --tests filter against declarative test selection.

for a senior

Justify TestLauncher for IDE work: task resolution, debugTestsOn, descriptor re-run; pick BuildLauncher for known-task filtered runs.

for a principal

Decide the execution strategy for an IDE/CI integration layer and where each launcher fits in the architecture.

## Two mental models **Task-centric (BuildLauncher):** 'Run task `:app:test`, but filtered.' ```kotlin connection.newBuild() .forTasks(":app:test") .withArguments("--tests", "com.acme.FooTest.shouldWork") .run() ``` You pick the task; `--tests` filters within it. If the class lives in another task, you must name that task. The filter is a string honoring the same patterns as the CLI. **Test-centric (TestLauncher):** 'Run these tests; figure out the task.' ```kotlin connection.newTestLauncher() .withJvmTestMethods("com.acme.FooTest", "shouldWork") .run() ``` Gradle locates the `Test` task(s) containing the class. No task name needed. ## Capability comparison | Concern | BuildLauncher + --tests | TestLauncher | |---|---|---| | Selection style | String filter on a named task | Declarative test selection | | Knowing the task | Required | Resolved by Gradle | | Per-test debug | No dedicated API | `debugTestsOn(port)` | | Re-run from results | Manual | `withTests(descriptors)` | | Test events | TEST events available via listener | TEST events available via listener | | Multi-source-set class | Run the specific task | `withTestsFor`/`forTaskPath` | ## When to use which - **IDE single-test run/debug:** TestLauncher — declarative, debuggable, descriptor-driven re-run. - **You explicitly want a task's full behavior** (e.g. a custom test task with extra wiring) but filtered: BuildLauncher + `--tests`. - **Scripted/CI one-liners** where the task is known: either works; `--tests` is familiar and terse. - **Dynamic/parameterized re-run fidelity:** TestLauncher with descriptors. ## Design takeaway If you're building IDE-like targeted execution, reach for TestLauncher; it encapsulates task resolution, debugging, and result-driven re-runs that you'd otherwise re-implement on top of BuildLauncher.

  • Which approach gives you per-test debugging without custom JVM-arg plumbing?
    TestLauncher, via debugTestsOn(port). BuildLauncher has no dedicated per-test debug API.
  • If you don't know which task contains a test class, which is easier?
    TestLauncher — it resolves the matching Test task(s) from the class/method selection, so you don't have to name the task.
  • Is there a case to still prefer BuildLauncher + --tests?
    Yes — when you specifically want the full task invocation (including its other side effects) filtered, or for terse known-task CI scripts.

saying these in an interview costs you the question

  • Claiming --tests can't filter at all — it can; the difference is task-centric vs test-centric.
  • Saying BuildLauncher offers debugTestsOn — that API is on TestLauncher.
  • Ignoring that TestLauncher resolves the task for you.

context