项目-懒人测试框架
-项目功能:
简单便利的测试相关方法的性能,比如说String的字符串相加和StringBuilder的add方法的性能**
- 技术栈:
1. 1.性能测试
关键字:
- 系统:自己开发的程序 - 生活中的一项实体系统
- 负载:单位时间内客户请求的数量
- 响应时间:客户从发起请求到接收到成功的响应时间 - 对我们软件系统来说,通信本身也会耗时
- 稳定性: 指任意时间,响应时间的波动情况,只有波动越小的系统,就是好系统。
1. 2.注解的使用
①我们的代码中出现了太多的魔法数字,注入 10 组,每组 1000 次这样的代码,这类数字应该是配置出来的而不是写死在代码中的,因此我们通过Measurement注解来配置。
②通过Benchmark注释来标注类中哪些方法需要测试
2. 3.反射的使用 :
①在CaseRunner类中利用反射取出实现Case接口的类、
②反射调用类中方法
③反射获取注解参数
1. 4.io的使用
①根据一个固定类,找到类加载器
②根据类加载器找到类文件的路径
③扫描路径中的所有类文件,并用字符串截取得到类名称,在通过实现Case接口条件判断出哪些是需要测试的类。
1. 5.集合的使用
①得到类的实例放在list集合下
②配合泛型添加集合
创建一个naven项目 - 测试字符串相加String 和 StringBilder 的性能
最简化版本:
public class StringConcat {
private static String addString() {
String a = "";
for (int i = 0; i < 10; i++) {
a += i;
}
return a;
}
private static String addStringBuilder() {
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();
addStringBuilder();
long t2 = System.nanoTime();
addString();
long t3 = System.nanoTime();
// 单位是纳秒
System.out.println("StringBuilder + 耗时:" + (t2 - t1));
System.out.println("String + 耗时:" + (t3 - t2));
}
}
查询结果: 我们交换执行顺序后发现俩个方法执行的结果差别很大
猜测可能影响性能的因素:
- 1)执行时间过短
- 2)次数太少(实验次数)
- 3)优化原因/AOT(编译器)静态 /JIT(编译器)动态
- 4)预热,机器预热,java预热
- 5)其他原因
所以我们把一些影响因素都进行调整,并且优化代码
项目目录:
2.1执行次数增大: 通过数据可以基本反映出StringBiluder的性能快与String
2.2增加Measurement注解设置测试组数以及测试次数,通过反射获得注解信息。
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Measurement {
int iteration();
int Group();
}
2.3增加Benchmark注解 标识哪些方法是用来被测试的方法
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Benchmark {
}
2.4增加Case接口,相当于指示器,待测试类实现此接口
package com.gxg;
//指示器,待测试类实现此接口
public interface Case {
}
2.5为方便客户,自动加载需要测试的case类,添加CaseLoader类
package com.gxg.CommUtils;
import com.gxg.Case;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
public class CaseLoader {
public CaseRunner load() throws ClassNotFoundException, IOException, IllegalAccessException, InstantiationException {
String pkgdot = "com.gxg.cases";
String pkg = "com/gxg/cases";
List<String> classNameList = new ArrayList<String>();
ClassLoader classLoader = this.getClass().getClassLoader();
Enumeration<URL> urls = classLoader.getResources(pkg);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (!url.getProtocol().equals("file")) {
//如果不是*.class文件,暂时不支持
continue;
}
//URL解码
String dirname = URLDecoder.decode(url.getPath(), "UTF-8");
File dir = new File(dirname);
if (!dir.isDirectory()) {
//不是目录
continue;
}
File[] files = dir.listFiles();
if (files == null) {
continue;
}
for (File file : files) {
String filename = file.getName();
String className = filename.substring(0, filename.length() - 6);
classNameList.add(className);
}
}
List<Case> caseList = new ArrayList<Case>();
//通过反射取出实现Case接口的类,就是需要测试的类
for (String className : classNameList) {
Class<?> cls = Class.forName(pkgdot + "." + className);
if (hasInterface(cls, Case.class)) {
caseList.add((Case) cls.newInstance());
}
}
return new CaseRunner(caseList);
}
private boolean hasInterface(Class<?> cls,Class<?> interfce){
Class<?>[] interf = cls.getInterfaces();
for(Class<?> i : interf){
if(i == interfce){
return true;
}
}
return false;
}
}
2.6具体实现类CaseRunner类
package com.gxg.CommUtils;
import com.gxg.Case;
import com.gxg.annotations.Benchmark;
import com.gxg.annotations.Measurement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
public class CaseRunner {
private static final int DEFAULT_ITERATIONS = 10;
private static final int DEFAULT_GROUP = 10;
private final List<Case> caseList;
public CaseRunner(List<Case> caseList){
this.caseList = caseList;
}
public void run(){
//找到对象中那些方法是需要测试的方法
for(Case bCase : caseList){
System.out.println(caseList);
int iterations = DEFAULT_ITERATIONS;
int group = DEFAULT_GROUP;
//先获取类级别的配置
Measurement classMeasuremennt = bCase.getClass().getAnnotation(Measurement.class);
if(classMeasuremennt != null){
iterations = classMeasuremennt.iteration();
group = classMeasuremennt.Group();
}
Method[] methods = bCase.getClass().getMethods();
for(Method method : methods){
Benchmark benchmark = method.getAnnotation(Benchmark.class);
if(benchmark == null){
continue;
}
Measurement methodmeasurement = method.getAnnotation(Measurement.class);
if(methodmeasurement != null){
iterations = methodmeasurement.iteration();
group = methodmeasurement.Group();
}
runCase(bCase,method,iterations,group);
}
}
}
private void runCase(Case bCase,Method method,int iterations,int group){
System.out.println(method.getName());
for(int i = 1; i <= group; i++){
System.out.println("第" + i + "次实验");
long t1 = System.nanoTime();
for(int j = 1; j < iterations; j++){
try {
method.invoke(bCase);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
long t2 = System.nanoTime();
System.out.println("耗时:" + (t2 - t1) + "纳秒");
}
}
}
2.7测试类StringConcatCase
package com.gxg.cases;
import com.gxg.Case;
import com.gxg.annotations.Benchmark;
import com.gxg.annotations.Measurement;
@Measurement(iteration = 5,Group = 5)
public class StringConcatCase implements Case {
@Benchmark
public static void addString(){
String str = "";
for(int i = 0; i < 10; i++){
str += i;
}
}
@Benchmark
public static void addStringBuilder(){
StringBuilder sb = new StringBuilder();
for(int i = 0; i < 10; i++){
sb.append(i);
}
}
}