置腳本用法與環(huán)境搭建指南)
不管你是剛接觸接口測試還是已經(jīng)在性能壓測里摸爬滾打了一段時間只要跟Jmeter打交道基本都會遇到BeanShell腳本這個“老朋友”。BeanShell是Jmeter內(nèi)置的一個輕量級Java腳本解釋器它最直接的意義是讓你不用離開Jmeter界面就能寫代碼完成各種“官方組件做不到”的操作。這篇內(nèi)容就是這個系列的第一篇咱們先把BeanShell是什么、為什么值得學(xué)、環(huán)境怎么搭、常見坑怎么避開一次講清楚后面幾篇再逐一拆解它在取樣器、前置處理器、后置處理器和斷言里的實際用法。1. 先搞清楚BeanShell是什么以及它憑什么被內(nèi)置在Jmeter里1.1 一個用Java寫但比Java更隨意的腳本解釋器BeanShell誕生于1999年左右作者是Patrick Niemeyer本質(zhì)是一個輕量級的Java腳本解釋器。什么意思呢你可以在不寫完整類、不用public static void main方法的情況下直接在里面寫Java語法片段寫完就能執(zhí)行。它支持完整的Java語法同時又做了不少“松綁”變量可以不聲明類型方法可以不定義在類里甚至能在腳本運(yùn)行過程中動態(tài)地使用Java類庫。拿Jmeter來說你打開安裝目錄下的lib文件夾會找到一個bsh-xxxx.jar的包這就是Jmeter內(nèi)置的BeanShell運(yùn)行時。因為這個解釋器本身就是用Java寫的所以它天然和Jmeter的Java生態(tài)無縫對接。你在BeanShell腳本里調(diào)用的vars、props、log這些對象其實底層都是Jmeter用Java代碼暴露出來的實例運(yùn)行邏輯跟你寫Java代碼幾乎一模一樣。我最早接觸它的時候心里只有一個念頭“這不就是簡化版Java嗎”確實如此。如果你懂一點Java或者任何面向?qū)ο笳Z言上手BeanShell幾乎沒有心理負(fù)擔(dān)這是它最大的優(yōu)勢。即便你完全不會Java只要會用Jmeter的斷言和正則也很快能理解它要干什么。1.2 為什么Jmeter偏偏內(nèi)置了BeanShell而不是更現(xiàn)代的腳本語言這個問題不少人都困惑過“Jmeter現(xiàn)在主推的是JSR223 Groovy為什么還要講BeanShell”答案要從歷史和使用場景兩個角度看。早期Jmeter需要一種輕量、好擴(kuò)展、對Java程序員友好的方式讓用戶可以在測試計劃里寫自定義邏輯。當(dāng)時Groovy還沒有在測試工具圈形成主流地位而BeanShell體量小一個幾百KB的jar包、啟動快、能和Java類庫無縫交互自然就成了Jmeter內(nèi)置方案。你裝完Jmeter之后不需要再下載任何插件直接就能用BeanShell這種“開箱即用”的體驗對很多只想快速處理一個接口返回值的測試人員來說非常友好。Groovy當(dāng)然更現(xiàn)代、性能更好但它需要額外的依賴管理而且在Jmeter較老版本里用起來不如BeanShell順手。所以直到今天你打開Jmeter的組件菜單還是能看到大量BeanShell打頭的組件。它不是被淘汰了而是仍然承擔(dān)著大量“輕量自定義邏輯”的活。1.3 BeanShell、JSR223、Groovy到底什么關(guān)系把這三者放一起看你會更清楚。JSR223是Java社區(qū)制定的一套“腳本語言綁定標(biāo)準(zhǔn)”它讓JVM上的各類腳本語言可以用統(tǒng)一的方式去執(zhí)行。Jmeter里的JSR223 Sampler就是一個遵循這個標(biāo)準(zhǔn)的通用組件你可以把Groovy、JavaScript、BeanShell等語言掛在它下面運(yùn)行。所以在Jmeter里你其實有兩條路直接用BeanShell系列組件Sampler、PreProcessor、Assertion等簡單直接用JSR223組件然后在語言下拉框里選BeanShell或Groovy更靈活。我個人的理解是BeanShell適合作為你“在Jmeter里寫代碼”的第一站因為它語法上和Java幾乎完全一致需要記的東西最少排查問題也直觀。等你真正工作流已經(jīng)跑得順了、需要大規(guī)模壓測時再往Groovy遷移也不遲。本系列先以BeanShell為主線把邏輯講透后續(xù)不需要你重復(fù)踩坑自然能平滑過渡。2. 環(huán)境準(zhǔn)備從JDK到Jmeter把運(yùn)行環(huán)境一次搭到位2.1 JDK版本不是所有Java都能跑JmeterJmeter本身是用Java寫的所以運(yùn)行它的機(jī)器上必須有JDK。這里注意一個細(xì)節(jié)能用JRE嗎在我實際經(jīng)驗里不太建議。因為Jmeter啟動腳本會自己去找java命令但后續(xù)偶爾需要用到j(luò)avac的一些能力而且不同版本Jmeter對JDK版本也有明確要求。以Jmeter 5.x為例官方要求Java 8以上推薦Java 11或17。版本太老比如Java 7啟動時會報UnsupportedClassVersionError版本太新比如Java 21在某些老版本Jmeter上也可能出現(xiàn)兼容性問題。我的建議是裝Java 11目前看無論跑Jmeter 5.4還是5.6.3都很穩(wěn)妥。安裝完JDK后強(qiáng)烈建議先把環(huán)境變量配好。Windows下需要設(shè)置JAVA_HOME并把%JAVA_HOME%\bin加到Path里。Linux/Mac下則通常在/etc/profile或/.bashrc里配置。配置完在命令行輸入java -version能看到類似“openjdk version “11.0.21””的輸出就說明Java環(huán)境沒問題。很多“Jmeter雙擊沒反應(yīng)”的問題排查到最后都是這里沒配好所以這步別跳過。2.2 Jmeter下載與解壓路徑名和數(shù)據(jù)習(xí)慣一次到位Jmeter的官方下載入口是Apache JMeter站點進(jìn)去找到Binary包下載即可。注意選擇zip包Windows或者tgz包Linux/Mac。解壓后你會看到一個apache-jmeter-5.x.x的目錄里面有幾個關(guān)鍵位置bin目錄存放啟動腳本jmeter.bat、jmeter.sh以及配置文件jmeter.propertieslib目錄存放核心依賴包包括我們前面說的bsh-xxx.jarlib/ext目錄存放第三方插件包比如后面你會用到的自定義插件。解壓時有個非?,F(xiàn)實的經(jīng)驗路徑里不要帶中文不要帶空格盡量避免類似“C:\Program Files (x86)\壓測工具\(yùn)apache-jmeter”這種組合。這聽起來像是潔癖實際上是因為BeanShell腳本一旦用到文件路徑、外部資源或者你在命令行里跑jmeter命令時特殊字符會引發(fā)各種莫名其妙的問題。你永遠(yuǎn)不希望自己花一小時排查腳本邏輯最后發(fā)現(xiàn)是路徑里一個空格導(dǎo)致的。2.3 啟動Jmeter并確認(rèn)BeanShell解釋器就緒Windows下進(jìn)bin目錄雙擊jmeter.bat或者用命令行執(zhí)行以下命令可看到更多啟動日志jmeter.batMac/Linux下執(zhí)行sh jmeter.sh啟動后出現(xiàn)的圖形界面就是一個完整的測試計劃編輯環(huán)境。這時候還不急著寫腳本先驗證一下BeanShell是否可用。最簡單的辦法是在測試計劃上右鍵 - 添加 - 取樣器 - BeanShell取樣器BeanShell Sampler然后在腳本框里寫一行l(wèi)og.info(BeanShell is ready!);點運(yùn)行后到j(luò)meter.log或者查看結(jié)果樹里看日志輸出。如果能看到“BeanShell is ready!”這行說明整個鏈路已經(jīng)通了。這個“最小驗證步驟”別看簡單它價值很大——它一次性確認(rèn)了JDK、Jmeter、BeanShell解釋器三者都在正常工作之后你再遇到問題就只需要懷疑腳本邏輯本身。2.4 需要額外安裝BeanShell插件嗎很多人初次接觸會在網(wǎng)上看到一堆“Jmeter BeanShell插件下載”的教程容易誤解為必須裝插件。其實不用。Jmeter內(nèi)置的BeanShell已經(jīng)覆蓋了絕大多數(shù)使用場景包括Sampler、前置處理器、后置處理器、斷言、定時器和監(jiān)聽器。只有當(dāng)你需要更高級的代碼編輯器強(qiáng)調(diào)功能或者想擴(kuò)展一些Jmeter沒內(nèi)置的類庫時才需要去考慮第三方插件。本系列前半部分完全基于內(nèi)置能力展開先把這些吃透比追新工具實在得多。3. BeanShell在Jmeter里的分布每個“工位”都是干嘛的3.1 組件菜單里那些帶“BeanShell”字樣的家伙分別管什么打開Jmeter的組件菜單你會看到一堆“BeanShell xxx”新手很容易看暈。這里我用一張表把它們的作用整理清楚組件名稱所屬類別核心用途BeanShell Sampler取樣器作為請求執(zhí)行一段腳本可以生成數(shù)據(jù)、寫日志甚至發(fā)起請求BeanShell PreProcessor前置處理器在取樣器執(zhí)行前運(yùn)行常用于動態(tài)生成參數(shù)、設(shè)置變量BeanShell PostProcessor后置處理器在取樣器執(zhí)行后運(yùn)行常用于解析響應(yīng)、提取變量BeanShell Assertion斷言對響應(yīng)結(jié)果做自定義判斷斷言失敗則標(biāo)記請求失敗BeanShell Timer定時器在請求前執(zhí)行可生成靈活的時間間隔配合思考時間BeanShell Listener監(jiān)聽器對測試結(jié)果做處理比如自定義寫入文件、實時統(tǒng)計這幾種“工位”的區(qū)別在于它們在請求生命周期中的執(zhí)行順序和位置。簡單記法Sampler是“我是來干活的”PreProcessor是“我跑在請求之前做準(zhǔn)備”PostProcessor是“我跑在請求之后收尾”Assertion是“我最后檢查你對不對”。理解了這個后面學(xué)用法時就不會搞混。3.2 腳本上下文里的“免檢內(nèi)置變量”vars、props、log、prev、SampleResult在BeanShell腳本里你并不是在真空中寫代碼。Jmeter每執(zhí)行到一處BeanShell組件都會自動注入一批現(xiàn)成的對象你直接拿變量名就能用。這一塊是BeanShell在Jmeter體系里最核心的“接口規(guī)范”本系列后面每一篇都會跟它們打交道這里先把底子打好。最常見的幾個vars類型是JMeterVariables用來在測試計劃內(nèi)部存取變量。最常用的方法是vars.put(key, value)和vars.get(key)。注意put的第二個參數(shù)必須是字符串傳數(shù)字時要先轉(zhuǎn)成字符串。props類型是JMeterProperties用來讀寫Jmeter的全局屬性粒度比vars更大跨線程組也共享。log直接往jmeter.log里寫日志我用得最多的就是log.info()和log.error()用它打印關(guān)鍵信息做調(diào)試比彈窗直觀得多。prev類型是SampleResult代表當(dāng)前取樣器的執(zhí)行結(jié)果可以通過prev.getResponseDataAsString()拿到響應(yīng)內(nèi)容。SampleResult在某些組件里會直接注入一個叫SampleResult的對象你會用到setSuccessful(true/false)、setResponseData等方法手動控制請求成功與否??吹竭@些你應(yīng)該能感覺到BeanShell腳本是怎么回事它本質(zhì)上就是一個“能直接操作Jmeter運(yùn)行時對象”的入口。你可以在腳本里寫循環(huán)、做字符串拼接、調(diào)用Java類庫然后把結(jié)果通過vars.put傳給后面的請求使用。3.3 寫腳本時的語法風(fēng)格兼容完整Java但不要求完整結(jié)構(gòu)這是BeanShell對新手的友好之處。你寫Java時代碼通常要放在一個類、一個方法里面而在BeanShell腳本框里你可以直接平鋪著寫String name jane; int count 3; for (int i 0; i count; i) { log.info(name - i); }它同時支持松散類型例如x 10; x abc;在標(biāo)準(zhǔn)Java里這是不可能編譯通過的但在BeanShell里可以。這種“怎么寫都能跑”的寬容度對快速驗證想法非常有用。但實際項目里我建議你還是盡量保持“半Java”風(fēng)格變量類型寫清楚邏輯分塊用縮進(jìn)和注釋保持清晰因為腳本一旦變長松散類型會讓你自己在排查時非常痛苦。3.4 腳本內(nèi)容存在哪里以及外置腳本文件怎么寫B(tài)eanShell腳本有兩種存放方式直接寫在組件的“Script”輸入框里適合短邏輯寫在一個外部文件里再在組件里勾選“Filename”填入路徑適合長邏輯和團(tuán)隊復(fù)用。我個人建議超過20行的腳本就外置。原因很現(xiàn)實現(xiàn)有Jmeter組件自帶的腳本框就是個普通文本框沒有代碼高亮沒有自動補(bǔ)全也沒有語法檢查。你把代碼放到外部文件里至少可以用自己順手的編輯器寫還能納入版本管理團(tuán)隊協(xié)作時不會因為測試計劃文件里的腳本內(nèi)容互相沖突。外置腳本文件名我習(xí)慣用.bsh后綴純文本文檔UTF-8編碼。你只需要在組件里填上絕對路徑或相對路徑Jmeter每次執(zhí)行時會讀取文件內(nèi)容。需要注意修改外部腳本文件后某些Jmeter版本需要重新執(zhí)行“保存測試計劃”或強(qiáng)制重啟才能讀到最新內(nèi)容我遇到過幾次“改了沒生效”的假象后來都是通過手動把腳本內(nèi)容復(fù)制到組件里或重啟解決的這點后面在調(diào)試部分還會細(xì)說。4. 安裝與初學(xué)階段的高頻問題以及我的排查方法4.1 啟動類問題雙擊沒反應(yīng)、閃退、報Class版本錯誤Jmeter啟動類問題八成出在JDK或路徑上我直接給你一張排查對照表現(xiàn)象大概率原因處理方案雙擊jmeter.bat后窗口閃一下消失JAVA_HOME未配置或指向了JRE重新配置JAVA_HOME指向JDK根目錄確保java -version正常報錯UnsupportedClassVersionErrorJDK版本過舊或過新?lián)QJDK 8/11/17并保證與Jmeter版本兼容報錯Unable to access jarfile啟動腳本里的相對路徑不對不要從其他目錄直接調(diào)用jmeter.bat先進(jìn)bin目錄再啟動界面中文顯示亂碼系統(tǒng)編碼和Jmeter配置不一致修改jmeter.properties里的language和file.encoding參數(shù)或改系統(tǒng)區(qū)域設(shè)置內(nèi)存不足壓測時卡死默認(rèn)JVM堆大小不足編輯bin目錄下jmeter.bat/sh里的HEAP配置加大-Xms和-Xmx這里稍微展開講下內(nèi)存。BeanShell腳本雖然單個執(zhí)行開銷不大但如果線程數(shù)高、腳本循環(huán)多JVM堆很快會被撐起來。我見過不少同事直接在圖形界面里壓到幾百并發(fā)結(jié)果Jmeter自己先OOM了。正常做法是在性能壓測時用命令行執(zhí)行jmeter -n -t test.jmx并且預(yù)先給足堆內(nèi)存腳本里也要避免大量創(chuàng)建大對象。4.2 腳本運(yùn)行后沒有生效最常見的三個誤用初學(xué)BeanShell最常見的問題是“腳本沒反應(yīng)”。排查下來其實大多是下面三種情況之一。第一你用錯了組件。比如你在PostProcessor里寫了vars.put一個變量但請求后面緊跟的取樣器并沒有用到這個變量你會以為腳本沒生效。實際上腳本執(zhí)行了只是變量沒被消費。這種情況可以通過在腳本里log.info把值打出來馬上能確認(rèn)變量是否存在。第二vars.put的值類型不對。Var只能存字符串你寫“int a 1; vars.put(num, a)”運(yùn)行時不會報錯但取出來再用時可能被當(dāng)成字符串處理。尤其在拼參數(shù)時稍不注意就會拿到1而不是1進(jìn)而引發(fā)類型轉(zhuǎn)換異常。解決辦法是明確轉(zhuǎn)換vars.put(num, String.valueOf(a))。第三腳本里拋異常但界面不直接展示。BeanShell腳本如果運(yùn)行時異常Jmeter通常只在日志里記錄不一定會彈窗你看到的結(jié)果可能就是“請求失敗”或者變量為空。所以一定要養(yǎng)成“加日志”的習(xí)慣。4.3 調(diào)試三板斧先打印、再拆腳本、最后看結(jié)果樹我在項目里調(diào)試BeanShell腳本從來都是三板斧。先用日志把關(guān)鍵變量打出來。腳本里隨處插入log.info(reponse new String(prev.getResponseDataAsString()))比任何斷言都直觀。日志位置就在bin目錄下的jmeter.log邊跑邊tailtail -f jmeter.log再拆腳本。把一段復(fù)雜邏輯拆成幾段逐一驗證每一段的輸出。BeanShell腳本本來就不適合寫幾百行一旦需要debug復(fù)雜邏輯就得強(qiáng)迫自己拆成小函數(shù)或分段執(zhí)行。最后看結(jié)果樹。在“查看結(jié)果樹”監(jiān)聽器里可以看到每個取樣器的請求數(shù)據(jù)、響應(yīng)數(shù)據(jù)以及斷言結(jié)果。如果BeanShell腳本里setSuccessful(false)結(jié)果樹里會明確顯示失敗。這個反饋鏈路是判斷腳本對錯的最直接依據(jù)。4.4 中文亂碼腳本文件編碼和響應(yīng)編碼一起查BeanShell遇到中文十次有八次是亂碼。處理時要分兩層看。一層是腳本文件本身的編碼。外部腳本我是用UTF-8保存的但Jmeter在某些Windows環(huán)境下默認(rèn)讀取GBK會導(dǎo)致字符串拼接中文后亂碼。解決方法是在腳本文件頭部加一行注釋指定編碼或者在jmeter.properties里設(shè)置file.encodingUTF-8。另一層是請求響應(yīng)內(nèi)容的編碼。如果你的接口返回UTF-8中文但Jmeter默認(rèn)用ISO-8859-1去解碼那么你在BeanShell里取值時就是亂碼。我習(xí)慣在腳本中手動處理String response prev.getResponseDataAsString(); byte[] bytes prev.getResponseData(); String decoded new String(bytes, UTF-8);這樣能繞開Jmeter默認(rèn)編碼帶來的坑。當(dāng)然最穩(wěn)妥的方案是全局統(tǒng)一Jmeter的sampler結(jié)果編碼、腳本文件編碼、外部文件讀取編碼全部設(shè)成UTF-8從根上消掉這類問題。5. 到底什么場景才值得用BeanShell什么場景應(yīng)該繞開5.1 適合用BeanShell的典型場景BeanShell在Jmeter里最值得用的場景第一類是動態(tài)參數(shù)生成。比如登錄接口需要加密的sign值你用正則和函數(shù)助手拼不出來但用BeanShell可以調(diào)用Java的MessageDigest生成MD5/SHA摘要幾十毫秒內(nèi)完成。第二類是復(fù)雜斷言。接口返回一堆嵌套JSON你需要在斷言里判斷“數(shù)組中某個對象的某個字段是否滿足多個條件”用BeanShell寫遠(yuǎn)比用JSON Path 多個斷言方便。第三類是跨請求變量引用與加工。比如上一個請求返回的時間戳要在下一個請求里加一個偏移量你在BeanShell里做個計算再vars.put出去非常自然。5.2 不建議用的場景性能敏感的大規(guī)模壓測BeanShell有一個客觀性能劣勢它的解釋執(zhí)行速度比Groovy編譯執(zhí)行慢。在高并發(fā)壓測時如果BeanShell腳本里做了大量循環(huán)、字符串正則、JSON解析很容易變成瓶頸。Jmeter官方也推薦在JSR223組件里優(yōu)先使用Groovy。我的經(jīng)驗是功能測試、接口聯(lián)調(diào)、小并發(fā)比如100以內(nèi)場景BeanShell完全可以一旦你做上千并發(fā)、強(qiáng)調(diào)穩(wěn)定性的壓測腳本就需要考慮遷移到Groovy或者盡量減少腳本內(nèi)的計算量。這其實是個取舍不是誰絕對好而是看場景。5.3 一個務(wù)實的學(xué)習(xí)路徑如果你想把BeanShell用得踏實建議按這個順序來先把本文的環(huán)境搭起來跑通一個可以打印日志的最小腳本熟練使用vars和prev學(xué)會把一個請求的參數(shù)動態(tài)化在PostProcessor里實操“提取變量并傳遞”在Assertion里寫自定義判斷替代一半以上的正則斷言掌握log.info調(diào)試法能夠在復(fù)雜腳本里定位問題再回頭去看Groovy你會因為已經(jīng)理解了腳本引擎和Jmeter對象體系遷移非???。后續(xù)這個系列我會圍繞這條路徑一篇一篇展開。第一篇先把地基打牢后面你踩坑的概率會低很多。6. 寫在最后我的一點經(jīng)驗之談我用Jmeter做接口自動化測試和性能壓測有幾年了。剛起步的時候身邊沒有多少人愿意折騰BeanShell大家都覺得用官方組件湊合一下就行。直到有一次我需要在一個下單流程里連續(xù)生成幾十個帶加密參數(shù)的請求才不得不硬著頭皮去啃腳本。那一次踩了不少坑也讓我真正發(fā)現(xiàn)BeanShell最大的價值你不是在“求Jmeter幫我做某件事”而是等于拿到了Jmeter內(nèi)部的代碼控制權(quán)幾乎所有定制化需求都能通過幾行腳本解決。對于新接觸這門工具的人我有個建議不要一上來就貪多別急著復(fù)制網(wǎng)上復(fù)雜的腳本。先把環(huán)境、組件位置、內(nèi)置變量搞熟認(rèn)真跑通一個“打印日志”的腳本這比你“背下”十個斷言寫法都管用。因為你在調(diào)試第一個最小腳本時建立的“編譯器是啞巴日志是朋友”的感覺會成為后續(xù)長期使用的護(hù)身符。下一篇我會從BeanShell Sampler入手帶大家寫第一個真正“干活”的腳本把動態(tài)參數(shù)生成和結(jié)果判斷串起來。