之前寫過一個性能測試框架,只是針對單一的HTTP接口的測試,對於業務接口和非HTTP接口還無法適配,剛好前端時間工作中用到了,就更新了自己的測試框架,這次不再以請求爲基礎,而是以方法爲基礎,這樣就可以避免了單一性,有一個base類,然後其他的各種單一性請求在單獨寫一個適配類就好了,如果只是臨時用,直接重新實現base即可。下面分享:
package com.fun.frame.thead;
import com.fun.frame.SourceCode;
import com.fun.frame.excute.Concurrent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import static com.fun.utils.Time.getTimeStamp;
/**
* 多線程任務基類,可單獨使用
*/
public abstract class ThreadBase<T> extends SourceCode implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(ThreadBase.class);
/**
* 任務請求執行次數
*/
public int times;
/**
* 計數鎖
* <p>
* 會在concurrent類裏面根據線程數自動設定
* </p>
*/
CountDownLatch countDownLatch;
/**
* 用於設置訪問資源
*/
public T t;
public ThreadBase(T t) {
this();
this.t = t;
}
public ThreadBase() {
super();
}
/**
* groovy無法直接訪問t,所以寫了這個方法
*
* @return
*/
public String getT() {
return t.toString();
}
@Override
public void run() {
try {
before();
List<Long> t = new ArrayList<>();
long ss = getTimeStamp();
for (int i = 0; i < times; i++) {
long s = getTimeStamp();
doing();
long e = getTimeStamp();
t.add(e - s);
}
long ee = getTimeStamp();
logger.info("執行次數:{},總耗時:{}", times, ee - ss);
Concurrent.allTimes.addAll(t);
} catch (Exception e) {
logger.warn("執行任務失敗!", e);
} finally {
after();
if (countDownLatch != null)
countDownLatch.countDown();
}
}
/**
* 運行待測方法的之前的準備
*/
protected abstract void before();
/**
* 待測方法
*
* @throws Exception
*/
protected abstract void doing() throws Exception;
/**
* 運行待測方法後的處理
*/
protected abstract void after();
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
public void setTimes(int times) {
this.times = times;
}
}
下面是幾個實現過的基礎類:
package com.fun.frame.thead;
import com.fun.httpclient.ClientManage;
import com.fun.httpclient.FanLibrary;
import com.fun.httpclient.GCThread;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* http請求多線程類
*/
public class RequestThread extends ThreadBase {
static Logger logger = LoggerFactory.getLogger(RequestThread.class);
/**
* 請求
*/
public HttpRequestBase request;
/**
* 單請求多線程多次任務構造方法
*
* @param request 被執行的請求
* @param times 每個線程運行的次數
*/
public RequestThread(HttpRequestBase request, int times) {
this.request = request;
this.times = times;
}
@Override
public void before() {
request.setConfig(FanLibrary.requestConfig);
GCThread.starts();
}
@Override
protected void doing() throws Exception {
getResponse(request);
}
@Override
protected void after() {
GCThread.stop();
}
/**
* 多次執行某個請求,但是不記錄日誌,記錄方法用 loglong
* <p>此方法只適應與單個請求的重複請求,對於有業務聯繫的請求暫時不能適配</p>
*
* @param request 請求
* @throws IOException
*/
void getResponse(HttpRequestBase request) throws IOException {
CloseableHttpResponse response = ClientManage.httpsClient.execute(request);
String content = FanLibrary.getContent(response);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
logger.warn("響應狀態碼:{},響應內容:{}", content, response.getStatusLine());
if (response != null) response.close();
}
}
下面是數據庫的:
package com.fun.frame.thead;
import com.fun.interfaces.IMySqlBasic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
/**
* 數據庫多線程類
*/
public class QuerySqlThread extends ThreadBase {
private static Logger logger = LoggerFactory.getLogger(QuerySqlThread.class);
String sql;
IMySqlBasic base;
public QuerySqlThread(IMySqlBasic base, String sql, int times) {
this.times = times;
this.sql = sql;
this.base = base;
}
@Override
public void before() {
base.getConnection();
}
@Override
protected void doing() throws SQLException {
base.excuteQuerySql(sql);
}
@Override
protected void after() {
base.mySqlOver();
}
}
下面是concurrent類:
package com.fun.frame.excute;
import com.fun.bean.PerformanceResultBean;
import com.fun.frame.Save;
import com.fun.frame.SourceCode;
import com.fun.frame.thead.ThreadBase;
import com.fun.profile.Constant;
import com.fun.utils.Time;
import com.fun.utils.WriteRead;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Concurrent {
private static Logger logger = LoggerFactory.getLogger(Concurrent.class);
/**
* 線程任務
*/
public ThreadBase thread;
public List<ThreadBase> threads;
public int num;
public static Vector<Long> allTimes = new Vector<>();
ExecutorService executorService;
CountDownLatch countDownLatch;
/**
* @param thread 線程任務
* @param num 線程數
*/
public Concurrent(ThreadBase thread, int num) {
this(num);
this.thread = thread;
}
/**
* @param threads 線程組
*/
public Concurrent(List<ThreadBase> threads) {
this(threads.size());
this.threads = threads;
}
public Concurrent(int num) {
this.num = num;
executorService = Executors.newFixedThreadPool(num);
countDownLatch = new CountDownLatch(num);
}
/**
* 執行多線程任務
*/
public PerformanceResultBean start() {
long start = Time.getTimeStamp();
for (int i = 0; i < num; i++) {
ThreadBase thread = getThread(i);
thread.setCountDownLatch(countDownLatch);
executorService.execute(thread);
}
shutdownService(executorService, countDownLatch);
long end = Time.getTimeStamp();
logger.info("總計" + num + "個線程,共用時:" + Time.getTimeDiffer(start, end) + "秒!");
return over();
}
private static void shutdownService(ExecutorService executorService, CountDownLatch countDownLatch) {
try {
countDownLatch.await();
executorService.shutdown();
} catch (InterruptedException e) {
logger.warn("線程池關閉失敗!", e);
}
}
private PerformanceResultBean over() {
Save.saveLongList(allTimes, num);
return countQPS(num);
}
ThreadBase getThread(int i) {
if (threads == null) return thread;
return threads.get(i);
}
/**
* 計算結果
* <p>此結果僅供參考</p>
*
* @param name 線程數
*/
public static PerformanceResultBean countQPS(int name) {
List<String> strings = WriteRead.readTxtFileByLine(Constant.LONG_Path + name + Constant.FILE_TYPE_LOG);
int size = strings.size();
int sum = 0;
for (int i = 0; i < size; i++) {
int time = SourceCode.changeStringToInt(strings.get(i));
sum += time;
}
double v = 1000.0 * size * name / sum;
PerformanceResultBean performanceResultBean = new PerformanceResultBean(name, size, sum / size, v);
performanceResultBean.print();
return performanceResultBean;
}
}
redis實現類缺失,因爲沒有遇到需要單獨實現的需求。
關於用代碼還是用工具實現併發,我個人看法所有所長,單究其根本,必然是代碼勝於工具,原因如下:門檻高,適應性強;貼近開發,利於調優。性能測試,併發只是開始,只有一個好的開始才能進行性能數據分析,性能參數調優。所以不必拘泥於到底使用哪個工具那種語言,據我經驗來說:基本的測試需求都是能滿足的,只是實現的代價不同。
groovy是一種基於JVM的動態語言,我覺得最大的優勢有兩點,第一:於java兼容性非常好,大部分時候吧groovy的文件後綴改成java直接可以用,反之亦然。java的絕大部分庫,groovy都是可以直接拿來就用的。這還帶來了另外一個優點,學習成本低,非常低,直接上手沒問題,可以慢慢學習groovy不同於Java的語法;第二:編譯器支持變得更好,現在用的intellij的ide,總體來說已經比較好的支持groovy語言了,寫起代碼來也是比較順滑了,各種基於groovy的框架工具也比較溜,特別是Gradle構建工具,比Maven爽很多。----此段文字爲了撐字數強加的,與內容無關。
技術類文章精選
- java一行代碼打印心形
- Linux性能監控軟件netdata中文漢化版
- 接口測試代碼覆蓋率(jacoco)方案分享
- 性能測試框架
- 如何在Linux命令行界面愉快進行性能測試
- 圖解HTTP腦圖
- 將swagger文檔自動變成測試代碼
- 五行代碼構建靜態博客
- 基於java的直線型接口測試框架初探
非技術文章精選
- 爲什麼選擇軟件測試作爲職業道路?
- 寫給所有人的編程思維
- 自動化測試的問題所在
- 成爲優秀自動化測試工程師的7個步驟
- 手動測試存在的重要原因
- 成爲自動化測試的7種技能
- 功能測試與非功能測試
- 自動化和手動測試,保持平衡!
- 自動化測試生命週期