用JUnit做接口性能测试是用循环或 @RepeatedTest多次调用目的接口,记录每次耗时,然后在测试中统计平均值、P99 等标准,并断言这些标准是不是达标。轻松嵌入CI流水线,每次创建都自动检查接口延迟有没有问题。
两个可以直接运行的示例:
使用 java.net.http.HttpClient(Java 11+,推荐)
使用 HttpURLConnection(Java 8 兼容)
1. 准备工作(Maven 依赖)
只需要引入 JUnit 5:
xml
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
2. 根据 Java 11+ HttpClient
java
import org.junit.jupiter.api.*;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ApiPerformanceTest {
// ========== 待测接口 ==========
private static final String API_URL = "https://jsonplaceholder.zmtests.com/posts/1";
private static final int WARMUP_COUNT = 5; // 预热次数
private static final int TEST_COUNT = 50; // 正式测试次数
private static final double MAX_AVG_MS = 500; // 平均延迟阈值(ms)
private static final double MAX_P99_MS = 1000; // P99 阈值(ms)
// 全局 HTTP 客户端,复用连接
private final HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
// 存储每次成功请求的延迟(ns)
private final List<Long> latencies = Collections.synchronizedList(new ArrayList<>());
@BeforeAll
void warmUp() throws Exception {
System.out.println("=== Warming up ===");
for (int i = 0; i < WARMUP_COUNT; i++) {
callApi();
}
}
/**
* 重复执行 50 次正式测试,每次测量 API 请求耗时
*/
@RepeatedTest(value = TEST_COUNT, name = "API请求 {currentRepetition}/{totalRepetitions}")
void singleRequestLatency() throws Exception {
long start = System.nanoTime();
HttpResponse<String> response = callApi();
long latencyNanos = System.nanoTime() - start;
// 只统计 2xx/3xx 的成功请求
if (response.statusCode() >= 200 && response.statusCode() < 400) {
latencies.add(latencyNanos);
System.out.printf("请求耗时: %.2f ms (status %d)%n",
latencyNanos / 1_000_000.0, response.statusCode());
} else {
System.err.printf("非成功状态码: %d%n", response.statusCode());
}
// 每次请求都检查状态码(功能回归的同时测性能)
assertEquals(200, response.statusCode(), "HTTP 状态码异常");
}
/**
* 重要 HTTP 请求方法
*/
private HttpResponse<String> callApi() throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_URL))
.timeout(Duration.ofSeconds(5))
.GET()
.build();
return client.send(request, HttpResponse.BodyHandlers.ofString());
}
@AfterAll
void reportAndAssert() {
assertFalse(latencies.isEmpty(), "未收集到任何成功的响应数据");
// 计算统计值
double avg = latencies.stream().mapToLong(Long::longValue).average().orElse(0);
long min = latencies.stream().mapToLong(Long::longValue).min().orElse(0);
long max = latencies.stream().mapToLong(Long::longValue).max().orElse(0);
double p99 = percentile(latencies, 99.0);
// 输出统计报告
System.out.println("\n===== API 延迟统计 =====");
System.out.printf("样本数: %d%n", latencies.size());
System.out.printf("平均: %.2f ms%n", avg / 1_000_000.0);
System.out.printf("最小: %.2f ms%n", min / 1_000_000.0);
System.out.printf("最大: %.2f ms%n", max / 1_000_000.0);
System.out.printf("P99: %.2f ms%n", p99 / 1_000_000.0);
// 性能断言:如果超出阈值则测试失败
assertTrue(avg / 1_000_000.0 < MAX_AVG_MS,
String.format("平均延迟 %.2f ms 超过阈值 %.2f ms", avg / 1_000_000.0, MAX_AVG_MS));
assertTrue(p99 / 1_000_000.0 < MAX_P99_MS,
String.format("P99 延迟 %.2f ms 超过阈值 %.2f ms", p99 / 1_000_000.0, MAX_P99_MS));
}
// 简单百分位数计算
private double percentile(List<Long> list, double percentile) {
List<Long> sorted = new ArrayList<>(list);
sorted.sort(Long::compareTo);
int index = (int) Math.ceil(percentile / 100.0 * sorted.size()) - 1;
return sorted.get(Math.max(index, 0));
}
}
代码:
@RepeatedTest 控制执行次数,每次重复都是一次独立的测试记录,便于定位偶发问题。
使用 List<Long> latencies 收集所有延迟数据,在 @AfterAll 统一分析。
预热 (@BeforeAll) 可避免 JIT 编译、DNS 缓存等初期原因干扰结果。
System.nanoTime() 提供纳秒精度,适合毫秒级延迟测量。
性能断言使测试直接“红/绿”,方便集成到 CI 中作为质量门禁。
3. Java 8兼容的HttpURLConnection版本
如果你的环境仍在使用 Java 8,也可以使用原生的 HttpURLConnection:
java
import org.junit.jupiter.api.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ApiPerformanceJava8Test {
private static final String API_URL = "https://jsonplaceholder.zmtests.com/posts/1";
private static final int WARMUP = 5;
private static final int ITERATIONS = 50;
private final List<Long> latencies = Collections.synchronizedList(new ArrayList<>());
@BeforeAll
void warmUp() throws Exception {
for (int i = 0; i < WARMUP; i++) doRequest();
}
@RepeatedTest(ITERATIONS)
void testLatency() throws Exception {
long start = System.nanoTime();
int status = doRequest();
long latency = System.nanoTime() - start;
latencies.add(latency);
assertEquals(200, status);
}
private int doRequest() throws Exception {
URL url = new URL(API_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
conn.setReadTimeout(3000);
conn.setRequestMethod("GET");
int status = conn.getResponseCode();
// 消耗响应体,保证连接可复用
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
while (br.readLine() != null) { /* 读空 */ }
}
conn.disconnect();
return status;
}
@AfterAll
void report() {
// ...统计和断言思路同上,略...
}
}
4. 运行方式
IDE:直接右键类名 → Run 'ApiPerformanceTest'。
命令行(Maven):
bash
mvn test -Dtest=ApiPerformanceTest
自定义被测接口:只需修改 API_URL 常量,或通过系统属性 ${api.url} 动态注入。
5. 注意事项
JUnit 是单用户串行测试:得到的是单线程下的接口延迟,可用于开发阶段的性能回归检测,但不代表高并发下的真实承载能力。如需压力测试,请使用 JMeter、Gatling 等专业工具。
统计有效性:建议预热至少5次,正式测试30~50次以上,避免个别极端值影响决定。
灵活性:如果不想用 @RepeatedTest,可以在一个 @Test 方法内写 for 循环,思路完全相同,但报告会只显示一个测试用例。
超时控制:生产代码中必须设置连接超时和读取超时,防止网络问题导致测试卡死。
将性能验收直接融入单元/集成测试,每次提交都能自动证实接口延迟是不是在约定范围内,及时发现由代码变更或环境问题引起的性能劣化。