Accelerating Test Execution: Parallel Testing with Gradle and JUnit 5

Introduction

Testing is a cornerstone of the software build process, yet it can become a bottleneck as projects grow. The most straightforward way to reduce test execution time is to take advantage of modern multi-core processors by running tests concurrently. In this article, we explore how to set up and execute parallel tests using Gradle and JUnit 5, enabling faster feedback and more efficient CI/CD pipelines.

Accelerating Test Execution: Parallel Testing with Gradle and JUnit 5
Source: www.baeldung.com

Configuring Gradle for Parallel Execution

To enable parallel test execution, we need to adjust the Gradle build configuration. The following build.gradle file demonstrates a minimal setup for a Java library project using Gradle 9 and JUnit 5:

plugins {
    id 'java-library'
}
test {
    maxParallelForks = (int) (Runtime.runtime.availableProcessors() / 2 + 1)
    useJUnitPlatform {
        includeTags testForGradleTag
    }
}
repositories {
    mavenCentral()
}
dependencies {
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}

The key property is maxParallelForks, which determines the maximum number of test processes that can run simultaneously. Here we set it to half the available processors plus one, a balanced starting point. The useJUnitPlatform block configures the JUnit 5 test engine, and includeTags allows filtering tests by the @Tag annotation. To make this filtering work, we must also define a default value in the gradle.properties file:

testForGradleTag=serial

With this default, only tests tagged with @Tag("serial") will run unless overridden. This optional setting simplifies our tutorial but is independent of the forking mechanism.

Understanding the Configuration

The maxParallelForks property controls the number of worker processes Gradle spawns. Each process runs a subset of test classes in isolation. Within a single process, tests within a class may also run in parallel if enabled (via JUnit 5 configuration). The formula Runtime.availableProcessors() / 2 + 1 provides a reasonable parallelism level that avoids overwhelming the system. You can adjust this number based on your hardware and test characteristics.

Notice the includeTags filter: it uses the Gradle property testForGradleTag. This enables dynamic tag selection – for example, you could run ./gradlew test -PtestForGradleTag=parallel to execute only parallel-marked tests.

Implementing Parallel Test Classes

Let’s create a test class named UnitTestClass1 to see parallel execution in action. We’ll include timing utilities to measure test duration:

@Tag("parallel")
@Tag("UnitTest")
public class UnitTestClass1 {
    private long start;
    private static long startAll;

    @BeforeAll
    static void beforeAll() {
        startAll = Instant.now().toEpochMilli();
    }

    @AfterAll
    static void afterAll() {
        long endAll = Instant.now().toEpochMilli();
        System.out.println("Total time: " + (endAll - startAll) + " ms");
    }

    @BeforeEach
    void setUp() {
        start = Instant.now().toEpochMilli();
    }

    private LocalTime localTimeFromMilli(long time) {
        return Instant.ofEpochMilli(time)
            .atZone(ZoneId.systemDefault())
            .toLocalTime();
    }
}

Now add four identical test methods that simulate work by sleeping for one second:

@Test
public void whenAny_thenCorrect1() throws InterruptedException {
    Thread.sleep(1000L);
    assertTrue(true);
}

@Test
public void whenAny_thenCorrect2() throws InterruptedException {
    Thread.sleep(1000L);
    assertTrue(true);
}

@Test
public void whenAny_thenCorrect3() throws InterruptedException {
    Thread.sleep(1000L);
    assertTrue(true);
}

@Test
public void whenAny_thenCorrect4() throws InterruptedException {
    Thread.sleep(1000L);
    assertTrue(true);
}

Finally, add a @AfterEach method that logs individual test times:

Accelerating Test Execution: Parallel Testing with Gradle and JUnit 5
Source: www.baeldung.com
@AfterEach
void tearDown(TestInfo testInfo) {
    long end = Instant.now().toEpochMilli();
    System.out.println(testInfo.getDisplayName() + " took " + (end - start) + " ms");
}

Analyzing Test Execution Times

When run sequentially, the four one-second tests would take approximately 4 seconds. With maxParallelForks set to an appropriate value (e.g., 2 on a quad‑core machine), the suite finishes in about 2 seconds. The exact speedup depends on the number of worker processes and the nature of your tests. The @BeforeAll and @AfterAll timestamps provide a clear picture of total execution time.

Best Practices and Considerations

Conclusion

Parallel testing with Gradle and JUnit 5 is a powerful technique to reduce build times without changing test logic. By adjusting maxParallelForks, leveraging tag‑based filtering, and structuring test classes appropriately, developers can achieve significant speedups on multi‑core machines. Start with the configuration shown in this section, experiment with your own test suites, and enjoy faster feedback loops.

Tags:

Recommended

Discover More

How to Integrate AI into Database Management: A Step-by-Step GuideUnveiling Fast16: A Stealthy State-Sponsored Sabotage MalwarePreserving Digital Infrastructure: How Chainguard Sustains Abandoned Open Source ProjectsExploring the Latest Web Innovations: Canvas HTML, Hexagonal Analytics, E-Ink OS, and CSS Image SwapsUnleash the Force: Top Lego Star Wars Sets for May the 4th