1.项目简介:
描述一个程序的好坏可以很好的帮助我们去优化程序,提高代码水平。在比较有相同功能的方法时,我们通常会在特定的负载下,看系统的响应时间和表现。
所以以基于JMH基准测试框架为灵感,编写了一款简单的测试框架“看谁跑的快”
2.项目灵感
在学习String和StringBuffer的时候,String使用“+”号连接字符串效率比StringBuffer的apppend()的效率要低很多。嗯…怎么证明?
而在学习排序算法的时候,直到Java自带的Arrays.Sort()的效率非常高,所以就想知道自己的排序算法和自带的排序算法的区别。
由此,引出了自己想写的项目
3.项目思路
- 测试用例准备
1.首先高中物理在进行试验的时候,我们要进行多组试验,而且要控制变量,反应在代码中,我们 肯定不能只测试一组数据,而是一定要多测试,达到减小误差的效果,为了实现配置可修改
通过自定义注解的方式配置试验组和每组要测试的数目
2.要测试的类都放到一个com.test.Cases包下然后进行并且要测试的类都要继承一个接口口, Case(只代表一个标记而已,不定义任何方法)
3.要测试的方法使用自定义注解BenchMark打上标记
-
测试用例的加载
1.定义一个类用于实例化测试用例类
逻辑:1.获取com.test.Cases所在的绝对路径,
2.获取Cases文件操作类
3.获取类名
4.通过反射来获取类的实例对象,并看其是否实现了Case接口,只有实现Case的类才是 要测试的类,将其加入到ArrayList中
2.运行测试方法,进行测试
逻辑:1.通过步骤1,已经得到了存放Case实例的集合
2.通过Case 类对象获取Method,
3.通过Metod,获取方法注解BenchMark,只有被@BenchMark方法注解过的类才可以 进行测试
4.通过Metod,获取配置信息的注解,并获取内部的值
5.通过invoke进行注解
4.目录结构
[外链图片转存失败(img-t5GQH13T-1563706700726)(D:\知识点总结\捕获.PNG)]
5.代码实现
自定义注解
参数配置
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Measurement {
int group() default 5;
int iteration() default 10;
}
测试类的标注
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BenchMark {
}
测试用例的自动加载
public class CaseLoader {
public CaseRunner load() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
String pkg="com\\test\\Cases";
String pkgDot="com.test.Cases";
//通过反射和类加载器拿到文件的绝对路径
Enumeration<URL> url = this.getClass().getClassLoader().getResources(pkg);
List<String> caseName=new ArrayList<String>();
while(url.hasMoreElements()){
URL url1=url.nextElement();
//获取路径类型
String protocol = url1.getProtocol();
if(!"file".equals(protocol)){
continue;
}
//将路径进行解码操作
String name1=URLDecoder.decode(url1.getPath(),"UTF-8");
//获取文件操作类
File file=new File(name1);
//获取文件下的列表
File[] files=file.listFiles();
for (File i:files){
//如果这个文件是目录直接跳出
if(i.isDirectory()){
continue;
}
//获取文件名
String name=i.getName();
if(i.isFile()){
//将文件名存入caseName中
caseName.add(name.substring(0,name.length()-6));
}
}
}
//获取实例对象
List<Case> listCase=new ArrayList<Case>();
for(String i:caseName){
if(hasIntfCase(i,Case.class)){
Class<?> cls=Class.forName(pkgDot+"."+i);
listCase.add((Case) cls.newInstance());
}
}
return new CaseRunner(listCase);
}
private boolean hasIntfCase(String i, Class<Case> caseClass) throws ClassNotFoundException {
Class<?> cls=Class.forName("com.test.Cases."+i);
Class<?>[] interfaces = cls.getInterfaces();
for(Class<?> j:interfaces)
if(j==caseClass){
return true;
}
return false;
}
}
测试:
public class CaseRunner {
private List<Case> list;
public CaseRunner(List<Case> list) {
this.list = list;
}
public void run() throws InvocationTargetException, IllegalAccessException {
//初始化测试参数
int iteration=10,group=5;
//遍历Case用例,进行测试
for(Case cases:list){
//获取Class对象
Class<Case> cls= (Class<Case>) cases.getClass();
//获取类级别的配置参数
Measurement measurement = cls.getAnnotation(Measurement.class);
int it=iteration,g=group;
if(measurement!=null) {
it = measurement.iteration();
g = measurement.group();
}
//获取类中所有方法
Method[] methods=cls.getMethods();
run(methods,cases,it,g);
}
private void run(Method[] methods,Case cases,int it,int g ) throws InvocationTargetException, IllegalAccessException {
for(Method method:methods) {
BenchMark benchMark1 = method.getAnnotation(BenchMark.class);
if (benchMark1 == null) {
continue;
}
Measurement measurement1 = method.getAnnotation(Measurement.class);
if (measurement1 != null) {
it = measurement1.iteration();
g = measurement1.group();
}
for (int i = 0; i < g; i++) {
System.out.println("第" + i + "组");
long start = System.currentTimeMillis();
for (int j = 0; j < it; j++) {
method.invoke(cases);
}
long end = System.currentTimeMillis();
System.out.println("花费" + (end - start + 1)+"ms");
}
}
}
}
com.test.Cases包下如果有需要测试的类,将待测试类实现Cases,然后要测试方法使用@BenchMark标注,并初始化参数@Measurement(group=xxx,iteration=xxx)
6.总结
完成了基本测试功能,可以自定义测试参数,对各类的代码进行测试,
项目总的来说还有许多不足的地方,比如没有实现预热功能,不能项目启动时,性能没有达到最好,没有实现存jar包导入项目,会继续跟进,完善项目