1. 程式人生 > >spring單元測試集錦

spring單元測試集錦

參考資料

普通查詢效能測試

本測試目的在於摸清單純的資料庫查詢的效能以及併發數量。
測試程式碼為java程式碼呼叫儲存過程然後成功返回查詢資料

儲存過程:

CREATE OR REPLACE FUNCTION "sp_common_region_getRegionPath"("id" int4)
  RETURNS varchar
AS $BODY$
declare tmpId integer;
  declare needGoing boolean;
  declare loopIndex integer;
  declare selected_result record;
  declare json_result varchar;
  declare tmp_array json[];
  declare real_array json[];
  declare tmp_json_obj json;
  declare arr_length integer;
begin

  tmpId:=id;
  needGoing:=true;
  loopIndex:=0;

  tmp_array:=array[]::json[];
  real_array:=array[]::json[];

  perform * from common_region cr where cr.id=tmpId;
  <<labelLoop4Lv1>>
  while needGoing loop
    if FOUND then
      needGoing:=true;
      <<labelLoop4Lv2>>
      for selected_result in select * from common_region cr where cr.id=tmpId loop


        select row_to_json(cr) into tmp_json_obj from common_region cr where cr.id=tmpId;
        --       raise notice 'tmp_json_obj is:%',tmp_json_obj;
        tmp_array:=array_prepend(tmp_json_obj,tmp_array);
        tmpId:=selected_result.parent_id;
        loopIndex:=loopIndex+1;
      end loop labelLoop4Lv2;
    else
      needGoing:=false;
      raise notice '注意,這裡沒有發現任何記錄。';
      if loopIndex=0 then
        return '';
      else
        exit labelLoop4Lv1;
      end if ;

    end if;
  end loop labelLoop4Lv1;


  return array_to_json(tmp_array);
end;
$BODY$
LANGUAGE 'plpgsql' VOLATILE
;

java端呼叫程式碼:


@Service
public class RegionService {

    @Autowired
    JdbcTemplate jdbcTemplate;

    public List<Region> getPath(Integer id){
        int theParamIndex=0;
        List<Region> paths=new ArrayList<>();
        paths=jdbcTemplate.execute(new CallableStatementCreator() {
            @Override
            public CallableStatement createCallableStatement(Connection con) throws SQLException {

                String sql="{ call \"sp_common_region_getRegionPath\"(?,null)}";
                sql="{ call \"sp_common_region_getRegionPath\"(?)}";
                CallableStatement st=con.prepareCall(sql);
                int paraIndex=1;
                st.setInt(paraIndex,id);
                return st;
            }
        },new CallableStatementCallback<List<Region>>(){
            @Override
            public List<Region> doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
                List<Region> res=new ArrayList<>();
                cs.execute();
                ResultSet rs=(ResultSet)cs.getResultSet();
                if (rs.next()){
                    String json_str=rs.getString(1);
                    if(ValidateUtils.isEmpty(json_str)){
                        return res;
                    }
                    res=JSONObject.parseArray(json_str,Region.class);
                }
                rs.close();
                cs.getConnection().setAutoCommit(true);
                return res;
            }
        });
        return paths;

    }
}


/**
* @author MR white
* @version 2.00
*/
public class Region {

public   Long  id=0L;
public void setId(Long id){
this.id=id;
}
public Long getId(){
return this.id;
}
public   Long  parent_id=0L;
public void setParent_id(Long parent_id){
this.parent_id=parent_id;
}
public Long getParent_id(){
return this.parent_id;
}
public   String  name="";
public void setName(String name){
this.name=name;
}
public String getName(){
return this.name;
}
public   Integer  level=0;
public void setLevel(Integer level){
this.level=level;
}
public Integer getLevel(){
return this.level;
}
public   String  code="";
public void setCode(String code){
this.code=code;
}
public String getCode(){
return this.code;
}
public   String  pingyin="";
public void setPingyin(String pingyin){
this.pingyin=pingyin;
}
public String getPingyin(){
return this.pingyin;
}
public   String  name_en="";
public void setName_en(String name_en){
this.name_en=name_en;
}
public String getName_en(){
return this.name_en;
}


public void setNULL(){
this.id=null;
this.parent_id=null;
this.name=null;
this.level=null;
this.code=null;
this.pingyin=null;
this.name_en=null;
}

public void resetDefaultVal(){
this.id=0L;
this.parent_id=0L;
this.name="";
this.level=0;
this.code="";
this.pingyin="";
this.name_en="";
}
}

