Contents
Locust這一款開源性能測試工具。然而,當前在網絡上針對Locust的教程極少,不管是中文還是英文,基本都是介紹安裝方法和簡單的測試案例演示,但對於較復雜測試場景的案例演示卻基本沒有,因此很多測試人員都感覺難以將Locust應用到實際的性能測試工作當中。
經過一段時間的摸索,包括通讀Locust官方文檔和項目源碼,並且在多個性能測試項目中對Locust進行應用實踐,事實證明,Locust基本能滿足日常的性能測試需求,LoadRunner能實現的功能Locust也基本都能實現。但是在error和並發用戶數之前沒有提供明確的對應關系圖,不能確定最佳的並發量。設計場景能力有點欠佳,不過對腳本設計很是靈活,可以在此作一定的彌補。
本文將從Locust的功能特性出發,結合實例對Locust的使用方法進行介紹。考慮到大眾普遍對LoadRunner比較熟悉,在講解Locust時也會采用LoadRunner的一些概念進行類比。
概述
先從Locust的名字說起。Locust的原意是蝗蟲,原作者之所以選擇這個名字,估計也是聽過這么一句俗語,“蝗蟲過境,寸草不生”。
而Locust工具生成的並發請求就跟一大群蝗蟲一般,對我們的被測系統發起攻擊,以此檢測系統在高並發壓力下是否能正常運轉。
壓力發生器的核心要點有三點:一是真實模擬用戶操作,二是模擬有效並發,三是模擬實際的場景。
在Locust測試框架中,測試場景是采用純Python腳本進行描述的。對於最常見的HTTP(S)協議的系統,Locust采用Python的requests庫作為客戶端,使得腳本編寫大大簡化,富有表現力的同時且極具美感。而對於其它協議類型的系統,Locust也提供了接口,只要我們能采用Python編寫對應的請求客戶端,就能方便地采用Locust實現壓力測試。從這個角度來說,Locust可以用於壓測任意類型的系統。
在模擬有效並發方面,Locust的優勢在於其摒棄了進程和線程,完全基於事件驅動,使用gevent提供的非阻塞IO和coroutine來實現網絡層的並發請求,因此即使是單台壓力機也能產生數千並發請求數;再加上對分布式運行的支持,理論上來說,Locust能在使用較少壓力機的前提下支持極高並發數的測試。
locust的安裝
安裝locust
在dos下輸入pip install locustio 回車
如果提示未找到pip命令,則需要進入python安裝目錄,找到D:\Python27\Scripts路徑,並將該路徑添加至環境變量中。
安裝pyzmq
在dos下輸入pip install pyzmq 回車
腳本編寫
編寫Locust腳本,是使用Locust的第一步,也是最為重要的一步。
簡單示例
先來看一個最簡單的示例。
from locust import HttpLocust, TaskSet, task
class ScriptTasks(TaskSet):
def on_start(self):
self.client.post("/login", { "username": "test", "password": "123456" })
@task(2)
def index(self):
self.client.get("/")
@task(1)
def about(self):
self.client.get("/about/")
@task(1)
def demo(self):
payload={}
headers={}
self.client.post("/demo/",data=payload, headers=headers)
class WebsiteUser(HttpLocust):
task_set = ScriptTasks
host = "http://example.com"
min_wait = 1000
max_wait = 5000
那么,如上Python腳本是如何表達出以上測試場景的呢?在這個示例中,定義了針對http://example.com網站的測試場景:先模擬用戶登錄系統,然后隨機地訪問首頁(/)和關於頁面(/about/),請求比例為2:1,demo方法主要用來闡述client對post接口的處理方式;並且,在測試過程中,兩次請求的間隔時間為1~5秒間的隨機值。
從腳本中可以看出,腳本主要包含兩個類,一個是WebsiteUser(繼承自HttpLocust,而HttpLocust繼承自Locust),另一個是ScriptTasks(繼承自TaskSet)。事實上,在Locust的測試腳本中,所有業務測試場景都是在Locust和TaskSet兩個類的繼承子類中進行描述的。
那如何理解Locust和TaskSet這兩個類呢?
簡單地說,Locust類就好比是一群蝗蟲,而每一只蝗蟲就是一個類的實例。相應的,TaskSet類就好比是蝗蟲的大腦,控制着蝗蟲的具體行為,即實際業務場景測試對應的任務集。
這個比喻可能不是很准確,接下來,我將分別對Locust和TaskSet兩個類進行詳細介紹。
class HttpLocust(Locust)
在Locust類中,具有一個client屬性,它對應着虛擬用戶作為客戶端所具備的請求能力,也就是我們常說的請求方法。通常情況下,我們不會直接使用Locust類,因為其client屬性沒有綁定任何方法。因此在使用Locust時,需要先繼承Locust類,然后在繼承子類中的client屬性中綁定客戶端的實現類。
對於常見的HTTP(S)協議,Locust已經實現了HttpLocust類,其client屬性綁定了HttpSession類,而HttpSession又繼承自requests.Session。因此在測試HTTP(S)的Locust腳本中,我們可以通過client屬性來使用Python requests庫的所有方法,包括GET/POST/HEAD/PUT/DELETE/PATCH等,調用方式也與requests完全一致。另外,由於requests.Session的使用,因此client的方法調用之間就自動具有了狀態記憶的功能。常見的場景就是,在登錄系統后可以維持登錄狀態的Session,從而后續HTTP請求操作都能帶上登錄態。
而對於HTTP(S)以外的協議,我們同樣可以使用Locust進行測試,只是需要我們自行實現客戶端。在客戶端的具體實現上,可通過注冊事件的方式,在請求成功時觸發events.request_success,在請求失敗時觸發events.request_failure即可。然后創建一個繼承自Locust類的類,對其設置一個client屬性並與我們實現的客戶端進行綁定。后續,我們就可以像使用HttpLocust類一樣,測試其它協議類型的系統。
原理就是這樣簡單!
在Locust類中,除了client屬性,還有幾個屬性需要關注下:
task_set: 指向一個TaskSet類,TaskSet類定義了用戶的任務信息,該屬性為必填;max_wait/min_wait: 每個用戶執行兩個任務間隔時間的上下限(毫秒),具體數值在上下限中隨機取值,若不指定則默認間隔時間固定為1秒;host:被測系統的host,當在終端中啟動locust時沒有指定--host參數時才會用到;weight:同時運行多個Locust類時會用到,用於控制不同類型任務的執行權重。
測試開始后,每個虛擬用戶(Locust實例)的運行邏輯都會遵循如下規律:
- 先執行
WebsiteTasks中的on_start(只執行一次),作為初始化; - 從
WebsiteTasks中隨機挑選(如果定義了任務間的權重關系,那么就是按照權重關系隨機挑選)一個任務執行; - 根據
Locust類中min_wait和max_wait定義的間隔時間范圍(如果TaskSet類中也定義了min_wait或者max_wait,以TaskSet中的優先),在時間范圍中隨機取一個值,休眠等待; - 重復
2~3步驟,直至測試任務終止。
class TaskSet
再說下TaskSet類。
性能測試工具要模擬用戶的業務操作,就需要通過腳本模擬用戶的行為。在前面的比喻中說到,TaskSet類好比蝗蟲的大腦,控制着蝗蟲的具體行為。
具體地,TaskSet類實現了虛擬用戶所執行任務的調度算法,包括規划任務執行順序(schedule_task)、挑選下一個任務(execute_next_task)、執行任務(execute_task)、休眠等待(wait)、中斷控制(interrupt)等等。在此基礎上,我們就可以在TaskSet子類中采用非常簡潔的方式來描述虛擬用戶的業務測試場景,對虛擬用戶的所有行為(任務)進行組織和描述,並可以對不同任務的權重進行配置。
在TaskSet子類中定義任務信息時,可以采取兩種方式,@task裝飾器和tasks屬性。
采用@task裝飾器定義任務信息時,描述形式如下:
from locust import TaskSet, task
class UserBehavior(TaskSet): @task(1) def test_job1(self): self.client.get('/job1') @task(2) def test_job2(self): self.client.get('/job2')
在如上兩種定義任務信息的方式中,均設置了權重屬性,即執行test_job2的頻率是test_job1的兩倍。采用tasks屬性定義任務信息時,描述形式如下:
若不指定執行任務的權重,則相當於比例為1:1。
from locust import TaskSet def test_job1(obj): obj.client.get('/job1') def test_job2(obj): obj.client.get('/job2') class UserBehavior(TaskSet): tasks = {test_job1:1, test_job2:2} # tasks = [(test_job1,1), (test_job1,2)] # 兩種方式等價
掌握了HttpLocust和TaskSet,我們就基本具備了編寫測試腳本的能力。此時再回過頭來看前面的案例,相信大家都能很好的理解了。在TaskSet子類中除了定義任務信息,還有一個是經常用到的,那就是on_start函數。這個和LoadRunner中的vuser_init功能相同,在正式執行測試前執行一次,主要用於完成一些初始化的工作。例如,當測試某個搜索功能,而該搜索功能又要求必須為登錄態的時候,就可以先在on_start中進行登錄操作;前面也提到,HttpLocust使用到了requests.Session,因此后續所有任務執行過程中就都具有登錄態了。
腳本增強
然而,當面對較復雜的測試場景,可能有的同學還是會感覺無從下手;例如,很多時候腳本需要做關聯或參數化處理,這些在LoadRunner中集成的功能,換到Locust中就不知道怎么實現了。可能也是這方面的原因,造成很多測試人員都感覺難以將Locust應用到實際的性能測試工作當中。
其實這也跟Locust的目標定位有關,Locust的定位就是small and very hackable。但是小巧並不意味着功能弱,我們完全可以通過Python腳本本身來實現各種各樣的功能,如果大家有疑問,我們不妨逐項分解來看。
在LoadRunner這款功能全面應用廣泛的商業性能測試工具中,腳本增強無非就涉及到四個方面:
- 關聯
- 參數化
- 檢查點
- 集合點
先說關聯這一項。在某些請求中,需要攜帶之前從Server端返回的參數,因此在構造請求時需要先從之前請求的Response中提取出所需的參數,常見場景就是session_id。針對這種情況,LoadRunner雖然可能通過錄制腳本進行自動關聯,但是效果並不理想,在實際測試過程中也基本都是靠測試人員手動的來進行關聯處理。
在LoadRunner中手動進行關聯處理時,主要是通過使用注冊型函數,例如web_reg_save_param,對前一個請求的響應結果進行解析,根據左右邊界或其它特征定位到參數值並將其保存到參數變量,然后在后續請求中使用該參數。采用同樣的思想,我們在Locust腳本中也完全可以實現同樣的功能,畢竟只是Python腳本,通過官方庫函數re.search就能實現所有需求。甚至針對html頁面,我們也可以采用lxml庫,通過etree.HTML(html).xpath來更優雅地實現元素定位。
然后再來看參數化這一項。這一項極其普遍,主要是用在測試數據方面。但通過歸納,發現其實也可以概括為三種類型。
- 循環取數據,數據可重復使用:e.g. 模擬3用戶並發請求網頁,總共有100個URL地址,每個虛擬用戶都會依次循環加載這100個URL地址;
- 保證並發測試數據唯一性,不循環取數據:e.g. 模擬3用戶並發注冊賬號,總共有90個賬號,要求注冊賬號不重復,注冊完畢后結束測試;
- 保證並發測試數據唯一性,循環取數據:模擬3用戶並發登錄賬號,總共有90個賬號,要求並發登錄賬號不相同,但數據可循環使用。
通過以上歸納,可以確信地說,以上三種類型基本上可以覆蓋我們日常性能測試工作中的所有參數化場景。
在LoadRunner中是有一個集成的參數化模塊,可以直接配置參數化策略。那在Locust要怎樣實現該需求呢?
答案依舊很簡單,使用Python的list和queue數據結構即可!具體做法是,在WebsiteUser定義一個數據集,然后所有虛擬用戶在WebsiteTasks中就可以共享該數據集了。如果不要求數據唯一性,數據集選擇list數據結構,從頭到尾循環遍歷即可;如果要求數據唯一性,數據集選擇queue數據結構,取數據時進行queue.get()操作即可,並且這也不會循環取數據;至於涉及到需要循環取數據的情況,那也簡單,每次取完數據后再將數據插入到隊尾即可,queue.put_nowait(data)。
最后再說下檢查點。該功能在LoadRunner中通常是使用web_reg_find這類注冊函數進行檢查的。在Locust腳本中,處理就更方便了,只需要對響應的內容關鍵字進行assert xxx in response操作即可。
Locust運行模式
在開始運行Locust腳本之前,我們先來看下Locust支持的運行模式。
運行Locust時,通常會使用到兩種運行模式:單進程運行和多進程分布式運行。
單進程運行模式的意思是,Locust所有的虛擬並發用戶均運行在單個Python進程中,具體從使用形式上,又分為no_web和web兩種形式。該種模式由於單進程的原因,並不能完全發揮壓力機所有處理器的能力,因此主要用於調試腳本和小並發壓測的情況。
當並發壓力要求較高時,就需要用到Locust的多進程分布式運行模式。從字面意思上看,大家可能第一反應就是多台壓力機同時運行,每台壓力機分擔負載一部分的壓力生成。的確,Locust支持任意多台壓力機(一主多從)的分布式運行模式,但這里說到的多進程分布式運行模式還有另外一種情況,就是在同一台壓力機上開啟多個slave的情況。這是因為當前階段大多數計算機的CPU都是多處理器(multiple processor cores),單進程運行模式下只能用到一個處理器的能力,而通過在一台壓力機上運行多個slave,就能調用多個處理器的能力了。比較好的做法是,如果一台壓力機有N個處理器內核,那么就在這台壓力機上啟動一個master,N個slave。當然,我們也可以啟動N的倍數個slave,但是根據我的試驗數據,效果跟N個差不多,因此只需要啟動N個slave即可。
腳本調試
Locust腳本編寫完畢后,通常不會那么順利,在正式開始性能測試之前還需要先調試運行下。
不過,Locust腳本雖然為Python腳本,但卻很難直接當做Python腳本運行起來,為什么呢?這主要還是因為Locust腳本中引用了HttpLocust和TaskSet這兩個類,如果要想直接對其進行調用測試,會發現編寫啟動腳本是一個比較困難的事情。因為這個原因,剛接觸Locust的同學可能就會覺得Locust腳本不好調試。
但這個問題也能克服,那就是借助Locust的單進程no_web運行模式。
在Locust的單進程no_web運行模式中,我們可以通過--no-web參數,指定並發數(-c)和總執行次數(-n),直接在Terminal中執行腳本。
在此基礎上,當我們想要調試Locust腳本時,就可以在腳本中需要調試的地方通過print打印日志,然后將並發數和總執行次數都指定為1,執行形式如下所示。
locust -f locustfile.py --no_web -c 1 -n 1
執行測試
通過這種方式,我們就能很方便地對Locust腳本進行調試了。
Locust腳本調試通過后,就算是完成了所有准備工作,可以開始進行壓力測試了。
Locust是通過在Terminal中執行命令進行啟動的,通用的參數有如下兩個:
-H, --host:被測系統的host,若在Terminal中不進行指定,就需要在Locust子類中通過host參數進行指定;-f, --locustfile:指定執行的Locust腳本文件;
除了這兩個通用的參數,我們還需要根據實際測試場景,選擇不同的Locust運行模式,而模式的指定也是通過其它參數來進行控制的。
單進程運行
no-web
如果采用no_web形式,則需使用--no-web參數,並會用到如下幾個參數。
-c, --clients:指定並發用戶數;-n, --num-request:指定總執行測試;-r, --hatch-rate:指定並發加壓速率,默認值位1。
D:\workSpaces\ApiAutoTest\TestCases\OpsUltraAPITest\MonitorAPITest>locust -f monitorAgent.py --no-web -c1 -n1
[2018-06-05 16:23:02,940] dengshihuang/INFO/locust.main: Starting Locust 0.8.1
[2018-06-05 16:23:02,941] dengshihuang/INFO/locust.runners: Hatching and swarming 1 clients at the rate 1 clients/s...
Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
Total 0 0(0.00%) 0.00
[2018-06-05 16:23:03,941] dengshihuang/INFO/locust.runners: All locusts hatched: WebsiteUser: 1
[2018-06-05 16:23:03,943] dengshihuang/INFO/locust.runners: Resetting stats
[2018-06-05 16:23:03,944] dengshihuang/INFO/locust.runners: All locusts dead
[2018-06-05 16:23:03,944] dengshihuang/INFO/locust.main: Shutting down (exit code 0), bye.
Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
POST /api/push 0 0(0.00%) 0 0 0 | 0 0.00
--------------------------------------------------------------------------------------------------------------------------------------------
Total 0 0(0.00%) 0.00
Percentage of the requests completed within given times
Name # reqs 50% 66% 75% 80% 90% 95% 98% 99% 100%
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
如果采用web形式,,則通常情況下無需指定其它額外參數,Locust默認采用8089端口啟動web;如果要使用其它端口,就可以使用如下參數進行指定。web
-P, --port:指定web端口,默認為8089.
D:\workSpaces\ApiAutoTest\TestCases\OpsUltraAPITest\MonitorAPITest>locust -f monitorAgent.py --port=8089
[2018-06-05 15:36:30,654] dengshihuang/INFO/locust.main: Starting web monitor at *:8089 [2018-06-05 15:36:30,684] dengshihuang/INFO/locust.main: Starting Locust 0.8.1
如果Locust運行在本機,在瀏覽器中訪問http://localhost:8089即可進入Locust的Web管理頁面;如果Locust運行在其它機器上,那么在瀏覽器中訪問http://locust_machine_ip:8089即可。此時,Locust並沒有開始執行測試,還需要在Web頁面中配置參數后進行啟動。
在Locust的Web管理頁面中,需要配置的參數只有兩個:
Number of users to simulate: 設置並發用戶數,對應中no_web模式的-c, --clients參數;Hatch rate (users spawned/second): 啟動虛擬用戶的速率,對應着no_web模式的-r, --hatch-rate參數。
參數配置完畢后,點擊【Start swarming】即可開始測試。
多進程分布式運行
不管是單機多進程,還是多機負載模式,運行方式都是一樣的,都是先運行一個master,再啟動多個slave。
啟動master時,需要使用--master參數;同樣的,如果要使用8089以外的端口,還需要使用-P, --port參數。
D:\workSpaces\ApiAutoTest\TestCases\OpsUltraAPITest\MonitorAPITest>locust -f monitorAgent.py --master --port=8089
[2018-06-05 15:36:30,654] dengshihuang/INFO/locust.main: Starting web monitor at *:8089 [2018-06-05 15:36:30,684] dengshihuang/INFO/locust.main: Starting Locust 0.8.1
啟動后,還需要啟動
啟動slave時需要使用--slave參數;在slave中,就不需要再指定端口了。masterslave才能執行測試任務。
D:\workSpaces\ApiAutoTest\TestCases\OpsUltraAPITest\MonitorAPITest>locust -f monitorAgent.py --slave
[2018-06-05 15:36:30,654] dengshihuang/INFO/locust.main: Starting web monitor at *:8089 [2018-06-05 15:36:30,684] dengshihuang/INFO/locust.main: Starting Locust 0.8.1
D:\workSpaces\ApiAutoTest\TestCases\OpsUltraAPITest\MonitorAPITest>locust -f monitorAgent.py --slave --master-host=<locust_machine_ip>
master和slave都啟動完畢后,就可以在瀏覽器中通過http://locust_machine_ip:8089進入Locust的Web管理頁面了。使用方式跟單進程web形式完全相同,只是此時是通過多進程負載來生成並發壓力,在web管理界面中也能看到實際的slave數量。如果slave與master不在同一台機器上,還需要通過--master-host參數再指定master的IP地址。
測試結果展示
Locust在執行測試的過程中,我們可以在web界面中實時地看到結果運行情況。
相比於LoadRunner,Locust的結果展示十分簡單,主要就四個指標:並發數、RPS、響應時間、異常率。但對於大多數場景來說,這幾個指標已經足夠了。

在上圖中,RPS和平均響應時間這兩個指標顯示的值都是根據最近2秒請求響應數據計算得到的統計值,我們也可以理解為瞬時值。
如果想看性能指標數據的走勢,就可以在Charts欄查看。在這里,可以查看到RPS和平均響應時間在整個運行過程中的波動情況。

除了以上數據,Locust還提供了整個運行過程數據的百分比統計值,例如我們常用的90%響應時間、響應時間中位值;平均響應時間和錯誤數的統計,該數據可以通過Download response time distribution CSV和Download request statistics CSV獲得,數據展示效果如下所示。


