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包導入項目,會繼續跟進,完善項目