在我们原来的接口自动化测试框架设计之初,已经规划好(约定)如下内容:
1、本框架设计实现接口测试用例数据与脚本分离,
2、jmeter脚本通过csv数据元件获取测试用例数据,
3、excel用例设计列:url、method、params、preResult、sql等预留列,
4、接口请求参数实现参数化,在params设计中使用参数${params}代替,
5、脚本设计在sampler添加beanshell前置处理器,编写替换参数脚本片段,
6、如有多个关联动态数据,可能需要通过sql查询数据并构造正确的请求参数,可能需要后置处理器给下一个接口传参
7、断言脚本设计:a>简单的接口采用响应断言即可,b>有数据变动的接口采用beanshell脚本断言,更准确。
8、至此接口自动化测试框架设计完成,从框架选型、脚本开发到框架搭建,这其中曲折不足为外人道哉!
第五点解析:在每一个Sampler事务请求下添加一个beanshell前置处理器,优先处理需要替换的参数,然后再传变量给sampler,具体实现:通过csv参数元件读取excel用例中的接口请求参数params,在此参数已经设计出哪些需要是被替换的参数进行标识如${mobile}、${practiesId}等等,那么需要替换的参数从何而来呢?根据接口测试用例的步骤可以构造所请求的参数:
1、来源于数据库,在excel用例设计了一列sql,用来执行获取接口请求的某一个数据;
2、来源于上一个接口,即动态参数关联,如token、某个数据Id此类参数;
3、函数自定义,如random随机数、randomString随机字符串、time时间戳等参数;
先来看一版代码:
String data=vars.get("params");
if(data.contains("$1")){
String pre_sql=vars.get("pre_sql");
if( !pre_sql.contains("SELECT") ){
String data=data.replace("$1",vars.get("questionId"));
vars.put("new_params",data);
}else{
Object questionID=vars.getObject("questionid_set").get(${__Random(0,9,num)}).get("id");
String data=data.replace("$1",questionID.toString());
vars.put("new_params",data);
}
}else if (data.contains("$userId") && data.contains("$token") ){
String userid = vars.get("userId");
vars.put("new_params",data.replace("$userId",userid));
String data = vars.get("new_params");
vars.put("new_params",data.replace("$token",vars.get("token")));
}else if (data.contains("$token") ){
vars.put("new_params",data.replace("$token",vars.get("token")));
}else if (data.contains("$userId") ){
vars.put("new_params",data.replace("$token",vars.get("userId")));
}else{
vars.put("new_params",data);
}
发现上面代码的问题了吗?
1、第一写代码连注释都没有,那么维护难度无疑是难以想象的;
2、java是面向对象的编程语言,代码中多个if...else if...嵌套已是编码大忌;
3、代码存在不规范,可读性差,容易出错,只有自己勉强看得懂;
那么针对上面情况如何解决,一般优化方案如下:
1) 可以进行嵌套,或者多重嵌套,但为保证代码逻辑清晰,提高可读性,尽量不要嵌套(不要在if条件中if条件判断)。
2) 按先后顺序依次判断是否成立,当一个if 语句检测为真,后面的else if 及 else语句都将被跳过,这是共识。
3) 在多分支条件下,若最多只有一个分支条件成立,使用If() {} else if() {} else if() {} else {} ,且按分支出现概率从大到小排放条件表达式,即概率上出现最多的放在最前面,减少程序判断次数,提高效率。
4) 多分支同时成立且都需要处理的情况下,需使用If() {} if() {} if() {},怎么看也不见得那么美观。
so,对于优化的点,上面的代码都命中,所以如何才能使之更优化?
记得前面学习python做接口自动化时,有教授一招通过settatr设置内属性和getattr获取内属性值替换参数的方法,其中还是用了正则表达式,那么思路来了?问题也随之而来!
1、java中关于变量(内属性),是需要实现get、set方法的,什么意思?需要提前申明类属性?那我怎么知道每次设计测试用例的请求参数哪些需要进行替换?无疑这个工作量也是耗时的;
2、除非很明确知道哪些参数需要替换、同样的问题存在,要是N个参数呢?它不像python可以通过字符串格式化输出那样调用方法。
问题找到了,那么如何解决问题?
1、不使用复杂的想法设计,可以通过正则匹配需要替换的参数,即约定规范替换参数的格式,如:#mobile#,正则表达式即简单:#(.+?)#
2、使用map、list集合框架,将变量按设计替换的参数顺序存储起来,然后再根据关键key进行设值;
a>第一步将准备的参数进行list储存,设计是有序的,根据params参数变量的数据储存
b>避免出现""空字符串,并且需要先处理掉
c>然后再根据params需要参数化的值当key,list的元素做value,使用map.put(key,value)进行设置;
d>然后再是替换参数,params中需要替换的参数作为key,替换成map对象的value
3、至此接口的请求参数构造完成,需要注意的一点就是list必须按params中#mobile#需要替换的参数有序存储
代码如下(避免出错,一些泛型使用,都采用了Object对象,可以接收更多类型):
public class TestDict {
private static String pattern = "#(.+?)#";
final static Logger Log = Logger.getLogger(TestDict.class);
/**
* 泛型的使用,指代某一种类型,以及不定长传参 当然没必要这么麻烦,可以使用List 就可以了
*
* @param args
* @return
*/
// 在声明具有模糊类型(比如:泛型)的可变参数的构造函数或方法时,Java编译器会报unchecked警告。鉴于这些情况,如果程序员断定声明的构造函数和方法的主体不会对其varargs参数执行潜在的不安全的操作,可使用@SafeVarargs进行标记,这样的话,Java编译器就不会报unchecked警告。
// 使用的时候要注意:@SafeVarargs注解,对于非static或非final声明的方法,不适用,会编译不通过。
// 非static申明的方法,可能需要在不定长参数类型前加上:@SuppressWarnings("unchecked")
@SafeVarargs
public static List getList(T... args) {
List list = new ArrayList();
for (int i = 0; i < args.length; i++) {
list.add(args[i]);
}
return list;
}
/**
* list集合去除空字符串元素
*
* @param lp
* @return
*/
public static List removeAll(List lp) {
List newList = new ArrayList();
for (int i = 0; i < lp.size(); i++) {
if (lp.get(i) != "") {
newList.add((Object) lp.get(i));
}
}
return newList;
}
/**
* 将list数组,根据需要替换参数的字符串生成map对象
*
* @param content
* @param lp
* @return
*/
public static Map TextToDict(String content, List lp) {
Map dict = new HashMap();
Pattern pt = Pattern.compile(pattern);
Matcher m = pt.matcher(content);
int i = 0;
while (m.find()) {
if (lp.get(i) != "") {
dict.put(m.group(1), (Object) lp.get(i));
i++;
}
i++;
}
//Log.info("list数组转成map对象");
return dict;
}
/**
* 使用替换的map参数替换json字符串中的参数变量
*
* @param content
* @param dict
* @return
*/
public static String replaceParam(String content, Map dict) {
// boolean isMatch = Pattern.matches(pattern, content);
Pattern pt = Pattern.compile(pattern);
Matcher m = pt.matcher(content);
StringBuffer newContent = new StringBuffer();
while (m.find()) {
if (m.group().contains(m.group(1))) {
m.appendReplacement(newContent, (String) dict.get(m.group(1)));
}
}
m.appendTail(newContent);
return newContent.toString();
}
}
测试main函数代码:
public static void main(String[] args) {
// json字符串
String content = "{\"mobile\":\"#mobile#\",\"passwd\":\"#passwd#\"}";
// List list = new ArrayList();
// list.add("13800138000");
// list.add("");
// list.add("110022");
// 内置api一行代码去除""
// list.removeAll(Collections.singleton(""));
// 在java中支持的泛型
List lp = TestDict.getList("13800138000", "", "110022", 123);
System.out.println("初始储存的元素列表:" + lp);
List newList = TestDict.removeAll(lp);
System.out.println("去除空字符串后的list数组:" + newList);
// 测试map对象
// Map map = new HashMap();
// map.put("mobile", "13800138000");
// map.put("age", "");
// map.put("passwd", "110022");
Map dict = TestDict.TextToDict(content, lp);
System.out.println("根据json字符串需要替换的参数,和list元素组成key:value键值对:" + dict);
String newContent = TestDict.replaceParam(content, dict);
System.out.println("再根据新的map对象,json字符串的替换参数与map的key的value进行替换:"
+ newContent);
// ArrayList泛型,定义List能接收所有对象,而不是指定的String或者Integer某一类型的List
// List lo = new ArrayList();
// lo.add(100);
// lo.add("我来了");
// System.out.println(lo);
}
控制台结果输出如下:
初始储存的元素列表:[13800138000, , 110022, 123]
去除空字符串后的list数组:[13800138000, 110022, 123]
2020-04-20 12:00:13 466 [INFO ] TestDict:117 - list数组转成map对象
根据json字符串需要替换的参数,和list元素组成key:value键值对:{passwd=110022, mobile=13800138000}
再根据新的map对象,json字符串的替换参数与map的key的value进行替换:{"mobile":"13800138000","passwd":"110022"}
上面的方法已经完成封装,可以直接投入jmeter脚本开发使用,可能需要调整的再回来修改代码即可。
虽然封装一堆方法,那么在jmeter中如何使用呢?
1、一般导出jar文件放在lib/ext目录下并重启jmeter,bsh脚本开发,import这个jar的包路径,然后初始化类调用方法即可,
2、使用maven构建中编译组件,将我们开发的包自动构建到指定目录lib/ext,
3、重点来了,上面那么多方法,你是不是有点懵,那么我们如何简化上面的代码(不使用封装的方法):
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//获取变量的值,使用一个变量接收,但此时变量接收后并不能直接作为参数
String mobile=vars.get("phone");
String empty=vars.get("empty");
String passwd=vars.get("number");
//json字符串,此处是举例,应该是excel用例中的params参数
String content = vars.get("params");
//编码时字符串双引号需要转义,使用变量参数时,则不需要
//String params="{\"appVersion\":\"V10.3\",\"channel\":0,\"deviceName\":\"iOS\",\"deviceType\":\"1\",\"deviceid\":\"123456\",\"loginType\":0,\"mobile\":\"#mobile#\",\"password\":\"#passwd#\",\"pushToken\":\"string\",\"systemVersion\":\"1\",\"verifyCode\":\"1\",\"zone\":\"86\"}";
//将接收变量的值赋给一个变量,此时的变量是可以被引用的${param}
vars.put("empty",empty);
vars.put("passwd",passwd);
vars.put("mobile",mobile);
//log.info(vars.get("mobile"));
//正则表达式
String pattern="#(.+?)#";
//正则表达式匹配编译对象
Pattern pt = Pattern.compile(pattern);
Matcher m = pt.matcher(content);
//新的字符类型:当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
StringBuffer newContent = new StringBuffer();
//注意此处就不使用if条件判断,
while (m.find()) {
// 上面条件满足是否有匹配内容,if判断是否有key
if (m.group().contains(m.group(1))) {
// 这个追加替换方法,修改了原来的字符串,又不会重新创建字符串
m.appendReplacement(newContent, vars.get(m.group(1)));
}
}
m.appendTail(newContent);
//将正则匹配替换换后的参数赋值给新的变量
vars.put("params",newContent.toString());
添加http请求示例,然后将${params}作为新的请求体,请求成功!!!
作者:收集明天的囬忆