結構圖
背景:
需要寫一個JMeter腳本來進行自動化測試,主要是通過接口調用一些東西,同時要對響應的數據進行處理,包括不限于錯誤信息的輸出。
1.SSE(摘錄)
SSE(Server-Sent Events)是一種基于HTTP協議、允許服務器主動向客戶端推送實時更新的技術?。它特別適用于單向數據流的實時場景,例如消息通知、AI對話流式響應等,通過保持長連接實現持續數據傳輸。
2.實現思路
2.1 用戶自定義變量組件
主要是用來統一更換和維護環境變量的,比如線上、線下環境host的切換
2.2 HTTP信息頭管理
設置https請求的信息頭,比如token,數據格式等等
2.3 CSV 數據文件設置
參數如下圖設置
變量名稱:appId,query,appName,needFiles,file
對應vars中的變量,變量值是根據分隔帶JMeter自動處理的,變量名稱 數量和分隔后的變量值 數量不對應也沒影響,兩者缺少的值會忽略或者設置為空值
2.4 HTTP請求組件
需要設置消息體數據,請求url等
下面三個插件,歸類到HTTP請求的子目錄下:如圖
2.4.1 JSR223 預處理程序
主要來處理一下請求中消息體數據中的一個參數,fileParam
根據csv文件中的標記來確定fileParam的具體值,如下代碼
import org.json.JSONObject;
import org.json.JSONArray;
import java.util.ArrayList;//判斷是否需要 files 參數
private Boolean needFiles(String str) {if("1".equals(str)) {return true;}else {return false;}
}//設置 files 參數
private void setFiles(String fileParams) {try{//將fileParams轉為json格式JSONObject jsonResponse = new JSONObject(fileParams);// 提取各個參數String filename = jsonResponse.optString("xxx", "");String fileHash = jsonResponse.optString("xxx", "");Integer filesize = jsonResponse.optInt("xxx", 0); // Integer類型String extension = jsonResponse.optString("xxx", "");String mimeType = jsonResponse.optString("xxx", "");// 創建 JSON 對象數組(List<Map> 格式)JSONArray fileParamsArray = new JSONArray();JSONObject fileObj = new JSONObject();fileObj.put("xxx", xxx);fileObj.put("xxx", xxx);fileObj.put("xxx", xxx);fileObj.put("xxx", xxx);fileObj.put("xxx", xxx);fileParamsArray.put(fileObj);// 存入 vars(JSON 字符串)vars.put("fileParam", fileParamsArray.toString());} catch (Exception e) {log.error("設置 files 參數 失敗!", e);prev.setSuccessful(false);} } try {String str = vars.get("needFiles"); String fileParams = vars.get("file");if(needFiles(str)){//需要文件參數setFiles(fileParams);}else{//不需要文件參數,設置為空JSONArray fileParamsArray = new JSONArray();vars.put("fileParam",fileParamsArray.toString()); }
} catch (Exception e) {log.error("判斷是否需要文件參數 失敗!", e);prev.setSuccessful(false);
}
2.4.2 JSR223 后置處理程序
對SSE響應的數據進行處理和判斷,確定好哪個數據是一次請求結束的標記
下面代碼是根據 event 含有 message_end 字段來做執行成功的標記
error字段來錯失敗的標記,同時進入斷言
下面的代碼是逐行匹配、逐個處理 SSE 事件,適合實時響應場景
import org.apache.jmeter.samplers.SampleResult;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import org.json.JSONObject;
import org.json.JSONException;// 判斷是否是流式響應
private Boolean isStreamingResponse(String response) {return response.contains("data: {");
}// 非流式響應處理
private void handleNonStreamingResponse(String response, SampleResult prev) throws Exception {JSONObject jsonResponse = new JSONObject(response);String msg = jsonResponse.get("msg");if ("智能體不存在".equals(msg)) {vars.put("response_type", "智能體不存在");vars.put("error_msg", msg);vars.put("isExist", "false");} else {vars.put("response_type", "非流式響應,未知錯誤");vars.put("error_msg", msg);vars.put("isExist", "false");}
}// 流式響應處理
private void handleStreamingResponse(String response, SampleResult prev) throws Exception {vars.put("response_type", "streaming");// 正則表達式,確保匹配完整JSONPattern pattern = Pattern.compile("data:\\s*(\\{[^{}]*\\})", Pattern.DOTALL)Matcher matcher = pattern.matcher(response);while (matcher.find()) {try {String eventJson = matcher.group(1).trim();// 處理可能的UTF-8 BOM頭if (eventJson.startsWith("\uFEFF")) {eventJson = eventJson.substring(1);}JSONObject jsonResponse = new JSONObject(eventJson);String eventType = jsonResponse.optString("event");if ("message_end".equals(eventType)) {vars.put("response_type", "智能體執行成功");vars.put("error_msg", "智能體執行成功");vars.put("isExist", "true");} else if ("error".equals(eventType)) { vars.put("response_type", "智能體執行失敗");String errorMsg = jsonResponse.get("message");vars.put("error_msg", errorMsg);vars.put("isExist", "false");}} catch (JSONException e) {log.warn("SSE事件JSON解析失敗,跳過該事件: " + vars.get("appName"));}}
}SampleResult prev = ctx.getPreviousResult();String response = prev.getResponseDataAsString();//添加APPID信息
vars.put("APPID",vars.get("appId"));//每次重置isExist的值,避免上次結果影響本次
vars.put("isExist", "true");try {if (!isStreamingResponse(response)) {handleNonStreamingResponse(response, prev);} else {handleStreamingResponse(response, prev);}
} catch (Exception e) {log.error("處理響應失敗!", e);prev.setSuccessful(false);
}
handleStreamingResponse方法:
對每次符合要求的數據進行處理和判斷
// 正則表達式,確保匹配完整JSON
Pattern pattern = Pattern.compile(“data:\s*(\{[^{}]*\})”, Pattern.DOTALL)
Matcher matcher = pattern.matcher(response);
data:\s*
匹配字符串 “data:”,后面跟 0個或多個空白字符(\s* 包括空格、換行符等)。(\{[^{}]*\})
\{ 匹配左花括號 {({ 需要轉義)。
[^{}]* 匹配 任意字符(除了 { 和 })0次或多次,確保匹配的是 單層花括號 的內容。
\} 匹配右花括號 }。
() 表示捕獲組,最終提取的是花括號內的內容。Pattern.DOTALL
讓 . 匹配 包括換行符在內的所有字符,確保多行文本也能被正確匹配。
.
Matcher matcher = pattern.matcher(response);
用編譯好的正則模式 pattern 去匹配輸入的字符串 response。
matcher 對象可以用于查找、提取符合正則規則的部分。
while (matcher.find()) {
try {
String eventJson = matcher.group(1).trim();matcher.find() 每次找到一個匹配項后,會移動內部指針,直到所有匹配項被遍歷完。
matcher.group(1) 提取正則中 第一個捕獲組(即 ({[^{}]*}) 匹配的 {…} 部分)。
2.4.3 JSR JSR223 Assertion
進行斷言處理,處理需要輸出的信息
// 斷言
if ("false".equals(vars.get("isExist"))) {// 獲取智能體名稱String appName = vars.get("appName");// 獲取具體失敗類型String respone_type = vars.get("response_type");// 獲取error_messageString error_message = vars.get("error_msg");// 獲取APPIDString appId = vars.get("APPID");//執行失敗AssertionResult.setFailure(true); // 標記斷言失敗AssertionResult.setFailureMessage(appName + "\n\t" + " 智能體ID:" + appId + "\n" + "\t 錯誤原因:"+ respone_type + "\n" + "\t error_message:" + error_message);// 添加到標簽列//prev.setSampleLabel(vars.get("respone_type")) // 修改響應消息為message變量的內容prev.setResponseMessage(respone_type);// 添加調試信息log.info("智能體名稱:", appName);log.info("智能體ID:", appId);log.info("錯誤原因:", respone_type);log.info("error_message:", error_message);
}