java端測試程式碼:


public class RegionTester extends BaseTest {
    @Autowired
    private RegionService regionService;
    @Test
    public void singleThreadGetPath(){
        List<Region> list=regionService.getPath(5600);
        System.out.println(JSONObject.toJSONString(list));
    }

    @Test
    public synchronized void multiThreadGetPath() throws InterruptedException {
        runMultiThreadTest(250, new Runnable() {
            @Override
            public void run() {
                Double rnd = (Math.random() * 10000);
                int rndId = rnd.intValue() % 5000;
                List<Region> list = regionService.getPath(rndId);
                System.out.println(JSONObject.toJSONString(list));
            }
        });

    }

}

注意,basetest是經過定製的,為了能夠同時執行多個執行緒而得到結果,加了一點東西,程式碼如下:

package main;


import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration({"classpath:spring-mvc.xml","classpath:spring-mybatis.xml"})
@ContextConfiguration(locations={
        "classpath*:applicationContext.xml",
        "classpath*:spring/spring-mvc.xml"}
        )
public class BaseTest {
    Date beginDate;
    Long begin=null;
    Long end=null;
    Date endDate=null;
    @Before
    public void init() {
        //--初始化spring上下文
        System.out.println("初始化spring上下文中......");
        //在執行測試之前的業務程式碼
        beginDate = new Date();
        begin = beginDate.getTime();
        System.out.println("任務開始時間:" + beginDate);
    }
    @After
    public void after() {
        //在測試完成之後的業務程式碼
        endDate = new Date();
        end = endDate.getTime();
        System.out.println("任務結束時間:" + endDate + "");
        System.out.println("任務話費時間:" + (end - begin) + "毫秒");
    }

    public void runMultiThreadTest(int runThreadCount,Runnable runnable){
        {
            final int threadSize=runThreadCount;
            ExecutorService executor= Executors.newFixedThreadPool(threadSize);
            final AtomicInteger lockCount=new AtomicInteger(threadSize);
            final AtomicInteger successCount=new AtomicInteger(0);
            try {
                for (int i = 0; i < threadSize; i++) {
                    final int theThreadNumber = i;
                    executor.submit(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                runnable.run();
                                successCount.incrementAndGet();
                            }
                            catch (Exception ed){
                                ed.printStackTrace();
                            }
                            finally {
                                lockCount.decrementAndGet();
                            }
                        }
                    });
                }



                while(true){
                    synchronized (this){
                        if(lockCount.intValue()>0){
                            ;
                        }
                        else{
                            break;
                        }
                    }
                }
                System.out.println("共執行執行緒"+threadSize+"個,成功執行執行緒:"+successCount.get()+"個");
            }
            catch (Exception ed){
                ed.printStackTrace();
            }
        }
    }
}

單執行緒的測試就算了。。測不出什麼東西。現在直接執行多執行緒的測試方法,然後可以看到。。
一堆報錯。

在這裡插入圖片描述

在這裡插入圖片描述

可以看到。。成功的執行緒就只有10個。。而我們的併發數量是250,可見250這個併發也是很恐怖的—不過引起這個的原因是druid的資料庫連線池的最大數量設定為10,下面我們設定一下併發為50然後再看看結果:
啟動時候留意一下pgadmin4,檢視一下資料庫連線,可以發現,
在這裡插入圖片描述

除了第一個是pgadmin拿來連線之外,其他的在資料庫連線池初始化的時候就已經事先獲得連線了。

而程式碼執行結果為:

在這裡插入圖片描述

在這裡插入圖片描述

還是會報錯,拿不到連線,不過併發成功數量已經升到66個了,而不是50個----按照邏輯成功的應該是50的,最多。。不過,每個執行緒獲取連線的時候估計會先等幾毫米,假如這時候一個任務查詢成功釋放連線的話,那麼也會獲得連線從而執行程式的。

對了,附加執行完成之後的pgadmin的監控圖:
在這裡插入圖片描述

