As a tech lead, how would you design a fast, reliable Surefire test-execution strategy across a large multi-module build?
answer
- Surefire units / Failsafe ITs
- forkCount 1C + per-fork dirs/ports
- pin Surefire+JUnit in parent POM
- ban shared static state & order deps
- CI never skips; profiles for local
basics
~20 sSplit fast unit tests (Surefire) from slow integration tests (Failsafe), parallelize units with forkCount per core while keeping per-fork resource isolation, pin one Surefire and test-framework version via parent POM, and enforce deterministic, thread-safe tests so the suite stays both fast and reliable.
solid answer
~40 sI'd separate concerns: Surefire runs fast, isolated unit tests in the test phase; Failsafe runs integration tests in integration-test/verify so cleanup always happens. For speed I'd parallelize units with forkCount=1C (or higher) and reuseForks=true to amortize JVM startup, giving each fork its own ports/temp dirs via ${surefire.forkNumber} to avoid clashes. Reliability comes from governance: pin Surefire and the JUnit/TestNG stack centrally in the parent POM (BOM-managed) so modules don't drift; ban shared mutable static state and order-dependence; quarantine flaky tests behind tags rather than disabling silently. I'd wire CI to consume surefire/failsafe XML uniformly, run units on every push and integration on a gated stage, and use profiles (e.g. -P fast) for local iteration. Treat -Dmaven.test.skip as an audited emergency lever only, never a default.
code
xml · 13 lines<build><pluginManagement><plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<forkCount>1C</forkCount>
<reuseForks>true</reuseForks>
<systemPropertyVariables>
<test.tmp>${project.build.directory}/fork-${surefire.forkNumber}</test.tmp>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins></pluginManagement></build>go deeper
Understand units and integration tests are run by different plugins.
Configure forkCount/reuseForks and separate unit vs integration phases.
Balance parallel speed against isolation and diagnose resource-collision flakiness.
Own the org-wide strategy: pluginManagement, version pinning, flaky-test policy, CI staging, and skip-flag governance.
## Goals A large multi-module build needs the test suite to be **fast enough** to run on every change yet **reliable enough** to trust. These pull in opposite directions (parallelism vs isolation), so the design is about managing that tension with governance. ## 1. Split unit vs integration - **Surefire** -> unit tests in `test` (fast, no external deps). - **Failsafe** -> integration tests in `integration-test`/`verify`, with `pre-/post-integration-test` for spinning resources up/down. Failure is deferred to `verify` so teardown always runs. - Adopt a naming/tag convention (`*Test` vs `*IT`, or `@Tag('integration')`). ## 2. Parallelize units safely ```xml <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>3.2.5</version> <configuration> <forkCount>1C</forkCount> <reuseForks>true</reuseForks> <systemPropertyVariables> <test.tmp>${project.build.directory}/fork-${surefire.forkNumber}</test.tmp> <server.port>0</server.port> <!-- random port --> </systemPropertyVariables> </configuration> </plugin> ``` `${surefire.forkNumber}` gives each JVM its own scratch space; random ports prevent collisions. Cap forks so CI agents don't thrash. ## 3. Governance for reliability - **Pin versions centrally**: one Surefire version and a BOM-managed JUnit/TestNG version in the parent POM, so 30 modules don't each drift and produce 'zero tests run' surprises. - **Enforce determinism**: no reliance on test order, no shared mutable statics/singletons across tests; fixed clocks/seeds. - **Flaky-test policy**: tag and quarantine, track, and fix - don't `@Disabled` silently. - **Skip discipline**: CI never skips; `-DskipTests` only for local artifact builds; `-Dmaven.test.skip` is an audited emergency path. ## 4. CI wiring - Run Surefire units on every push; gate Failsafe integration in a later stage (containers/DB). - Publish `target/surefire-reports` + `failsafe-reports` XML uniformly to the CI test dashboard. - Provide a `-P fast` profile for local loops (e.g. skip slow tags) without weakening CI. ## 5. Multi-module specifics - Use `mvn -T 1C` (build-level parallelism) carefully alongside Surefire fork parallelism to avoid oversubscribing cores. - Keep test configuration in the parent's `<pluginManagement>` so every module inherits the same isolation/parallelism settings. The net: parallelism for speed, per-fork isolation + centralized version pinning + determinism rules for reliability.
- How do you stop parallel forks from colliding on ports and files?Give each fork its own resources using ${surefire.forkNumber} for temp dirs and bind to random/ephemeral ports (port 0).
- Why centralize Surefire and JUnit versions in the parent POM?To prevent per-module drift that causes inconsistent or silently-skipped test runs across a large reactor.
- How do you combine mvn -T (parallel modules) with Surefire forking?Carefully - both consume cores; size -T and forkCount together to avoid oversubscription and CPU thrash.
It's like running many restaurant kitchens (forks) in parallel: each needs its own pantry and stove (per-fork temp dirs/ports), one corporate recipe book (pinned versions), and a rule against cooks sharing the same cutting board (no shared state) - otherwise speed creates chaos.
saying these in an interview costs you the question
- Making -DskipTests or maven.test.skip a default in CI.
- Cranking up parallelism without per-fork resource isolation.
- Letting each module pick its own Surefire/JUnit version.
- Silently @Disabling flaky tests instead of quarantining and fixing.