做性能的同学一定遇到过这样的场景:应用级别的性能测试发现一个操作的响应时间很长,然后要花费很多时间去逐级排查,最后却发现罪魁祸首是代码中某个实现低效的底层算法。这种自上而下的逐级排查定位的方法,效率通常都很低,代价也很高。所以,我们就需要在项目早期,对一些关键算法进行代码级别的性能测试,以防止此类在代码层面就可以被发现的性能问题,遗留到最后的系统性能测试阶段才被发现。但是,从实际执行的层面来讲,代码级性能测试并不存在严格意义上的测试工具,通常的做法是:改造现有的单元测试框架。
而最常使用的改造方法是:
这里之所以采用执行 n 次的方式,是因为函数执行时间往往是毫秒级的,单次执行的误差会比较大,所以采用多次执行取平均值的做法。
那么有没有现成的这样的测试工具呢?
当然也是有的,比如今天我们介绍的主角-- ContiPerf。
ContiPerf 是一个轻量级的测试工具,基于JUnit 4 开发,可用于效率测试等。可以指定在线程数量和执行次数,通过限制最大时间和平均执行时间来进行性能测试。
官网地址:https://sourceforge.net/p/contiperf/wiki/Home
接下来我们一起来实践一个例子,
首先,加入 pom 依赖包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--引入 ContiPerf 测试工具-->
<dependency>
<groupId>org.databene</groupId>
<artifactId>contiperf</artifactId>
<version>2.3.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
这里为了演示,编写了一个简单的测试接口: UnitTestService.java
/**
* 测试接口类
* @author zuozewei
*
*/
public interface UnitTestService {
public String process(String msg);
}
实现类:UnitTestServiceImpl.java
@Service
public class UnitTestServiceImpl implements UnitTestService {
/**
* 为了测试,这里直接返回传入的值
*/
@Override
public String process(String msg) {
// TODO Auto-generated method stub
return msg;
}
}
编写 UnitTestServiceTest 测试类,进入 ContiPerfRule。
/**
* 编写接口性能测试类
* @author zuozewei
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest //SpringBootTest 是springboot 用于测试的注解,可指定启动类或者测试环境等,这里直接默认。
public class UnitTestServiceTest {
@Autowired
UnitTestService testService;
// 引入 ContiPerf 进行性能测试
@Rule
public ContiPerfRule contiPerfRule = new ContiPerfRule();
@Test
@PerfTest(invocations = 10000,threads = 100) //100个线程 执行10000次
public void test() {
String msg = "this is a test";
String result = testService.process(msg);
//断言 是否和预期一致
Assert.assertEquals(msg,result);
}
}
注意: @Rule 是J unit 提供的一个扩展接口注解,其接口类为:org.junit.rules.MethodRule,注意在 Junit5 中,已经被 TestRule 所替代了。也可以通过对类指定 @PerfTest 和 @Required,表示类中方法的默认设置。 @PerfTest注解:
@Required注解:
运行测试,控制台会生成结果:
com.zuozewei.springbootcontiperfdemo.service.UnitTestServiceTest.test
samples: 10000
max: 331
average: 33.3522
median: 30
同时访问:target/contiperf-report/index.html,会生成图表:
注意:图表需要访问外国网站才能显示 图表中的指标:
这里主要是对 Junit 和 ContiPerf 的使用简单的示例,在单元测试阶段的时候考虑做这种代码级性能测试,肯定会提高 ROI(投入产出比)的,而且代价非常小,希望本文对各位同学都能有所启发。
示例代码:
https://github.com/7DGroup/JMeter-examples/tree/master/contiperf/springboot-contiperf-demo
参考资料: [1]:https://sourceforge.net/p/contiperf/wiki/Home [2]:软件测试52讲 茹炳晟