可見程式結束之後連線都釋放了。

接下來進行多次測試,務求獲得比較準確的效能概況。

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

併發太高的話無論如何一些請求都是會出錯的,這樣不行。
無論如何,客戶端發起的請求都必須完成,無論是250同時來的請求還是1000個,單鑑於單機的硬體都是有限的,無法無限提升連線數量,所以,下面提出嘗試一種解決方案,就是用阻塞佇列進行處理。

阻塞佇列執行任務效能測試

好了,併發數量太大的話多執行緒的執行都成問題了,現在改為阻塞佇列來試試,下面要改改baseTest的程式碼,新增阻塞佇列的測試方法。

package main;


import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration({"classpath:spring-mvc.xml","classpath:spring-mybatis.xml"})
@ContextConfiguration(locations={
        "classpath*:applicationContext.xml",
        "classpath*:spring/spring-mvc.xml"}
        )
public class BaseTest {
    Date beginDate;
    Long begin=null;
    Long end=null;
    Date endDate=null;
    @Before
    public void init() {
        //--初始化spring上下文
        System.out.println("初始化spring上下文中......");
        //在執行測試之前的業務程式碼
        beginDate = new Date();
        begin = beginDate.getTime();
        System.out.println("任務開始時間:" + beginDate);
    }
    @After
    public void after() {
        //在測試完成之後的業務程式碼
        endDate = new Date();
        end = endDate.getTime();
        System.out.println("任務結束時間:" + endDate + "");
        System.out.println("任務話費時間:" + (end - begin) + "毫秒");
    }

    /***用於測試最大併發***/
    public void runMultiThreadTest(int runThreadCount,Runnable runnable){
        {
            final int threadSize=runThreadCount;
            ExecutorService executor= Executors.newFixedThreadPool(threadSize);
            final AtomicInteger lockCount=new AtomicInteger(threadSize);
            final AtomicInteger successCount=new AtomicInteger(0);
            try {
                for (int i = 0; i < threadSize; i++) {
                    final int theThreadNumber = i;
                    executor.submit(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                runnable.run();
                                successCount.incrementAndGet();
                            }
                            catch (Exception ed){
                                ed.printStackTrace();
                            }
                            finally {
                                lockCount.decrementAndGet();
                            }
                        }
                    });
                }



                while(true){
                    synchronized (this){
                        if(lockCount.intValue()>0){
                            ;
                        }
                        else{
                            break;
                        }
                    }
                }
                System.out.println("共執行執行緒"+threadSize+"個,成功執行執行緒:"+successCount.get()+"個");
            }
            catch (Exception ed){
                ed.printStackTrace();
            }
        }
    }

    /***用於測試任務阻塞的任務佇列執行下的效能***/
    public void runMultiThreadByBlockQueue(int threadCount,TaskProducer producer,TaskConsumer consumer){
        final LinkedBlockingQueue<TaskOutLine> queue=new LinkedBlockingQueue<>(threadCount);
        final int threadSize=threadCount;
        ExecutorService executor= Executors.newFixedThreadPool(threadSize);
        final AtomicInteger lockCount=new AtomicInteger(threadSize);
        final AtomicInteger successCount=new AtomicInteger(0);
        try {
            /***執行緒池同時產生任務佇列***/

            for (int i = 0; i < threadSize; i++) {
                final int theThreadNumber = i;
                TaskOutLine tmpOutLine=new TaskOutLine();
                tmpOutLine.taskIndex=theThreadNumber;
                executor.submit(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            tmpOutLine.taskData=producer.produce();
                            queue.put(tmpOutLine);
                        }
                        catch (Exception ed){
                            ed.printStackTrace();
                        }
                        finally {

                        }
                    }
                });
            }
            /***另起一個執行緒用於消費佇列**/
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (lockCount.get()>0){
                        try{
                            TaskOutLine currentObj=queue.take();
                            consumer.consume(currentObj);
                            successCount.incrementAndGet();
                        }
                        catch (Exception ed){

                        }
                        finally {
                            lockCount.decrementAndGet();
                        }


                    }

                }
            }).start();



            while(true){
                synchronized (this){
                    if(lockCount.intValue()>0){
                        ;
                    }
                    else{

                        break;
                    }
                }
            }
            System.out.println("共執行執行緒"+threadSize+"個,成功執行執行緒:"+successCount.get()+"個");
        }
        catch (Exception ed){
            ed.printStackTrace();
        }


    }


    public interface TaskProducer{
        public Object produce();
    }
    public interface TaskConsumer{
        public void consume(TaskOutLine taskOutLine);
    }

    public class TaskOutLine{
        public int taskIndex=0;
        public Object taskData=new Object();

        public int getTaskIndex() {
            return taskIndex;
        }

        public void setTaskIndex(int taskIndex) {
            this.taskIndex = taskIndex;
        }

        public Object getTaskData() {
            return taskData;
        }

        public void setTaskData(Object taskData) {
            this.taskData = taskData;
        }
    }
}

