Locust文件就是一般的Python文件。唯一的需求就是它至少需要一個繼承于Locust
的類.
Locust類
Locust類代表一個用戶(如果愿意,也可以是一個準(zhǔn)備出動的蝗蟲)。Locust會為每一個模擬用戶生成一個locust類實例。同時會有一些locust類屬性被定義。
task_set
屬性
task_set
屬性是指向一個定義用戶行為的TaskSet
類,下面會有詳細(xì)的介紹。
min_wait
和max_wait
屬性
除了task_set
屬性,另外一個經(jīng)常被使用的就是min_wait
和max_wait
屬性。是用于各自以毫秒為單位的最小值和最大值,一個模擬用戶將會在每個任務(wù)執(zhí)行時的等待執(zhí)行的時間間隔。min_wait
和max_wait
默認(rèn)設(shè)置為1000
,如果不聲明的話,Locust會默認(rèn)在每個任務(wù)間等待1秒
。
參考下面的代碼,每個用戶將會在每個任務(wù)間等待5至15
秒:
from locust import Locust, TaskSet, task_set
class MyTaskSet(TaskSet):
@task
def my_task(self):
print "executing my_task"
class MyLocust(Locust):
task_set = MyTaskSet
min_wait = 5000
max_wait = 15000
min_wait
和max_wait
屬性可以用于重寫TaskSet
類。
weight
屬性
你可以通過同一個文件來運行兩個locust,就像這樣:
locust -f locust_file.py WebUserLocust MobileUserLocust
如果你更傾向于用這種方法來運行,便可以在這些類中嘗試weight
屬性。比如,就像這樣來定義web用戶比Mobile用戶多3倍
:
class WebUserLocust(Locust):
weight = 3
...
class MobileUserLocust(Locust):
weight = 1
...
host
屬性
host
屬性是到要加載目標(biāo)URL
的前綴(如:"http://google.com")。通常情況下,當(dāng)Locust被啟動時,在命令行中是需要通過--host
來指定的。如果host
屬性在locustfile文件中被聲明,則在命令行中則不需要使用--host
屬性來再次聲明。
TaskSet
類
如果Locust類代表一只準(zhǔn)備出動的蝗蟲,那么你可以說TaskSet
類代表蝗蟲的大腦。每一個Locust類中必須要包含一個指向TaskSet
的task_set
屬性設(shè)置。
TaskSet
就像它的名字一樣,是一個任務(wù)集合。這些任務(wù)是常規(guī)的Python調(diào)用,如果我們壓力測試一個拍賣網(wǎng)站,便可以做這些操作加載啟動頁面
、搜索一些產(chǎn)品
、競標(biāo)
。
當(dāng)一個壓力測試被啟動時,每一個準(zhǔn)備的Locust類實例將會開始執(zhí)行它們的TaskSet
。接下來是每一個TaskSet
找到它的task
并調(diào)用它。它將在min_wait
和max_wait
屬性值之間隨機等待幾毫秒(除非min_wait
和max_wait
被定義在TaskSet中,在這種情況下將會使用TaskSet設(shè)置的值)。然后,它將會找到一個新task
并調(diào)用,再次等待,一直這樣持續(xù)下去。
聲明task
對于TaskSet
來說,典型的聲明task
的方法是直接使用task
。
參考這個例子:
from locust import Locust, TaskSet, task
class MyTaskSet(TaskSet):
@task
def my_task(self):
print "Locust instance (%r) executing my_task" % (self.locust)
class MyLocust(Locust):
task_set = MyTaskSet
@task 將會獲取一個可選的權(quán)重參數(shù),用于說明任務(wù)執(zhí)行的比率。在下面的例子中 task2 將會比 task1 執(zhí)行的次數(shù)多兩倍:
from locust import Locust, TaskSet, task
class MyTaskSet(TaskSet):
min_wait = 5000
max_wait = 15000
@task(3)
def task1(self):
pass
@task(6)
def task2(self):
password
class MyLocust(Locust):
task_set = MyTaskSet
task
屬性
使用@task
操作符來聲明task是一種便捷的方法,并且經(jīng)常是最好的方式。然而,也可以定義TaskSet
中的task通過設(shè)置tasks屬性(使用操作符@task比tasks屬性更流行)。
tasks 屬性不是python列表的調(diào)用就是一個<callbale:int>字典。tasks是python調(diào)用接收執(zhí)行task的TaskSet類實例參數(shù)。下面是一個極其簡單的示例(不會影響任何測試):
from locust import Locust, TaskSet
def my_task(l):
pass
class MyTaskSet(TaskSet):
tasks = [my_task]
class MyLocust(Locust):
task_set = MyTaskSet
如果task屬性被定義在列表中,每次任務(wù)被執(zhí)行時,將會隨機
從 tasks 屬性中選擇。如果 tasks 是一個帶有關(guān)健字和數(shù)值調(diào)用的字典,被執(zhí)行的任務(wù)將會被隨機選擇以數(shù)字的比率來執(zhí)行。就像下面的這樣:
{my_task: 3, another_task:1}
my_task 將會比 another_task 多執(zhí)行三倍。
TaskSet
可以嵌套
TaskSet
有一個重要的屬性就是可以被嵌套,由于真實的網(wǎng)站是有一定的業(yè)務(wù)層級結(jié)構(gòu)的,并帶有一些子模塊。嵌套的TaskSet將會幫助我們來定義更加真實的用戶行為。比如,我們可以定義TaskSet
像下面的結(jié)構(gòu)
- Main user behaviour
- Index page
- Forum page
- Read thread
- Reply
- New thread
- View next page
- Read thread
- Browser categories
- Watch movies
- Filter movies
- About page
嵌套TaskSet的方法就像使用task
屬性來說明task一樣,但代替參考Python函數(shù),你可以參考下面的TaskSet
:
class ForumPage(TaskSet):
@task(20)
def read_thread(self):
pass
@task(1)
def new_thread(self):
pass
@task(5)
def stop(self):
self.interrupt()
class UserBehaviour(TaskSet):
tasks = {ForumPage:10}
@task
def index(self):
pass
在上面的示例中,當(dāng)UserBehaviour的TaskSet執(zhí)行時,F(xiàn)orumPage會被選中來執(zhí)行,接下來ForumPage的TaskSet將會開始執(zhí)行。ForumPage的TaskSet會找到它的tasks并執(zhí)行它,再等待,一直這樣持續(xù)下去。
針對上面的例子中有一個重要的事情要注意,就是在ForumPage頁面中的Stop方法中調(diào)用self.interrupt()
。這個做的事情是停止執(zhí)行ForumPage任務(wù)并在UserBehaviour實例中繼續(xù)執(zhí)行。如果在ForumPage中,我們沒有調(diào)用interrupt()
方法,除非被調(diào)用否則Locust不會調(diào)用ForumPage任務(wù)。但通過interrupt
函數(shù) ,我們可以結(jié)合weight
任務(wù)來定義模擬用戶離開Forum.
也可以在類內(nèi)部聲明嵌套TaskSet,通過使用@task
操作符,像聲明正常的task一樣:
class MyTaskSet(TaskSet):
@task
class SubTaskSet(TaskSet):
@task
def my_task(self):
pass
on_start
函數(shù)
TaskSet可以選擇聲明on_start
函數(shù)。如果這樣的話,當(dāng)模擬用戶開始執(zhí)行TaskSet類時,函數(shù)被調(diào)用。
關(guān)聯(lián)Locust
實例,或父TaskSet
實例
TaskSet
實例有locust
屬性來指向它的Locust
實例,屬性parent
用來指向它的父TaskSet
(它會指向Locsut實例,在基類TaskSet中)。
HTTP
請求
到現(xiàn)在為止,我們僅覆蓋了一個Locsut用戶的部分任務(wù)計劃。為了真實的壓力測試一個系統(tǒng)時,我們需要生成HTTP
請求。為了幫助我們實現(xiàn)這個功能,可以使用HttpLocust
類。當(dāng)使用這個類時,每一個實例將會獲得一個用于生成Http
請求的HttpSession
實例的client
屬性。
class HttpLocust
表示一個用于壓力測試的孵化和攻擊系統(tǒng)的HTTP
用戶
。
這個用戶的行為通過task_set
屬性來定義,直接指向TaskSet
類。
這個類創(chuàng)建一個client
屬性,在初始化時,HTTP
客戶端支持為每一個用戶在請求間保存session。
client=None
HttpSession實例在Locust初始化時被創(chuàng)建。client
支持cookies
,同時在請求間會保存session。
當(dāng)從HttpLocust
類繼承時,我們可以使用client
屬性來對服務(wù)器生成HTTP
請求。下面是一個locust文件示例用于在一個網(wǎng)站的兩個URL / 和 /about/ 。
from locust import HttpLocust, TaskSet, task
class MyTaskSet(TaskSet):
@task(2)
def index(self):
self.client.get('/')
@task(1)
def about(self):
self.client.get('/about/')
class MyLocust(HttpLocust):
task_set = MyTaskSet
min_wait = 5000
max_wait = 15000
使用上面的Locust類,每一個模擬用戶將間隔5-15秒內(nèi)請求,并且/
將會比/about/
請求數(shù)量多2倍
。
細(xì)心的讀者會發(fā)現(xiàn)有一些奇怪,我們使用self.client
關(guān)聯(lián)HttpSession
實例,而不是TaskSet
,也不是self.locust.client
。我們可以這樣做,是因為TaskSet
類有一個屬性調(diào)用client
簡單的返回self.locust.client
。
使用HTTP client
每一個HttpLocust
實例在client
屬性中有一個HttpSession
實例。HttpSession
類實際上是requests.Session
的子類,可使用get
post
put
delete
head
patch
和 options
方法來生成HTTP
請求,用于Locust的數(shù)據(jù)統(tǒng)計。HttpSession
實例在請求間維護(hù)cookies,因此可用于登錄網(wǎng)站并保存session在請求之間。client
可以通過Locust實例的TaskSet實例來關(guān)聯(lián),因此很容易獲取client并在任務(wù)中生成HTTP請求。
下面是一個生成GET
請求到 /about 路徑的示例(在這里,我們可以假設(shè) self 是一個TaskSet
或 HttpLocust
類的實例):
response = self.client.get("/about")
print "Response staus code:", response.status_code
print "Response content:", response.content
下面是一個生成POST
請求的示例:
response = self.client.post("/login", {"username": "testuser", "password": "password"})
安全模式
HTTP
client被配制運行在safe_mode
。這樣做是任何請求在連接超時、錯誤、相似失敗時將不會拋出異常,而是返回一個空的假Response對象。請求將會在Locust統(tǒng)計中算做一次失敗。返回假Response內(nèi)容屬性將會被設(shè)置為None,并且它的status_code將會是0.
手動設(shè)置請求是成功或失敗
默認(rèn)情況下,請求被標(biāo)記為失敗除非在返回狀態(tài)碼是OK(2XX)。大部分時間內(nèi),這個默認(rèn)就是你所需要的。然而,比如在測試一個URL節(jié)點,你期待返回狀態(tài)碼為404,或者測試一個即使錯誤發(fā)生也會返回200的系統(tǒng),因此,需要手工控制locust來判斷是成功還是失敗。
一個可以生成失敗請求,即使當(dāng)響應(yīng)代碼是OK
,通過使用catch_response
參數(shù)和with
語法:
with client.get("/", catch_response = True) as response:
if response.content != "Success":
response.failure("Got wrong response")
就像一個可以使用響應(yīng)為OK
的請求當(dāng)做失敗來處理,一個方法就是可以使用catch_response
參數(shù)和with
語法來讓請求HTTP錯誤時,仍然統(tǒng)計數(shù)據(jù)為成功:
with client.get("/does_not_exist/", catch_response = True) as response:
if response.status_code = 404:
response.success()
使用動態(tài)參數(shù)來分組URL請求
針對網(wǎng)站,有一個常用的功能是獲取URL中包含一些動態(tài)參數(shù)的頁面數(shù)據(jù)。通常情況下,在Locust統(tǒng)計中,使用動態(tài)分組在URL中是很有意義的。通過name
參數(shù)來給HttpSession
傳遞不同的請求方法。
比如:
# Statistics for these requests will be grouped under: /blog/?id=[id]
for i in range(10):
client.get("/blog?id=%i" % i, name = "/blog?id=[id]")
常用庫
通常,大家想分享多個locust文件用于分享常用的庫。在這種情況下,定義項目根目錄
用于調(diào)用Locsut是很重要的,建議將所有的locust文件有些話在項目的根目錄中。
一個平鋪的結(jié)構(gòu)像下面這樣:
- 項目根目錄
commonlib_conf.py
commonlib_auth.py
locustfile_web_app.py
locsutfile_api.py
locustfile_ecommerce.py
locust文件可以調(diào)用常用的庫通過使用import commonlib_auth
.然而,這種方法不會從locust文件中,清晰分辨出常用庫。
子文件夾可以有一個清晰的方法(查看下面的示例),但是locust僅會有運行l(wèi)ocsut文件的位置引用相關(guān)的模塊。如果你想從你的根目錄導(dǎo)入(如,你運行l(wèi)ocust命令的位置),確保在任何locust文件中添加常用庫前有代碼sys.path.append(os.getcwd())
,會生成導(dǎo)入根目錄(如,當(dāng)前工作目錄)。
- project root
__init__.py
-
common/
__init__.py
config.py
auth.py
-
locustfiles/
__init__.py
web_app.py
api.py
ecommerce.py
使用上面的項目結(jié)構(gòu),你的locust文件可以通過下面代碼導(dǎo)入常用的庫:
sys.path.append(os.getcwd())
import common.auth