前段時間做 QA 的活兒, 一直在弄 jMeter, 用來測試后端服務(wù). 做的不是 API 壓測, 而是走 user journey, 所以還挺有意思的. (壓測有啥意思啊...)
基本工作流程就是 1) 用 jMeter 抓包, 錄 req, res; 2) 整理 req, res 提取出關(guān)鍵變量; 3) 處理一些特殊情況, 做 fallback, 好把 user journey 走完.
這個過程中 2) 最有意思. 我做的工作基本是看 api 返回結(jié)果, 然后看前端代碼 (iPad 客戶端, 用的 react native) 的處理邏輯, 模仿它的處理流程對數(shù)據(jù)做相應(yīng)的操作, 然后發(fā) request, 直到把 user journey 走到頭.
之前這是一個 QA (稱之為 z) 在做, 后來我接了. 然后我發(fā)現(xiàn)... 特么這 QA 根本沒法做. 因?yàn)?QA 對后端 API (如果你知道后端 DAO 的格式, 你就知道數(shù)據(jù)結(jié)構(gòu)長啥樣) 以及前端數(shù)據(jù)操作 (你不知道 react native 代碼里對后端來的數(shù)據(jù)怎么操作, 你怎么進(jìn)行下一步, 發(fā)送 request?) 都不可能太了解, 也不大有臨時去翻代碼的耐心. 深深為 QA 還要做這個活感到亞歷山大. 所以, 或許, 這就該是個 DEV 的工作 (一個前后端代碼都寫的 DEV). 你得了解數(shù)據(jù)流以及這個過程中的數(shù)據(jù)轉(zhuǎn)化, 才能走完這 user journey.
我們的 z 在寫 jMeter 的時候, 錯誤的用了 Java. 明顯不對. 因?yàn)槲覀兦昂蠖藬?shù)據(jù)都是 JSON, 用 Java 來操作 JSON 實(shí)在太麻煩了. (而且我早就沒有了手寫 Java 的勇氣) jMeter 通過 JSR 223 (Java 腳本平臺) 支持多種腳本來撰寫測試邏輯, 大概有 Java, Groovy, BeanShell script, jexl (Java Expression Language), javascript. 我就不論證 javascript 怎么特別特別好, 特別適合我們的情況了. 都是友軍襯托的好, Java 和 groovy 能用嗎? beanshell 和 jexl 根本是歷史遺留語言... Anyway, 我用了 javascript.
下面是流水賬.
定義 pwd, 好加載 js 腳本文件, 在 User Defined Variables
加入 pwd: ${__BeanShell(import org.apache.jmeter.services.FileServer; FileServer.getFileServer().getBaseDir())}
, 這樣就能在 js sampler 里用:
load(vars.get("pwd") + "/js/utils.js")
加載相關(guān)的函數(shù)和變量.
一些 js snippets:
// vars 是 get 和 put, 剛開始用成了 set 害我好久不知道為啥值就是設(shè)置不進(jìn)去...
vars.put("offset", (${__threadNum}-1)*${dealers_per_batch});
var numberOfCarPlates = parseInt(vars.get("car_plate_numbers_#"));
for (var i = 0; i < numberOfCarPlates; i++) {
var car_plate_number = vars.get("car_plate_numbers_" + (i + 1));
...
}
var sa2carPlateNumbers = JSON.parse(vars.get("sa2carPlateNumbers")) || {};
vars.put("pre_inspection_maps", prev.getResponseDataAsString());
//
function jMeterPut(store, jmKey, jmValues) {
store.put(jmKey + "_#", jmValues.length);
for (var i = 0; i < jmValues.length; i++) {
store.put(jmKey + "_" + (i + 1), jmValues[i]);
}
}
JDBC Request snippets:
SELECT DISTINCT dealer_id
FROM user
INNER JOIN user_group ON user.id = user_group.user_id
WHERE dms_user_id = 'PMT'
ORDER BY dealer_id
LIMIT ${dealers_per_batch} OFFSET ${offset};
SELECT dms_dealer_code
FROM outlet
WHERE id = '${dealer_id}'
LIMIT 1;
一些 one liner, 當(dāng)變量使:
${__Random(17500000000,17501111111,telephone)}
${__javaScript('側(cè)視'+'一二三四五六七八九十'[parseInt(Date.now()*Math.random())%10]+'甲乙丙丁戊己庚辛壬癸'[Date.now()%10],name)}
// 哪個方便用哪個, groovy 也不錯
${__groovy(new Date().format("yyyy-MM-dd HH:mm:ss"),)}
${__javaScript(new Date(Date.now() - (24-8)*60*60*1000).toISOString())}
// 記得用 Function Helper 生成和測試這些 one liner, 但可惜
// function helper 不會幫你轉(zhuǎn)義逗號, 你需要自己加
${__javaScript(var a=[];[1\,2\,3\,4\,5\,6\,7\,8\,9].forEach(function(i){return a.push("0"+i);});a=a.sort(function(){return Math.random() < 0.5;}).splice(0\,1+parseInt( Math.random()*9)).sort();JSON.stringify(a) ,)}
${__javaScript(var x=new Date(Date.now()+Math.random()*10*24*60*60*1000);x.setMinutes(0);x.setSeconds(0);x.setMilliseconds(0);x.toISOString(),)}
坑
JSON extractor 如果只有一個變量, 不需要寫 default value. 如果有多個變量, 則需要用 ;
號分隔, 而且必須要有 default value. 不然提取不出來.
jMeter 的變量都是 string, 所以不要往變量里面存 boolean 之類的數(shù)據(jù)類型, 請存入 "true" 和 "false" 字符串 (復(fù)合結(jié)構(gòu)用 JSON.stringify, JSON.parse 來序列化). 用 If Controller 的時候, 也要使用 "${should_confirm}" === "true"
來做判斷 (兩邊都有引號). 一些栗子:
"${selected_owner_id}" !=== ""
Math.random() > ${dos_event_threshold}
-
"true" === "false"
(turn off controller)