背景:
性能測試中為了更加接近真實模擬現實應用,對於提交的信息每次都需要提交不同的數據,或使用不同的值,最為典型的就是登錄時的賬號。
性能測試工具需要提供動態參數化功能,如商業化的LoadRunner就提供了非常強大的參數化支持,可支持各種參數取值方式,循環取值,或隨機取值
而且還提供了參數模擬測試的功能,可以說非常完善
另外目前常用的性能測試工具Jmeter也提供了參數化的支持,如提供了"CSV Data Set Config"組件,可設置取值出的變量和取值結束后是否循環等
在nGrinder中怎么設置參數呢,它並沒有提供現成的參數化工具,你無法直觀的設置參數取值方法,都只能在腳本中處理
在開始之前我們需要更深刻得去理解測試場景中進程,線程等的概念
線程,進程,測試次數:
在測試場景設置中,我們先簡單假設不根據時間測試,只根據次數來測試,那么在測試場景中,需要設置的參數就包括了
進程數,線程數,測試數
虛擬用戶數=進程數*線程數
測試總數=虛擬用戶數*測試數
就是說,如果進程數設置為5,線程數設置為2,那么虛擬用戶數為10
如測試數設為3,那么一共運行的測試總次數為10*3=30
再回想一下,之前Groovy腳本中的結構,BeforeThread,或者AfterThread,在幫助中的這幅圖,可以更清楚的說明整個過程,在圖中
每個進程下有若干的線程,每個線程,執行若干次測試
Groovy腳本獲取相關的值:
在Groovy中,通過提供的方法可獲取包括當前的進程號,當前的線程號,總的進程數,總的線程數,當前的測試數,總測試數等信息
如測試進程數設置為5,測試線程數設置為2,測試數為3
在Groovy腳本中
1 processnumber=grinder.processNumber //獲取當前的進程號,應該會相應返回0,1,2,3,4
2 totalprocess=grinder.getProperties().getInt("grinder.processes", 1) //獲取測試的進程總數,返回5
3 threadnumber=grinder.threadNumber //獲取當前的線程號,應該會相應返回0,1
4 totalthread=grinder.getProperties().getInt("grinder.threads", 1) //獲取測試的線程總數,應該返回2
5 run=grinder.runNumber;//獲取當前的測試號,應該相應返回1,2,3
6 totalrun=grinder.getProperties().getInt("grinder.runs", 1); //返回測試的總數,如3
因此現在在每個測試中,我們都可以明確獲取測試的進程,線程和測試數的相關信息
當我們需要做參數化的時候,就可以根據這些信息來進行計算
nGrinder中有線程,有進程,還有測試數的概念,所以計算參數的時候,其實比LoadRunner或Jmeter更復雜,Jmeter中只有線程的概念。
當我們試圖要弄明白里面的這些概念時,要慢慢來,先嘗試着把設置弄簡單點,然后再逐步逐步設置的復雜,才更容易理解
下面我們假設幾種場景來說明如何取值,假設把一份賬號存放在資源中,我們要取的參數就是這個賬號,可能需要循環取,可能需要不重復的取
腳本參數_循環取數,單進程,賬號資源冗余:
設置的場景如下:
這個場景中,我們簡單得把進程設置為1,這樣就只需要考慮多線程,然后測試次數設置為5,但線程數量小於資源賬號數
假設資源中賬號為QAARKTESR_0001,QAARKTESR_0002.。。。QAARKTESR_0010
我們預期的取值應該是:
在Groovy中,在一開始先就把文件讀取過來,然后轉換為字符數組
def array = new File('./resources/accountlist.txt') as String[]
那在每次測試的時候,只要使用當前的線程號,0,1,2就可以取得賬號
為了簡單說明,在Groovy中,打印當前的線程,當前的測試次數,和取得的賬號
grinder.logger.info("thread number, run, account {},{},{}",grinder.threadNumber,grinder.runNumber,array[grinder.threadNumber]);
測試執行之后,查看日志輸出
檢查日志,可以看到輸出符合我們的預期,線程從0到2,執行次數從0到4,取了三個賬號,每個執行了5次
腳本參數_循環取數,但進程,賬號資源缺乏
還是剛才的場景,只是我們讓線程數大於資源數量
此時賬號只有了三個,QAARKTESR_0001,QAARKTESR_0002,QAARKTESR_0003
這樣的時候,我們預期的參數取值應該是這樣,其中,賬號循環取值
仍然是把賬號讀取到數組中,我們仍然是通過線程的號去取數組中的賬號,但因為數組長度要小於線程數,所以此時需要用取余的操作
int len=array.size(); //先取數組的長度,此時應該返回3
int arrayindex=grinder.threadNumber%len; //每次循環的賬號應該是等於線程數%長度
grinder.logger.info("thread number, run, account {},{},{}",grinder.threadNumber,grinder.runNumber,array[arrayindex]);
測試場景設置
查看測試日志:
可以看到線程3,此時循環取得的賬號是賬號1 thread number, run, account 3,0,QAARKTESR_0001
線程4 ,此時循環取的賬號是賬號2 ,thread number, run, account 4,0,QAARKTESR_0002
腳本參數_循環取數,多進程,賬號資源冗余
剛才在1,2兩種場景中,我們都把進程設置為1,這樣我們就可以只考慮線程,但如果我們把進程也添加進來,那么如何循環取參數呢?
一旦把進程考慮進來,就要比之前的要復制多了,我們還是先看比較簡單的情況
此時我們假設,賬號的數要大於虛擬用戶的數
賬號我們仍然是QAARKTESR_0001一直到QAARKTESR_0010
場景設置如下:
那么我們預期的參數取值,應該是這樣的
那么這個時候,我們要取的賬號就需要進行一下簡單的計算
序號=線程總數*當前的進程號+當前的線程號
注意,所有的進程,線程,測試號都是從0開始
如第一個進程,進程號就是0 ,線程總數是3
那么每個線程中,計算的方法,就是0*3+0,1,2
int threadcount=grinder.getProperties().getInt("grinder.threads", 1); //取線程總數,因為是3
int arrayindex=grinder.processNumber*threadcount+grinder.threadNumber;
grinder.logger.info("thread number, run, account {},{},{}",grinder.threadNumber,grinder.runNumber,array[arrayindex]);
場景設置:
運行之后查看日志,可以發現因為收到日志的限制,直接可以查看的只是一個進程的日志,另外一個進程的日志沒辦法查看到
本身日志顯示或查看的限制,所以只能看到線程1,2,3各自取了賬號QAARKTESR_0001,QAARKTESR_0002,QAARKTESR_0003
思考:
如果本身是多進程,多線程,那么如果賬號數量還小於總的線程數,那么如何計算呢
其實公式和剛才的公式是一樣的,只是在最后取余一下賬號總數
即序號=( 進程號*線程總數+線程號)%賬號總數
腳本參數_保證參數唯一性:
有時候我們需要在循環取參數的時候保證每個參數都是唯一取一次,比如注冊賬號,第二重復注冊可能就已經是失敗了
那么這時,我們需要每次測試的時候都需取唯一的數據
我們還是先設置進程數量為1,此時仍然只需要考慮線程數
那么這個時候,其實測試次數其實是9,而我們准備的賬號總數是10,所以是充分的,准備正好的9個賬號也是可以的,應該不會影響最后的取值
按照這樣的設置,我們預期的結果應該是這樣的
那么如何計算得出這樣的參數呢
取賬號序號=(當前線程號*循環測試總數)+當前的測試數序號
如當前線程號是1,循環測試總數固定是3 ,當前測試序號是1
那么應該取的序號是 1*3+1=4
groovy中的腳本應該
int testcount=grinder.getProperties().getInt("grinder.runs", 1); //測試總數,應該為3
int arrayindex=(grinder.threadNumber*testcount)+grinder.runNumber;// 取在數組中的序號
grinder.logger.info("thread number, run, account {},{},{}",grinder.threadNumber,grinder.runNumber,array[arrayindex]);
實際設置場景
運行,然后查看日志,可以看到實際循環了9個賬號,和我們的預期是一樣的
這個方式其實相當於LoanRunner中參數設置中取值方式為Unique的方式,但通過這種方式計算的時候,因為本身是要保證數據不重復,所以測試循環次數,還有數據要自己計算好,日常工作中常常遇到的是不重復的循環注冊賬號的場景
但這個場景中,我們還是簡單的把進程數設置為了1,只考慮多線程
如果循環取數,同樣是多線程的時候,怎么計算出這個序號呢
其實思路是一樣的,只是公式看上去要復雜一點點
序號=(進程數*線程總數*測試總數)+(線程數*測試總數)+當前的測試序號
只要明白了nGrinder中的進程,線程,循環測試的概念,並且我們本身都是可以從方法中取得這些數據,所以不論多進程,多線程,還是單進程,單線程都是一樣的,都是可以計算出來的
參考資料:
1 http://debugtalk.com/post/head-first-locust-advanced-script/