看誰跑的快—性能測試框架

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章