项目报告:
心路历程:
项目的灵感来自一款叫做JMH的基准测试框架。
JMH是代码微基准测试的工具套件
什么是性能测试
系统在特定负载的情况下,相应时间和稳定性的表现情况。
1.系统:自己开发的程序,测试反映出我们开发程序的质量好坏。
2.负载:单位时间内客户请求的数量。
3.相应时间:客户从发起请求到接收到成功或失败响应的时间。
4.稳定性:指任意时间,响应时间的波动情况,波动越小的系统越好。
开发环境:
Windows环境下的IntelliJ IDEA
项目语言:
Java语言
项目功能:
- 自动加载测试用例
- 通过接口标记待测试类
- 通过注解标记待测试方法
- 通过注解实现多级配置
- 编译器预热
- 用POI生成Excel报表
项目技术
注解,反射,集合,接口,Java POI
项目详解:
首先来阐述一下性能测试框架的必要性,如果没有一个良好的性能框架,检测代码的性能就会不可避免的出现误差,举个栗子:
字符串拼接 vs StringBuilder拼接:
public class MainFirst {
private static String testStringAdd() {
String s = " ";
for (int i = 0; i < 10; i++) {
s += i;
}
return s;
}
private static String testStringBuildrtAdd() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
sb.append(i);
}
return sb.toString();
}
public static void main(String[] args) {
long t1 = System.nanoTime();
testStringAdd();
long t2 = System.nanoTime();
testStringBuildrtAdd();
long t3 = System.nanoTime();
System.out.printf("字符串相加:%d%n",t2-t1);
System.out.printf("StringBuilder:%d%n",t3-t2);
}
}
结果:
如果将执行顺序换一下:
public static void main(String[] args) {
long t1 = System.nanoTime();
testStringBuildrtAdd();
long t2 = System.nanoTime();
testStringAdd();
long t3 = System.nanoTime();
System.out.printf("字符串相加:%d%n",t3-t2);
System.out.printf("StringBuilder:%d%n",t2-t1);
}
结果:
看到这个结果时应该就能明白性能测试框架的重要性了。
猜测可能影响性能的因素:
- 执行时间过短
- 实验次数太少
- 编译器自动优化代码:AOT编译器,JIT编译器自动优化
- 没有预热
总结一下JMH大致是如何来建立框架的:
JMH:利用注解来完成配置:要测试的类和方法通过注解标注出来,每次的实验组次数通过注解配置,进行多少次试验也是通过注解配置。
所以重点是书写注解的过程,根据这些因素加上已经有的JMH框架,我们就可以自己写一个性能测试框架:
性能测试框架化:
- 利用Benchmark注解标记出需要测试的方法
- 利用Measurement注解配置测试的一些相关配置(3级配置:默认+类级别+方法级别)
- 完成测试用例自动发现加载的过程
- 如何获取指定包下的所有类==>如何找到指定目录下的所有字节码文件
- 如何区分哪些类是需要测试的类(将要测试的类放在一个接口里)
- 如何定义注解:注解的三个阶段(编译,编译-运行,运行):这里使用了运行阶段的注解的获取方式(利用反射)
1.注解的使用:
语法:
定义注解:
@interface Measurement {
int iterations() default = 3; //默认值
}
使用注解:
@Measurement(iterations = 10);如果没有默认值,就一定要给出值,
或者:如果有默认值,则可以不给出值,有以下三种写法:
@Measurement 等同于@Measurement()等同于@Measurement(iterations = 3);
class Demo {
}
注解的分类:@RetentionPolicy:
-
在编译期间,文件变成*.class文件时,注解已经不存在了–SOURCE
-
在编译期间,文件变成*.class文件时,注解还存在,但是运行时不存在了–CLASS
1和2是利用注解处理器:Annotations Processor来获取信息 -
运行期间,注解始终存在(保存在了方法区的类的元信息中)–RUNTIME
3是利用Reflection(反射)来获取信息
注解的适用场景:(把代码逻辑转成配置逻辑)
修改的代码逻辑的成本一定高于修改配置逻辑的成本
为什么直接用配置呢:因为有些配置和代码是强相关的
2.自动加载测试用例:
加载一个类 :如何找到这个类所在目录?
ClassLoader classLoader = Main.class.getClassLoader();
Enumeration<URL> urls = classLoader.getResources("包名");
while(urls.hasMoreElements()){
URL url = urls.nextElement();
// System.out.println(url.getPath());//目录有中文时会出现乱码
//解决方法:System.out.println(URLDecoder.decode(url.getPath(), "UTF-8"));
// System.out.println(url.getProtocal());
//确定*.class
File dir = new File(URLDecoder.decode(url.getPath(),"UTF-8"));
if(!dir.isDirectory()){
continue;
}
File[] files = dir.listFiles();
if(files == null){
continue;
}
for(File file : files){
//严谨一点的话应该判断一下是不是java的字节码
String filename = file.getName();
Strign className = filename.substring(0,filename.length()-6);
//System.out.println(className);
//获取类的实例
Class<?> cls = Class.forName("包名"+ className);
//利用接口找出需要的class
Class<?>[] interfaces = cls.getInterfaces();
for(Class<?> interf : interfaces){
if(interf == Case.class){
System.out.println(className);
}
}
}
}
找到类后,确定类中有哪些 “ * . class ”文件
这里只能处理“*.class”的情况,不能处理打成jar包的情况,jar包不在同一个目录下,需要另一个方法。
拿到类的名称后就可以获取类的实例,通过反射获取
3.用POI生成Excel报表
//生成一个文档对象
HSSFWorkbook wb = new HSSFWorkbook();
//在文档中生成一个表单对象
HSSFSheet sheet = wb.createSheet("测试报表");
//在表单里创建第一行
HSSFRow row = sheet.createRow(0);
//设施样式
HSSFCellStyle style = wb.createCellStyle();
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
//生成一个列对象
HSSFCell cell = null;
//给出表格的头名称
String[] title = {"测试用例","测试次数","测试耗时"};
for (int j = 0; j < title.length; j++) {
//在第一行里创建列
cell = row.createCell(j);
//将元素填入第row行cell列里
cell.setCellValue(title[j]);
//设置样式
cell.setCellStyle(style);
}
//输出一个Excel文件
FileOutputStream outputStream = new FileOutputStream("E:/testBook.xls");
try {
//写入到文件中
wb.write(outputStream);
//关闭流
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
看一下报表结果:
4.系统预热
直接来看一下预热和不预热的结果比较,上面的图就是预热了的输出报表,下面给出没有预热的输出报表:
可以看到测试出来的差别,所以预热还是很有必要的。
预热代码:
用
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//系统预热
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface WarmUp {
int iterations() default 2000;
}
在执行测试用例之前进行预热:
//进行预热
int warm=1000 ;
WarmUp warmup=method.getAnnotation(WarmUp.class);
if(warmup!=null){
warm=warmup.iterations();
}
for(int w=0;w<warm;w++){
method.invoke(bCase);
}