然後在測試檔案新增程式碼:

    @Test
    public synchronized void multiThreadGetPathByQueue() throws InterruptedException {

        runMultiThreadByBlockQueue(250, new TaskProducer() {
            @Override
            public Object produce() {
                Double rnd = (Math.random() * 10000);
                Integer rndId = rnd.intValue() % 5000;
                return rndId;
            }
        }, new TaskConsumer() {
            @Override
            public void consume(TaskOutLine outLine) {

                TaskOutLine resOutLine=outLine;
                Integer rndId=(Integer)resOutLine.taskData;
                List<Region> list = regionService.getPath(rndId);
                System.out.println("任務序號:"+outLine.taskIndex);
                System.out.println(JSONObject.toJSONString(list));
            }
        });

    }

好了,多次執行看看效能如何:

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

可以看到,併發再多也是能夠處理完畢的,就是耗費的時間有點多。

要5到6秒。按照第一種效能的測試來看。。50個併發的耗時是500ms到600ms,那麼,一批一批完成250個任務的話,時間應該在2500ms到3000ms,所以阻塞佇列可以處理完所有請求,防止併發過多而崩潰,但並不是效能的最優解。

下面將提出效能更好的方案。

任務分批提交

還記得執行緒池的設定嗎?執行緒池可以設定最大執行緒執行數量的,也就是說,我們可以修改成為每次可以處理的最大併發數,那麼修改一下程式碼:

baseTest

ount, int threadPoolSize, Runnable runnable){
        {
            final int threadSize= totalThreadCount;
            ExecutorService executor= Executors.newFixedThreadPool(threadPoolSize);
            final AtomicInteger lockCount=new AtomicInteger(threadSize);
            final AtomicInteger successCount=new AtomicInteger(0);
            try {
                for (int i = 0; i < threadSize; i++) {
                    final int theThreadNumber = i;
                    executor.submit(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                runnable.run();
                                successCount.incrementAndGet();
                            }
                            catch (Exception ed){
                                ed.printStackTrace();
                            }
                            finally {
                                lockCount.decrementAndGet();
                            }
                        }
                    });
                }



                while(true){
                    synchronized (this){
                        if(lockCount.intValue()>0){
                            ;
                        }
                        else{
                            break;
                        }
                    }
                }
                System.out.println("共執行執行緒"+threadSize+"個,成功執行執行緒:"+successCount.get()+"個");
            }
            catch (Exception ed){
                ed.printStackTrace();
            }
        }
    }

然後,呼叫例子是這樣的:


    @Test
    public synchronized void multiThreadGetPath() throws InterruptedException {
        runMultiThreadTest(250,25, new Runnable() {
            @Override
            public void run() {
                Double rnd = (Math.random() * 10000);
                int rndId = rnd.intValue() % 5000;
                List<Region> list = regionService.getPath(rndId);
                System.out.println(JSONObject.toJSONString(list));
            }
        });

    }

我們分別設定執行緒池的最大數量為25,30,40,觀察一下效率變化。

最大執行緒數量25:

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

最大執行緒數量30:
對了,在baseTest裡面新增一下說明,例如:
在這裡插入圖片描述

這樣就能得到最大執行緒數量了。

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

40的如下:

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

嗯。。。耗費時間=應用伺服器耗時+資料庫伺服器耗時+資料傳輸耗時

即使應用伺服器已經開多了執行緒不過時間看來沒有多大的優化,估計已經到了資料庫伺服器的極限了。