效能測試乾貨分享:JMeter如何使用Bean Shell進行引數化?

博為峰網校發表於2021-04-27

在效能測試過程中,為了能夠真實模擬使用者請求,往往要將請求的報文進行引數化處理。JMeter配置元件與前置處理器都可以進行引數化,但都存在侷限性。為了幫助使用者更好地進行引數化,JMeter提供了BeanShell取樣器。

BeanShell取樣器支援BeanShell指令碼語言,這是一種完全符合Java語法規範的指令碼語言。下面就以一個樣例來說明,如何使用BeanShell來進行引數化。加我VX:atstudy-js 回覆“測試”,進入軟體測試學習交流裙~~用例示例如下:

1.需求場景

有一個TCP服務,接收並處理地理位置上報的報文。報文由以下幾部分組成:訊息頭+訊息體+校驗碼。其中,除了訊息體之外,其他部分可以是固定的,訊息體包含地理位置的經緯度、速度訊息和傳送時間。經緯度是長度為8位的16進位制字串,速度訊息也是8位的16進位制字串,可以固化,傳送時間是時間戳。要求上報的位置訊息頻率是1秒,每次上報的位置需不同。

TCP服務接收到位置報文之後,進行業務處理,然後返回一個16進位制的報文。

2.測試指令碼準備

對於此測試需求,光使用JMeter內建的函式,是不能滿足的,因此考慮使用Bean Shell處理,透過編寫Java方法的方式進行引數化設定。

開啟JMeter,按以下步驟進行操作。

(1)新增執行緒組

在測試計劃上右鍵點選->新增->執行緒(使用者)->執行緒組:

執行緒組設定如下:

執行緒組名稱設為:BeanShell示例。這裡設定100個執行緒,迴圈1次,也就是要傳送100個請求,每個請求的報文都不相同。

(2)新增Bean Shell取樣器

線上程組上右鍵->新增->取樣器->Bean Shell取樣器:

新增成功後如下圖所示:

(3)編寫Bean Shell指令碼

用Java編寫以下指令碼:

package com.example.rabbitmq.rabbitmq;

import java.text.ParseException;

import java.text.SimpleDateFormat;

public class Baowen {

// 出發城市的經度

private static double Depart_City_longitude = 110.330685;

// 出發城市的緯度

private static double Depart_City_latitude = 20.035221;

// 抵達城市的經度

private static double Arrival_City_longitude = 110.720005;

// 抵達城市的緯度

private static double Arrival_City_latitude = 19.610221;

// 兩個城市之間單程的位置點數

private static int count = 5000;

private static String dateFormate = "yyMMddHHmmss";

private static String baseTime = "200609153143";

// 報文字首

private static String beginMsg = "02004022018986042206198026706200010000000000000003";

private static String midMsg = "000B00020111";

// 報文字尾

private static String endMsg = "010400000000EE7E";

// 經度上移動的步頻

private static Double getLongitudeStep() {

return (Depart_City_longitude - Arrival_City_longitude) /count;

}

// 緯度上移動的步頻

private static Double getlatitudeStep() {

return (Depart_City_latitude - Arrival_City_latitude) /count;

}

// 把整數轉換為長度8位的16進位制字串

private static String integer2HexString(Integer number) {

String result = Integer.toHexString(number);

int len = result.length();

StringBuilder sb = new StringBuilder("");

// 不足8位時,前面補0

if(len < 8) {

for(int i = len; i < 8; i++) {

sb = sb.append("0");

}

sb.append(result);

}

return sb.toString();

}

public static String getLocationMsg(int num) {

double longitude, latitude;

// 如果位置點已經到達目的城市,則需返回,上傳返程的位置點加我VX:atstudy-js 回覆“測試”,進入軟體測試學習交流裙~~用例示例如下:

if((Depart_City_longitude - num*getLongitudeStep()) >= Arrival_City_longitude) {

longitude = Arrival_City_longitude + (num%count) * getLongitudeStep();

latitude = Arrival_City_latitude + (num%count) * getlatitudeStep();

} else {

longitude = Depart_City_longitude - (num%count) * getLongitudeStep();

latitude = Depart_City_latitude - (num%count) * getlatitudeStep();

}

System.out.println("longitude: " + longitude);

System.out.println("latitude: " + latitude);

String strLongitude = integer2HexString(new Double(longitude*1000000).intValue());

String strLatitude = integer2HexString(new Double(latitude*1000000).intValue());

System.out.println("strLongitude: " + strLongitude);

System.out.println("strLatitude: " + strLatitude);

SimpleDateFormat sdf = new SimpleDateFormat(dateFormate);

long time = System.currentTimeMillis();

try {

time = sdf.parse(baseTime).getTime();

} catch (ParseException e) {

e.printStackTrace();

}

// 每次上報報文的時間,隨著執行緒號每次遞減1000毫秒,保證間隔1秒的頻率傳送報文

String strTime = sdf.format(time - num*1000);

System.out.println( strTime);

String msg = beginMsg + strLatitude + strLongitude + midMsg + strTime + endMsg;

return msg;

}

}

