JMeter接口自动化测试框架通过java脚本设计通用替换参数的方法

在我们原来的接口自动化测试框架设计之初,已经规划好(约定)如下内容:

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);

/**
* <T> 泛型的使用,指代某一种类型,以及不定长传参 当然没必要这么麻烦,可以使用List<Object> 就可以了
*
* @param args
* @return
*/
// 在声明具有模糊类型(比如:泛型)的可变参数的构造函数或方法时,Java编译器会报unchecked警告。鉴于这些情况,如果程序员断定声明的构造函数和方法的主体不会对其varargs参数执行潜在的不安全的操作,可使用@SafeVarargs进行标记,这样的话,Java编译器就不会报unchecked警告。
// 使用的时候要注意:@SafeVarargs注解,对于非static或非final声明的方法,不适用,会编译不通过。
// 非static申明的方法,可能需要在不定长参数类型前加上:@SuppressWarnings("unchecked")

@SafeVarargs
public static <T> List<T> getList(T... args) {

List<T> list = new ArrayList<T>();

for (int i = 0; i < args.length; i++) {
    list.add(args[i]);
}

return list;
}

/**
* list集合去除空字符串元素
*
* @param lp
* @return
*/

public static List<Object> removeAll(List<Object> lp) {

List<Object> newList = new ArrayList<Object>();

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<String, Object> TextToDict(String content, List<Object> lp) {
    Map<String, Object> dict = new HashMap<String, Object>();
    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<String, Object> 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<String> list = new ArrayList<String>();
// list.add("13800138000");
// list.add("");
// list.add("110022");
// 内置api一行代码去除""
// list.removeAll(Collections.singleton(""));
// 在java中支持的泛型
List<Object> lp = TestDict.getList("13800138000", "", "110022", 123);
System.out.println("初始储存的元素列表:" + lp);
List<Object> newList = TestDict.removeAll(lp);
System.out.println("去除空字符串后的list数组:" + newList);
// 测试map对象
// Map<String, String> map = new HashMap<String, String>();
// map.put("mobile", "13800138000");
// map.put("age", "");
// map.put("passwd", "110022");

Map<String, Object> 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<Object> lo = new ArrayList<Object>();

// 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}作为新的请求体,请求成功!!!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章