前段时间做 QA 的活儿, 一直在弄 jMeter, 用来测试后端服务. 做的不是 API 压测, 而是走 user journey, 所以还挺有意思的. (压测有啥意思啊...)
基本工作流程就是 1) 用 jMeter 抓包, 录 req, res; 2) 整理 req, res 提取出关键变量; 3) 处理一些特殊情况, 做 fallback, 好把 user journey 走完.
这个过程中 2) 最有意思. 我做的工作基本是看 api 返回结果, 然后看前端代码 (iPad 客户端, 用的 react native) 的处理逻辑, 模仿它的处理流程对数据做相应的操作, 然后发 request, 直到把 user journey 走到头.
之前这是一个 QA (称之为 z) 在做, 后来我接了. 然后我发现... 特么这 QA 根本没法做. 因为 QA 对后端 API (如果你知道后端 DAO 的格式, 你就知道数据结构长啥样) 以及前端数据操作 (你不知道 react native 代码里对后端来的数据怎么操作, 你怎么进行下一步, 发送 request?) 都不可能太了解, 也不大有临时去翻代码的耐心. 深深为 QA 还要做这个活感到亚历山大. 所以, 或许, 这就该是个 DEV 的工作 (一个前后端代码都写的 DEV). 你得了解数据流以及这个过程中的数据转化, 才能走完这 user journey.
我们的 z 在写 jMeter 的时候, 错误的用了 Java. 明显不对. 因为我们前后端数据都是 JSON, 用 Java 来操作 JSON 实在太麻烦了. (而且我早就没有了手写 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")
加载相关的函数和变量.
一些 js snippets:
// vars 是 get 和 put, 刚开始用成了 set 害我好久不知道为啥值就是设置不进去...
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, 当变量使:
${__Random(17500000000,17501111111,telephone)}
${__javaScript('侧视'+'一二三四五六七八九十'[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 不会帮你转义逗号, 你需要自己加
${__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 之类的数据类型, 请存入 "true" 和 "false" 字符串 (复合结构用 JSON.stringify, JSON.parse 来序列化). 用 If Controller 的时候, 也要使用 "${should_confirm}" === "true"
来做判断 (两边都有引号). 一些栗子:
"${selected_owner_id}" !=== ""
Math.random() > ${dos_event_threshold}
-
"true" === "false"
(turn off controller)
Supplementary
执行顺序
变量和函数
http://jmeter.apache.org/usermanual/functions.html