When should you use name-based `filter {}` versus tag-based `useJUnitPlatform { includeTags }`, and how do they interact on the same task?
answer
- name = FQN/package; tag = semantic
- two layers intersect (AND)
- tags survive renames
- name filter for generated tests
- tags drive CI stages
basics
~20 sUse name filters for structural/package selection (*IntegrationTest); use tags for semantic categories that survive renames (slow, db). On one task both apply: a test must pass the name filter AND the tag filter to run.
solid answer
~40 s`filter {}` matches fully-qualified **names** — great for selecting by package or class-name convention. Tags via `useJUnitPlatform { includeTags/excludeTags }` match **semantic labels** decoupled from names, so they survive refactors and express intent (slow, smoke, flaky). They are not mutually exclusive: configured on the same `Test` task they **intersect** — a test must satisfy the name filter and the tag filter to run. Prefer tags for categorization that drives CI staging because renaming a class won't silently drop it from a stage; use name filters when your only signal is package layout or you can't add annotations (e.g. third-party-generated tests). A typical layout: default `test` excludes `slow` tags; a dedicated `integrationTest` task includes them, optionally narrowed by a name filter to a package.
code
kotlin · 5 linestasks.register<Test>("integrationTest") {
useJUnitPlatform { includeTags("slow") }
filter { includeTestsMatching("com.acme.integration.*") }
shouldRunAfter(tasks.test)
}go deeper
Know both exist: names match FQN, tags match labels.
Explain the AND intersection when both apply and the rename-stability argument for tags.
Design separate tasks combining tags and name filters; justify the layering for a real CI pipeline.
Set org policy: tags as the canonical categorization signal feeding pipeline stages; name filters only as a structural fallback; document migration off JUnit 4 categories.
## Two filtering layers Gradle applies test selection in two stacked layers on a `Test` task: 1. **Engine layer** — `useJUnitPlatform { includeTags/excludeTags/includeEngines }`. Resolved by JUnit Platform during discovery, based on `@Tag` metadata. 2. **Gradle layer** — `filter { includeTestsMatching/excludeTestsMatching }`. Applied by Gradle on the fully-qualified names of whatever the engine discovered. Because they sit in different layers, when both are present they **compose as an intersection**: a test runs only if it passes the tag filter (engine) *and* the name filter (Gradle). ## Choosing the right tool | Need | Prefer | Why | |------|--------|-----| | Select by package/class convention | name `filter {}` | matches FQN directly | | Select by semantic category (slow/db/smoke) | `@Tag` + `includeTags` | survives renames, expresses intent | | Can't modify test source (generated) | name `filter {}` | no annotation needed | | Drive CI stages reliably | tags | refactors don't silently drop coverage | ## Why tags beat names for stability Name filters couple your build to a naming convention. Rename `OrderSlowTest` to `OrderHeavyScenario` and an `excludeTestsMatching("*SlowTest")` silently stops excluding it — a coverage/perf regression with no error (the pattern still matches *something* else, so `failOnNoMatchingTests` won't catch it). A `@Tag("slow")` label moves with the method during the rename. ## Putting it together ```kotlin tasks.test { useJUnitPlatform { excludeTags("slow") } // engine layer } tasks.register<Test>("integrationTest") { useJUnitPlatform { includeTags("slow") } // engine layer filter { includeTestsMatching("com.acme.integration.*") } // gradle layer shouldRunAfter(tasks.test) } ``` Here `integrationTest` runs tests that are **both** `@Tag("slow")` and in `com.acme.integration` — the intersection of the two layers. ## Caveat Name filters work with any engine; tag filters need `useJUnitPlatform()`. If a module is still on JUnit 4 (`useJUnit()`), tags are inert and you must fall back to `@Category` + `includeCategories`, or to pure name filtering.
- If a test passes the name filter but is excluded by a tag, does it run?No. The layers intersect — failing either the tag or the name filter excludes the test.
- Why can a name-based exclude silently lose effectiveness over time?It is coupled to naming. A rename can make the pattern stop matching the intended test, and `failOnNoMatchingTests` won't catch it if other tests still match.
- Give a case where you must use name filtering, not tags.When tests are generated/third-party and you can't add `@Tag` annotations, or the module still uses JUnit 4 without categories.
saying these in an interview costs you the question
- Claiming name filter and tag filter are OR-ed — they intersect (AND).
- Recommending name patterns for CI categorization without noting the rename-fragility.
- Assuming tags work regardless of the chosen test framework.