以下简单介绍一下如何使用 JunitPerf 进行性能测试,JunitPerf 是基于 JUnit4 的一个单元性能测试插件,对于会远程调用 API 测试比较合适,如果想要比较 nanosecond 延迟的则需要使用JMH.
使用 JunitPerf 测试一些 SDK 的性能,个人觉得还是不错,而且比 JMeter 使用起来会简单一些,不需要写 JMeter 插件.
此例子假设使用 MAVEN 管理项目,所以在 POM 文件中添加:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>com.github.noconnor</groupId>
<artifactId>junitperf</artifactId>
<version>1.9.1</version>
</dependency>
假设你想衡量 DemoPerfService 类中的 getServiceId 方法:
public class DemoPerfService {
public String getServiceId(String userId){
return UUID.randomUUID().toString();
}
}
那么你可以构建如下的测试类:
public class DemoServiceTest {
@Rule
public JUnitPerfRule perfTestRule = new JUnitPerfRule();
DemoPerfService demoPerfService;
@Before
public void setupService(){
this.demoPerfService = new DemoPerfService();
}
@Test
@JUnitPerfTest(threads = 50,durationMs = 1200,warmUpMs = 100,maxExecutionsPerSecond = 110)
public void getServiceId_withoutTestRequirement() {
String result =demoPerfService.getServiceId("userid");
System.out.println(result);
Assert.assertNotNull(result);
}
直接运行就可以进行压力测试,默认的测试报告可以在 build/reports 目录下获取:
以下是对于测试类的几点说明:
Item | 定义说明 | Default 值或说明 |
---|---|---|
@Rule | 申明为 JUnit 的 Rule 类 | |
JUnitPerfRule | JUnitPerf 测试规则类 | |
@JunitPerfTest | 声明为性能测试方法 | |
threads | 测试使用的线程数 | |
durationMs | 测试持续时间 | |
warmUpMs | 测试热身时间 | 热身时间的测试数据不会计算进最后的测试结果 |
maxExecutionsPerSecond | 方法执行的上限 | RateLimiter,控制 TPS 上限 |
使用@JUnitPerfTestRequirement 可以给性能测试设置期望值,这个 annotation 的属性有:
属性 | 定义 |
---|---|
percentits | 设置例如 90%/95%/50% 响应时间的期望 |
executionsPerSec | 期望每秒执行测试 (TPS) |
allowedErrorPercentage | 允许错误比例 |
minLatency | 期望最小延时,如果实际最小延时超过这个数,则失败 |
maxLatency | 期望最大延时,如果实际最大延时超过这个,则失败 |
meanLatency | 期望中位数延时 |
下面是使用了 JUnitPerfTestRequirement 的一个测试方法,需要和@JUnitPerfTest一起使用:
具体代码如下例:
@Test
@JUnitPerfTest(threads = 50,durationMs = 1200,warmUpMs = 100,maxExecutionsPerSecond = 110)
@JUnitPerfTestRequirement(percentiles = "90:7,95:7,98:7,99:8", executionsPerSec = 10_000, allowedErrorPercentage = 0.10f)
public void getServiceId() {
String result =demoPerfService.getServiceId("userid");
System.out.println(result);
Assert.assertNotNull(result);
}
运行之后,如果发现没有满足 JUnitPerfTestRequirement 定义,则报错:
java.lang.AssertionError: Test throughput threshold not achieved
Expected: is <true>
but: was <false>
Expected :is <true>
是不是很简单!
JUnitPerf 有不同的测试报告,个人觉得 HTML 的测试报告比较实用,具体只需要:
@Rule
public JUnitPerfRule perfTestRule = new JUnitPerfRule(new HtmlReportGenerator("perf/report.html"));
public class DemoServiceTest {
@Rule
// public JUnitPerfRule perfTestRule = new JUnitPerfRule(new HtmlReportGenerator("perf/report.html"));
public JUnitPerfRule perfTestRule = new JUnitPerfRule();
DemoPerfService demoPerfService;
@Before
public void setupService(){
this.demoPerfService = new DemoPerfService();
}
@Test
@JUnitPerfTest(threads = 50,durationMs = 1200,warmUpMs = 100,maxExecutionsPerSecond = 110)
@JUnitPerfTestRequirement(percentiles = "90:7,95:7,98:7,99:8", executionsPerSec = 10_000, allowedErrorPercentage = 0.10f)
public void getServiceId() {
String result =demoPerfService.getServiceId("userid");
System.out.println(result);
Assert.assertNotNull(result);
}
@Test
@JUnitPerfTest(threads = 50,durationMs = 1200,warmUpMs = 100,maxExecutionsPerSecond = 110)
public void getServiceId_withoutTestRequirement() {
String result =demoPerfService.getServiceId("userid");
System.out.println(result);
Assert.assertNotNull(result);
}
}
最后可以再设定的目录中查看测试报告,测试报告和默认的 HTML 测试报告是一致的.
压力测试过程中,有时数据不能复用,举个例子来说,如果想测试完全没有访问 redis 缓存情况下,通过 userid 查询的 user 信息速度,那么压测的时候 userid 就不能复用,因为一旦访问了就会放入 redis 缓存而影响结果,这个可以通过使用其他的方法解决,比如曾今使用过 BlockingQueue 的方法进行过尝试,具体方法如下:
这样就解决了数据不能重复的方法,具体方法可以参考如下代码:
static BlockingQueue<String> distinctIdQueue ;
@Rule
public JUnitPerfRule perfTestRule =
new JUnitPerfRule(new HtmlReportGenerator("data/report_test.html"));
@BeforeClass
public static void setupQueue() throws IOException {
distinctIdQueue = new LinkedBlockingQueue<>();
Files.readAllLines(
Paths.get("data/userid.txt")
).parallelStream().forEach(
item-> {
try {
distinctIdQueue.put(item);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
);
}
@Test
@JUnitPerfTest(threads = 50,durationMs = 1200,warmUpMs = 100,maxExecutionsPerSecond = 110)
@JUnitPerfTestRequirement(percentiles = "90:7,95:7,98:7,99:8", executionsPerSec = 10_000, allowedErrorPercentage = 0.10f)
public void getServiceId() {
String uesrId = distinctIdQueue.take();
String result =demoPerfService.getServiceId(userId);
System.out.println(result);
Assert.assertNotNull(result);
}
}