// 透過上下文獲取JMeter正在執行的執行緒號

int threadNum = ctx.getThreadNum();

log.info("當前執行緒號: " + threadNum);

String msg = Baowen.getLocationMsg(threadNum);

log.info("報文: " + msg);

// 透過內建的vars物件,把報文放入變數容器中,變數的key為msg,供後面的指令碼使用

vars.put("msg", msg);

說明:

1、對時間的處理:以基準時間為參考,每次上報報文的時間,隨著執行緒號的遞增,每次遞減1000毫秒,這樣可以保證間隔1秒的頻率傳送報文。

2、對位置的處理:設定往返的兩個地點,每次上報一個位置,經緯度都按照指定的步頻變化,當經緯度等於或超過終點時,又從終點開始返回,這樣來模擬兩個城市之間往返。

3、最後把結果放入JMeter的變數容器中,後續指令碼只需以“${msg}”就可以引用該變數(如果獲取不到,需要vars.get("msg "))。

(4)新增TCP取樣器

線上程組上右鍵->新增->取樣器->TCP取樣器:

由於本次測試的是TCP服務,所以這樣需要新增一個TCP取樣器。TCP取樣器配置如下:

TCPClient classname:填寫TCP報文格式,有三類,這裡選擇org.apache.jmeter.protocol.tcp.sampler.BinaryTCPClientImpl,是常用的十六進位制報文格式。

Re-use connection:複用TCP長連線請求。TCP長連線的時候需要勾選它。

EOL(行尾位元組值):報文結束標誌。因為報文是16進位制的,報文的結束標誌字元是7e ,7e的二進位制是 0111 1110,對應的十進位制是126。

(5)新增察看結果樹

線上程組上右鍵->新增->監聽器->察看結果樹:

(6)新增聚合報告

線上程組上右鍵->新增->監聽器->聚合報告:

3.測試結果

點選執行按鈕,結果如下:

透過聚合報告可知,請求全部傳送成功,並且得到正確的響應。透過察看結果樹,對比不同的TCP取樣器,可以看到請求報文的差異:

報文003:020040220189860422061980267062000100000000000000030131b5eb069383d8000B00020111200609153141010400000000EE7E

報文004:020040220189860422061980267062000100000000000000030131b59606938426000B00020111200609153140010400000000EE7E

4.Bean Shell的應用場景

以上介紹的是自定義函式場景下對Bean Shell的使用。事實上,Bean Shell還有其他的應用場景。

(1)引用外部Java檔案

如果已經有了現成的Java檔案,或者需要從外部Java檔案中呼叫方法,那麼可以使用Bean Shell引入Java檔案,在指令碼中使用。

比如:現有一個Java檔案,名為MyJavaFile.java,在Bean Shell指令碼中可以這樣使用:

// 引入外部Java檔案

source("D:/script/MyJavaFile.java")

// 呼叫Java檔案中的方法

String result = new Baowen().getLocation();

// 儲存變數

vars.put("msg", result);

在bean shel中透過source("程式碼絕對路徑")的方式引入java,然後就和普通的Java類一樣呼叫其方法就行。

(2)引用外部class檔案

這種方式跟上一種類似,先用addClassPath("class檔案所在的目錄")引入 class檔案,然後再用“import包名.類名“的方式匯入類,接下來就可以呼叫class檔案當的類和方法了:

// 引入外部class檔案

addClassPath("D:\\")

// 匯入包和類

Import MyPackeage.Baowen;

// 呼叫Java檔案中的方法

String result = new Baowen().getLocation();

// 儲存變數

vars.put("msg", result);

(3)引用外部Jar包

外部Jar包要先匯入測試中,可以透過兩種方式進行匯入。其一,是在測試計劃中,透過瀏覽按鈕,將需要匯入的jar包引入。

另外,也可以把Jar包放在JMeter安裝目錄|lib\ext下。

然後在Bean Shell中引用:

// 匯入包和類

Import MyPackeage.Baowen;

// 呼叫Java檔案中的方法

String result = new Baowen().getLocation();

// 儲存變數

vars.put("msg", result);

Bean Shell是一個小型的、免費的、嵌入式的Java原始碼直譯器,具有物件指令碼語言特性,能夠動態地執行標準JAVA語法。並且,Bean Shell支援“鬆散的”或者動態地指定型別型別。能夠在執行時做型別檢查,而不需要先定義變數以及指定特定的變數型別來指向變數。

因為Bean Shell是用java寫的,執行在同一個虛擬機器的應用程式,因此可以方便地引用指令碼中的物件,能夠滿足複雜的引數化需求。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31407649/viewspace-2770167/,如需轉載,請註明出處,否則將追究法律責任。

相關文章