JUnit – Performance Testing

In this tutorial, you will learn how to test performance in JUnit, ensuring that your code meets performance requirements and runs efficiently.

Performance testing in JUnit focuses on measuring execution time for methods and ensuring they complete within acceptable limits. JUnit provides native support for performance validation using the assertTimeout() method, and it can be extended with third-party libraries for more detailed analysis.


Using assertTimeout() in JUnit

JUnit’s assertTimeout() method allows you to specify a maximum execution time for a piece of code. If the code takes longer than the specified time, the test fails. Here’s the syntax:

</>
Copy
static <T> T assertTimeout(Duration timeout, Executable executable);
static <T> T assertTimeout(Duration timeout, Executable executable, String message);

timeout: The maximum allowed execution time. – executable: The block of code to be tested. – message: An optional custom message displayed on failure.

Basic Example

Let’s test a method that simulates a long-running task:

</>
Copy
import static org.junit.jupiter.api.Assertions.assertTimeout;
import org.junit.jupiter.api.Test;
import java.time.Duration;

class PerformanceTest {

    void longRunningTask() throws InterruptedException {
        Thread.sleep(1000); // Simulates a 1-second task
    }

    @Test
    void testTaskCompletesWithinTime() {
        assertTimeout(Duration.ofMillis(2000), () -> longRunningTask(), "Task should complete within 2 seconds.");
    }
}

Explanation:

  • Task Simulation: The longRunningTask() method simulates a task that takes 1 second to complete.
  • Timeout Validation: The test ensures that the method completes within 2 seconds using assertTimeout().

Failing Test Example

If the method takes longer than the allowed time, the test fails:

</>
Copy
import static org.junit.jupiter.api.Assertions.assertTimeout;
import org.junit.jupiter.api.Test;
import java.time.Duration;

class PerformanceTest {

    void slowTask() throws InterruptedException {
        Thread.sleep(3000); // Simulates a 3-second task
    }

    @Test
    void testTaskExceedsTimeLimit() {
        assertTimeout(Duration.ofMillis(2000), () -> slowTask(), "Task exceeded the allowed time limit.");
    }
}

In this case, the test will fail with the message: Task exceeded the allowed time limit.


Using assertTimeoutPreemptively()

The assertTimeoutPreemptively() method terminates the test immediately if the timeout is exceeded. This is useful for testing tasks that might hang or run indefinitely.

</>
Copy
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import org.junit.jupiter.api.Test;
import java.time.Duration;

class PreemptiveTimeoutTest {

    void potentiallyHangingTask() throws InterruptedException {
        Thread.sleep(5000); // Simulates a task that might hang
    }

    @Test
    void testPreemptiveTimeout() {
        assertTimeoutPreemptively(Duration.ofMillis(2000), () -> potentiallyHangingTask(), "Task exceeded preemptive timeout.");
    }
}

Key Difference:

  • assertTimeout(): Waits for the method to finish before checking the duration.
  • assertTimeoutPreemptively(): Stops the method immediately if the timeout is exceeded.

Using External Libraries for Performance Testing

For advanced performance testing, you can integrate external libraries like JMH (Java Microbenchmark Harness). JMH is a powerful tool for benchmarking and analyzing performance-critical code.

Basic Example with JMH

Here’s an example of using JMH to benchmark a method:

</>
Copy
import org.openjdk.jmh.annotations.*;

import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
public class JMHExample {

    @Benchmark
    public void testMethodPerformance() {
        // Code to benchmark
        for (int i = 0; i < 1000; i++) {
            Math.sqrt(i);
        }
    }
}

Key Features:

  • Precise Benchmarking: JMH provides detailed performance metrics.
  • Multiple Modes: Supports different benchmarking modes like throughput, average time, and more.

Why Test Performance?

  • Ensure Responsiveness: Verify that methods execute within acceptable time limits, especially for time-critical applications.
  • Identify Bottlenecks: Detect performance issues early in development to avoid scaling problems later.
  • Optimize Resource Usage: Validate that your code uses system resources efficiently.
  • Maintain Quality: Prevent performance regressions when making changes or adding features to your application.

Best Practices for Performance Testing

  • Set Realistic Timeouts: Choose timeouts based on expected performance benchmarks.
  • Test in Realistic Environments: Run performance tests in environments that mimic production conditions.
  • Avoid Over-Optimizing: Focus on meaningful performance improvements rather than micro-optimizations.
  • Combine Tools: Use JUnit for basic validation and tools like JMH for detailed performance analysis.
  • Run Multiple Iterations: Execute tests multiple times to account for fluctuations in performance metrics.

Conclusion

Using assertTimeout() and assertTimeoutPreemptively(), you can validate execution times and prevent regressions. For advanced scenarios, integrating tools like JMH provides detailed insights into performance bottlenecks.