项目的灵感来自一款叫做JMH的基准测试框架。
JMH是代码微基准测试的工具套件
系统在特定负载的情况下,相应时间和稳定性的表现情况。
1.系统:自己开发的程序,测试反映出我们开发程序的质量好坏。
2.负载:单位时间内客户请求的数量。
3.相应时间:客户从发起请求到接收到成功或失败响应的时间。
4.稳定性:指任意时间,响应时间的波动情况,波动越小的系统越好。
Windows环境下的IntelliJ IDEA
Java语言
注解,反射,集合,接口,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);
}
结果:
看到这个结果时应该就能明白性能测试框架的重要性了。
猜测可能影响性能的因素:
总结一下JMH大致是如何来建立框架的:
JMH:利用注解来完成配置:要测试的类和方法通过注解标注出来,每次的实验组次数通过注解配置,进行多少次试验也是通过注解配置。
所以重点是书写注解的过程,根据这些因素加上已经有的JMH框架,我们就可以自己写一个性能测试框架:
语法:
定义注解:
@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(反射)来获取信息
注解的适用场景:(把代码逻辑转成配置逻辑)
修改的代码逻辑的成本一定高于修改配置逻辑的成本
为什么直接用配置呢:因为有些配置和代码是强相关的
加载一个类 :如何找到这个类所在目录?
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包不在同一个目录下,需要另一个方法。
拿到类的名称后就可以获取类的实例,通过反射获取
//生成一个文档对象
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();
}
直接来看一下预热和不预热的结果比较,上面的图就是预热了的输出报表,下面给出没有预热的输出报表:
可以看到测试出来的差别,所以预热还是很有必要的。
预热代码:
用
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);
}