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}作爲新的請求體,請求成功!!!

 

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