1 概述
腳本錄制編寫是性能測試的一個重要環節。在性能測試過程中,虛擬用戶模擬真實用戶使用被測系統,這個“模擬”的過程正是通過性能測試腳本來實現的。因此,編寫一個準確無誤的腳本對性能測試有至關重要的意義。完成性能測試腳本包括兩個步驟:腳本錄制和腳本編寫,本文重點關注腳本編寫。
2 腳本錄制
2.1.錄制方式
HTTP協議腳本錄制可選兩種方式:基于HTML和基于URL。選擇哪種錄制方式的原則如下:基于瀏覽器的HTTP應用系統選擇HTML,基于其他方式的HTTP應用系統選擇URL。
2.2.錄制注意點
取消錄制期間自動關聯功能;
如果部分測試腳本出現問題,需要重新錄制,可以只錄制存在問題的片斷腳本,方法是不選中錄制啟動對話框中的Record the application startup。
3 腳本編寫
3.1.常用技術
LoadRunner性能測試腳本編寫常用的技術包括參數化,關聯,邏輯控制和腳本模塊化。
3.1.1.參數化
參數化就是將腳本中的常量轉化為變量的過程。通過錄制生成的腳本所有的數據都是常量,為了達到向服務器發送的數據多樣化的目的,需要將一些數據常量轉化為變量。
3.1.2.關聯
關聯就是查找動態數據,并把查詢到的數據以參數的形式保存起來。在B/S或者C/S系統中,服務器返回給客戶端的數據有些是動態改變的,例如客服系統的人工來話流水號和工作流系統的工單流水號。當打開工單生成頁面后,工單流水號已經從服務器端獲取到了,而在提交工單步驟,需要將該流水號返回給服務器。因此,在提交工單之前,在腳本中必須獲得流水號。獲得流水號的方法就是關聯。
使用關聯功能動態保存的參數跟直接通過參數化生成的參數是一致的。唯一不同的是,通過參數化生成的參數在腳本中可以高亮顯示。
3.1.3.邏輯控制
業務系統在實際應用中,業務操作步驟間往往存在邏輯。比如,客服3.0工作流系統,業務代表處理工單,如果待辦區沒有工單等待處理,則先從工單池中提取工單到待辦區,然后進行處理,并且需要優先處理超時或即將超時的工單。在工單處理的性能測試腳本中,也必須遵從這種業務邏輯。
LoadRunner性能測試腳本采用C語言,因此腳本邏輯控制同C語言一致,使用if,switch,while/for/do控制結構。
3.1.4.腳本模塊化
腳本模塊化的目的是:提高腳本可讀性、可重用性和腳本生產效率。腳本模塊化的本質是抽取函數,一些很通用的函數甚至可以封裝為DLL。模塊化性能測試腳本的思想跟自動化測試的ActionWord有相似之處。
例如:客服3.0系統的登錄功能,無論是工作流、知識庫、公告便簽還是培訓考試,它們都使用相同的登錄頁面。我們可以把登錄腳本抽取為一個函數csp_login(char *staffno,char * password),需要登錄操作時,不需要錄制和拷貝腳本,只要調用這個函數就可以了。
注意:并不是所有的腳本代碼塊都需要做模塊化處理,只有那些穩定不變、并且經常用到的代碼塊才需要做模塊化處理。不做得不償失的事。
3.2.典型函數
LoadRunner中,常用的函數有很多,這里只介紹編寫性能測試腳本過程中那些必然用到的函數。本文重點關注這些典型函數的應用場合及注意點,至于函數詳細使用說明請參見LoadRunner幫助文檔。
3.2.1.事務相關
3.2.1.1. lr_start_transaction/lr_end_transaction
功能:事務開始/結束標記。
應用場合:需要統計某一段代碼塊執行所需要的時間,這兩個函數需要成對使用。
舉例:工作流系統性能測試中有一個需求,300人在線,提交工單操作平均響應時間在3秒以內,則需要在提交工單請求步驟之前插入lr_start_transaction,提交工單請求步驟之后插入lr_end_transaction。
注意點:這兩個函數只是標記函數,用于標記事務開始/結束,因此可以嵌套使用,即事務中還可以包含子事務。
3.2.1.2. lr_think_time
功能:模擬思考時間,即等待時間。
應用場合:在線用戶測試,為了讓每一個虛擬用戶模擬一個真實用戶的行為,即讓一個虛擬用戶對系統產生的壓力跟真實用戶相當,就必須使用這個函數。這是因為,用戶在使用系統的過程中,從一個操作轉換到另一個操作,是需要時間的,這個時間就是思考時間。
舉例:客服3.0工作流系統在線用戶測試。對于工單查詢操作,輸入查詢條件后提交查詢,從輸入查詢條件至提交查詢的時間間隔就是思考時間。因為LoadRunner無法模擬鍵盤輸入的過程,它只能模擬鍵盤輸入的等待時間,此時需要在提交查詢的那個動作前插入lr_think_time函數。
注意點:在錄制腳本中,原子事務內不要包含lr_think_time函數,否則該思考時間將被統計到事務響應時間中,造成結果不準確。另外lr_think_time是否啟作用,可通過runtime-seting進行設置。
注:原子事務指那些不能再分割為更小事務的事務,它經常指一個單一的業務操作,通常表現為一個URL請求。
3.2.1.3. lr_rendezvous
功能:在Vuser腳本中設置集合點。
應用場合:并發測試。
舉例:客服3.0培訓考試系統,100人同時打開同一份試卷。則需要在打開試卷的語句前插入lr_rendezvous函數,并在場景中設置集合點策略。
注意點:非并發測試,例如在測試系統的處理能力時,最好不要設置集合點,因為一旦設置了集合點,將導致一些VUser處于等待狀態,在這等待過程中服務器將是空閑的,這將導致不能準確的測試出服務器的真實性能水平。集合點更多用于發現系統的并發問題。
3.2.2.參數化/關聯
3.2.2.1.lr_save_string/lr_save_int
功能:將某一字符串/整型保存為參數。
應用場合:有些變量的值通過C語言生成,之后在測試腳本中要使用這些變量。
舉例:客服3.0業務配置臺系統增加業務代表操作,業務代表的工號和姓名使用C語言函數生成。工號和姓名分別保存在staff_no和staff_name變量中,則在腳本中可以使用lr_save_int(staff_no,"staffno"),lr_save_string(staff_name, "stafffname")將工號和姓名參數化。
注意點:無。
3.2.2.2.web_reg_save_param
功能:在服務器返回的文本中查找一個或者多個字符串,并將搜索到的字符串值保存在參數中。
應用場合:在B/S或者C/S系統中,服務器返回給客戶端的數據有些是動態改變的,在腳本的下一個步驟中,需要使用該動態數據。這時,就需要使用關聯獲得該動態數據。
舉例:客服3.0工作流系統,工單辦理每次都從待辦區中打開第一條工單,為打開第一條工單,需要獲取第一條工單的完整URL(包括URL中的parameter及其值),而每一次進入待辦區,第一條工單有可能是不一樣的。為獲取第一條工單的URL,將打開工單的URL做關聯。已知打開待辦區操作獲得的HTML有如下片斷:
<a href="#"onclick="javascript:openseviceforprocess('/iwflow/FindJspID.jsp?serialNo=2008092200000033&serviceID=0099&nodeID=140004&dealID=2008092200000056&hisFlag=0&skillID=020401&dealSkillID=020101&dealStaff=1200','false');">
可在打開待辦區的操作前插入如下語句:
web_reg_save_param("tt_url", "LB=javascript:openseviceforprocess('","RB=','false')", "Ord=1","IgnoreRedirections=Yes", "Search=Body","RelFrameId=1", LAST );
運行腳本后,tt_url的值為:
/iwflow/FindJspID.jsp?serialNo=2008092200000033&serviceID=0099&nodeID=140004&dealID=2008092200000056&hisFlag=0&skillID=020401&dealSkillID=020101&dealStaff=1200
這個URL就是打開第一條工單的URL,有了URL,便可打開工單。
注意點:
(1)LoadRunner工具只能識別文本,在HTTP協議中只能識別HTML文檔,因此關聯的依據是HTML源碼,而不是經過瀏覽器解析后的可視化文本。這一點很重要。
(2)關聯還能將多個匹配的參數保存在數組中,方法是指定ORD的屬性值為ALL,之后通過“{參數名_1}”, “{參數名_2}”, “{參數名_3}”格式可獲得數組元素的值。
(3)該函數有一個屬性NOTFOUND,默認值為ERROR,也就是說,如果找不到要查找的數據,將報出錯誤,在必要的時候,例如腳本邏輯控制需要,可以將NOTFOUND的屬性值設為WARNING,這樣LoadRunner將不產生錯誤。
3.2.2.3.lr_save_searched_string
功能:在某一個字符緩沖區中搜索指定的字符串,并將搜到的字符串保存在參數中。
應用場合:可配合LoadRunner的關聯功能,靈活獲取服務器端返回的數據。
舉例:客服3.0工作流系統,工單處理每次都從待辦區中打開第一條工單,打開工單的URL已經通過關聯保存在tt_url參數中,在工單處理提交時,需要使用serviceNo,serviceID,nodeID,dealID,tt_url的值如下:
/iwflow/FindJspID.jsp?serialNo=2008092200000033&serviceID=0099&nodeID=140004&dealID=2008092200000056&hisFlag=0&skillID=020401&dealSkillID=020101&dealStaff=1200
可使用以下函數保存serviceNo,serviceID,nodeID,dealID的值。
//保存serialNo,serviceID,nodeID,dealID參數 int getTTData(){ int i = 0;int j=0; char *tt_url = lr_eval_string("{tt_url}"); int len= strlen(tt_url); while(tt_url[i]!='='){i++;} while(tt_url[j]!='&'){j++;} lr_save_searched_string(tt_url,len,0,"serialNo",1,j-i-1,"serialNo"); i++;j++;while(tt_url[i]!='='){i++;} while(tt_url[j]!='&'){j++;} lr_save_searched_string(tt_url,len,0,"serviceID",1,j-i-1,"serviceID"); i++;j++;while(tt_url[i]!='='){i++;} while(tt_url[j]!='&'){j++;} lr_save_searched_string(tt_url,len,0,"nodeID",1,j-i-1,"nodeID"); i++;j++; while(tt_url[i]!='='){i++;} while(tt_url[j]!='&'){j++;} lr_save_searched_string(tt_url,len,0,"dealID",1,j-i-1,"dealID"); return 0; } |
注意點:無
3.2.2.4.lr_save_datetime
功能:將時間保存為參數。
應用場合:應用系統需要把時間數據提交給服務器端。
舉例:客服3.0工作流系統活動工單查詢,默認查詢從當天開始的最近三天工單。 則查詢的開始時間和結束時間可用lr_save_datetime獲取。
lr_save_datetime("%y-%m-%d00:00", DATE_NOW-2*ONE_DAY, "queryBeginTime");
lr_save_datetime("%y-%m-%d23:59", DATE_NOW, "queryEndTime");
注意點:無
3.2.2.5. web_save_timestamp_param
功能:將當前時間戳保存為參數。
應用場合:應用系統需要把時間戳提交給服務器端。
舉例:多媒體坐席客戶端,在向MClient提交信息時,需要附帶客戶端的時間戳,則可以使用該函數獲取當前時間戳。
注意點:與lr_save_datetime不同的是,本函數保存的是時間戳,而lr_save_datetime保存的是日期和時間。
3.2.2.6.lr_eval_string
功能:將某一字符串中包含的所有參數替換為真實值,并返回替換后的字符串。
應用場合:欲查看某一參數的值,可使用該函數。
舉例:客服3.0工作流系統,生成工單時打開工單頁面準備工單提交,提交之前想查看已通過關聯保存的serialNo參數的值。方法如下:
lr_output_message(lr_eval_string("TheserialNo is {serialNo}"));
注意點:如果不存在該參數,將把“{參數名}”當作普通字符串輸出。如本例,如果不存在serialNo參數,則輸出:The serialNo is {serialNo}。
3.2.3.驗證點
3.2.3.1.web_reg_find
功能:在HTML文檔中查找指定的字符串。
應用場合:該函數是檢查點函數,在腳本中需要插入檢查點的地方使用。
舉例:客服3.0工作流系統,提交工單生成后,需要驗證工單是否提交成功。則可根據頁面提示“工單生成成功”進行驗證。在提交生成工單步驟前插入:
web_reg_find("Text=工單生成成功",LAST);
注意點:該函數是注冊型參數,需要在請求服務器數據步驟之前插入該函數。與該函數功能類似的函數是web_find,但是web_find只對HTML方式的腳本起作用,對URL方式腳本則不起作用,而且web_find函數效率低下,已被廢棄。
3.2.3.2. web_image_check
功能:判斷某一個圖片是否存在HTML頁面中。
應用場合:同web_reg_find函數一樣,該函數也是檢查點函數,在腳本中需要插入檢查點的地方使用。
舉例:客服3.0培訓考試系統并發測試,50個人同時打開試卷,為了驗證打開試卷成功,根據試卷中的圖片public/images/onexam.gif進行驗證:
web_image_check("web_image_check","Src=public/images/onexam.gif",LAST);
注意點:要使該函數生效,需要在runtime-seting中將打開。與web_reg_find不一樣的是,該函數不是注冊型函數,因此需要在請求返回步驟之后插入該函數。上文提過,LoadRunner只能識別文本,因此web_image_check函數其本質仍然是文本驗證,完全可以用web_reg_find替代,而且強烈推薦使用web_reg_find作為檢查點函數。
3.2.4.日志輸出
3.2.4.1. lr_output_message
功能:將VUser的消息打印到日志文件和輸出窗口中,打印的消息帶有腳本行信息。
應用場合:方便查看運行信息,輔助問題定位。
舉例:客服3.0系統,登錄工號已經參數化,調試腳本時將當前的登錄工號輸出到Replay Log窗口中。代碼如下:
lr_output_message("Thestaffno is %s",lr_eval_string("{staffno}"));
注意點:與該函數具有類似功能的還有:lr_debug_message,lr_log_message lr_message,lr_error_message它們之間的不同之處這里不作詳細介紹,請參見LoadRunner幫助文檔。
3.2.4.2. lr_vuser_status_message
功能:將VUser的消息輸出到場景運行的VUser狀態窗口。
應用場合:將一些關鍵信息輸出到VUser運行狀態窗口,方便場景執行時查看。
舉例:在場景運行過程中,出現了錯誤。根據錯誤窗口提示,該錯誤屬于VUser ID為2的虛擬用戶,為了方便將系統登錄用戶名與VUser ID對應起來,以方便問題定位。可以使用以下代碼:
lr_vuser_status_message("Thelogin username is %s", lr_eval_string("{username}"));
場景執行時,可方便查看到VUserID與登錄用戶名的對應關系,如下圖:
注意點:無
3.2.5.其它實用函數
3.2.5.1. lr_get_vuser_ip
功能:獲得VUser的IP地址。
應用場合:在使用IP欺騙時,為了驗證IP欺騙是否成功,可以使用該函數。
舉例:在場景運行過程中,將每一個VUser的IP在VUser運行狀態窗口中顯示出來。
char *ip; ip = lr_get_vuser_ip(); if (ip) { lr_vuser_status_message("The IP addressis %s", ip); } |
注意點:為了使IP欺騙成功,使用IP欺騙向導設置好IP后,還要將打開才可。
3.2.5.2. lr_load_dll
功能:加載外部DLL。
應用場合:腳本需要使用外部DLL時,使用該函數加載DLL。
舉例:函數getDateTime(char * time,int seconds,char * resultTime)已封裝在timeutil.dll中,getDateTime的功能是根據傳入的日期字符串time(如2008-09-24 16:56:24),秒偏移量seconds,計算返回結果日期字符串resultTime,代碼如下:
int hours =atoi(lr_eval_string("hours")); char acceptEndTime[20]; lr_load_dll("../timeutil.dll"); getDateTime(lr_eval_string("{acceptBeginTime}"),3600*hours,acceptEndTime); |
注意點:該函數為LoadRunner提供了調用外部接口的能力。
3.3.封裝,構建可重用腳本
3.3.1.簡單函數封裝
LoadRunner使用C語言作為腳本,因此只要是合法的C代碼都可以在LoadRunner中運行。為了提高腳本可讀性和腳本生產效率,有必要將性能測試腳本模塊化。
客服3.0工作流系統,查詢工單池是一個很常見的操作。我們可以把查詢工單池操作封裝為一個queryTTPool函數,函數體如下,在腳本中,將所有的查詢工單池操作替換為queryTTPool函數調用,提高了腳本的可讀性:
//查詢工單池 int queryTTPool(char* nodeType){ lr_save_string(nodeType,"nodeType"); lr_save_datetime("%Y-%m-%d 00:00",DATE_NOW-2*ONE_DAY, "acceptBeginTime"); lr_save_datetime("%Y-%m-%d 23:59", DATE_NOW,"acceptEndTime"); lr_start_transaction("WF_查詢工單池工單"); web_submit_data("IWFController", "Action=http://{wf_sysurl}/IWFController", "Method=POST", "RecContentType=text/html", "Referer=http://{wf_sysurl}/iwflow/common/UnitQueryDealForm.jsp?pageNo=1", "Snapshot=t32.inf", "Mode=HTML", ITEMDATA, "Name=ACTIONID","Value=UniteQueryDealAction", ENDITEM, "Name=pageNo", "Value=1", ENDITEM, "Name=sortType", "Value=", ENDITEM, "Name=sortField", "Value=", ENDITEM, "Name=reSortFlag", "Value=", ENDITEM, "Name=acceptPhone", "Value=", ENDITEM, "Name=serialNo", "Value=", ENDITEM, "Name=serialFlag", "Value=0", ENDITEM, "Name=serviceName", "Value=", ENDITEM, "Name=serviceID", "Value=", ENDITEM, "Name=acceptBeginTime","Value={acceptBeginTime}", ENDITEM, "Name= acceptEndTime ", "Value={acceptEndTime}",ENDITEM, "Name=urgentID", "Value=", ENDITEM, EXTRARES, "Url=/iwflow/image/kms-1_23.gif", ENDITEM, "Url=/iwflow/buttons/obtainProcess-2.gif", ENDITEM, LAST); lr_end_transaction("WF_查詢工單池工單",LR_AUTO); } |
3.3.2.DLL封裝
使用DLL有很多好處。高度重用的函數制作成DLL,方便腳本調用。將與IVR交互的消息函數封裝成DLL后,便可利用LoadRunner測試IVR性能。制作DLL可以選擇VC或者MinGW Developer Studio等工具,至于DLL的制作細節,本文不作介紹,請參見相關指導書。
4 腳本調試
LoadRunner的VUser Generator本身的調試功能比較弱,只能設置斷點,無法單步跟蹤。當腳本出現問題時,可以使用lr_debug_message,lr_output_message,lr_eval_string等函數協助定位。
腳本錯誤大部分原因都是向服務器發送的數據不對,因此還可以利用HttpAnalyzer工具進行HTTP協議跟蹤,通過比較LoadRunner發送的數據和瀏覽器發送的數據,便能很快定位出問題根源。