簡(jiǎn)介
Locust是一個(gè)使用Python編寫(xiě)的可擴(kuò)展、分布式的開(kāi)源性能測(cè)試工具。
優(yōu)點(diǎn)
- 相比于Jmeter、LoadRunner這種基于GUI的工具而言,Locust使用Python語(yǔ)言來(lái)描述測(cè)試場(chǎng)景使模擬用戶行為變得更加靈活和簡(jiǎn)潔,除了Http(s)協(xié)議之外,Locust可以測(cè)試任意協(xié)議的系統(tǒng),只需要實(shí)現(xiàn)Python調(diào)用對(duì)應(yīng)協(xié)議的庫(kù)進(jìn)行請(qǐng)求即可(類(lèi)似HttpLocust類(lèi))。
- Locust的并發(fā)機(jī)制采用協(xié)程的方式,相比于進(jìn)程和線程減少了系統(tǒng)級(jí)資源調(diào)度,因此單機(jī)的產(chǎn)生的并發(fā)能力相比于LoadRunner、jmeter得到了大幅的提升。
安裝
pip install locust
Locust腳本編寫(xiě)
import queue
from locust import HttpLocust, TaskSet, task
from locust.clients import HttpSession
from sign import ParserData
class UserBehavior(TaskSet):
parser_data = ParserData() # 解析接口傳參類(lèi)
_client = 20
version = 119
user_info = None
@staticmethod
def get_user_info(response):
r = response.json().get('content')
return {
'market_id': r.get('marketId'),
'token': r.get('token')
}
def on_start(self):
try:
user, password = self.locust.users.get_nowait()
except queue.Empty:
print('test data run out. test ended.')
exit(0)
client = HttpSession(base_url='http://login.xxxx.cn')
data = self.parser_data(loginName=user,
password=password,
client=self._client,
version=self.version)
response = client.post(url='/login', data=data)
self.user_info = self.get_user_info(response)
self.locust.users.put_nowait((user, password))
@task(2)
def index(self):
url = '/index'
data = self.parser_data(market_id=self.user_info['market_id'],
client=self._client,
version=self.version,
pnum='3')
headers = {'Authorization': 'Barer:' + self.user_info['token'],
'Accept': 'application/vnd.hs-api.v1+json'}
with self.client.post(url=url, data=data, headers=headers,
verify=False, catch_response=True) as response:
if response.status_code == 200:
response.success()
else:
response.failure('http error.')
@task(1)
def shop_car_list(self):
url = '/shopCar/list'
data = self.parser_data(market_id=self.user_info['market_id'],
ischaidan='1',
client=self._client,
version=self.version)
headers = {'Authorization': 'Bearer:' + self.user_info['token'],
'Accept': 'application/vnd.hs-api.v1+json'}
with self.client.post(name='ShopCar', url=url, data=data, headers=headers,
verify=False, catch_response=True) as response:
if response.status_code != 200 or "失敗" in response.text:
response.failure('response error.')
else:
response.success()
class Stay(TaskSet):
index = 0
def on_start(self):
self.index += 1
@task
def get_error(self):
response = self.client.get('/1', name='error', allow_redirects=False,
verify=False, catch_response=True)
if response.status_code == 200:
response.success()
else:
response.failure('http error.')
@task
def logout(self):
self.interrupt()
class User(TaskSet):
tasks = {Stay: 1}
@task(1)
def user(self):
self.client.get('/', verify=False)
class WebsiteUser(HttpLocust):
task_set = UserBehavior
host = 'https://xxxx.api.xxxx.cn'
min_wait = 1000
max_wait = 3000
users = queue.Queue()
users.put_nowait(('user1', '1232'))
users.put_nowait(('user2', '1234'))
users.put_nowait(('user3', '1321'))
weight = 3
stop_timeout = 20
class WebsiteU(HttpLocust):
task_set = User
host = 'https://www.baidu.com'
min_wait = 0
max_wait = 0
weight = 1
stop_timeout = 60
簡(jiǎn)單解釋下:
- UserBehavior和WebsiteUser兩個(gè)類(lèi)實(shí)現(xiàn)測(cè)試場(chǎng)景使用3個(gè)用戶賬號(hào),每個(gè)用戶會(huì)去先登錄,然后分別去查看首頁(yè)和進(jìn)入購(gòu)物車(chē)頁(yè)面
- 首先導(dǎo)入了需要用到的類(lèi),HttpLocust類(lèi)為L(zhǎng)ocust子類(lèi),模擬客戶端的請(qǐng)求類(lèi),Taskset類(lèi)為任務(wù)集類(lèi),task為任務(wù)裝飾器。
Taskset
UserBehavior為T(mén)askset子類(lèi),該類(lèi)主要用來(lái)定義每個(gè)虛擬用戶的操作行為
Taskset子類(lèi)中可以定義一個(gè)on_start方法在正式開(kāi)始測(cè)試前只執(zhí)行一次,相當(dāng)與初始化操作(這里說(shuō)的只執(zhí)行一次是每個(gè)虛擬用戶都會(huì)去執(zhí)行一次),比如獲取登錄token等操作。
teskset類(lèi)中的每個(gè)任務(wù)都需要用@task(weight=1)裝飾器去裝飾為一個(gè)任務(wù),weight為執(zhí)行的權(quán)重,如果不裝飾,locust不會(huì)認(rèn)為這是一個(gè)任務(wù),UserBehavior類(lèi)中@task(1)、@task(2)裝飾器表示3個(gè)用戶里面有2個(gè)用戶去模擬執(zhí)行index方法,有1個(gè)用戶去執(zhí)行shop_car_list方法
taskset類(lèi)中self.client屬性請(qǐng)求操作時(shí)傳入catch_response參數(shù),設(shè)置為T(mén)rue,可以標(biāo)記響應(yīng)結(jié)果為成功或失敗,即使響應(yīng)是成功的,也可以標(biāo)記為失敗,默認(rèn)為Fasle
taskset類(lèi)中self.client屬性請(qǐng)求操作時(shí)有一個(gè)name參數(shù),當(dāng)設(shè)置了name的值時(shí),最后的請(qǐng)求結(jié)果展示中name字段會(huì)顯示這里定義的name值,相當(dāng)與給這個(gè)方法起了一個(gè)別名.
taskset類(lèi)中interrupt(reschedule=True)方法在頂層的taskset類(lèi)(即被指定到Locust子類(lèi)中的taskset)中不可用,reschedule為T(mén)rue時(shí),從被嵌套的任務(wù)中出來(lái)立即執(zhí)行新的任務(wù),如果為False從被嵌套的任務(wù)中出來(lái)會(huì)等待min_time-max_time之間的隨機(jī)時(shí)間,然后再執(zhí)行新的任務(wù),這個(gè)方法主要用來(lái)跳出嵌套的任務(wù)集
HttpLocust
WebsiteUser為HttpLocust子類(lèi),該類(lèi)是用來(lái)模擬用戶的類(lèi),定義了一些用戶信息,及請(qǐng)求方式
HttpLocust子類(lèi)中task_set屬性用來(lái)指定模擬用戶執(zhí)行的操作,即Taskset子類(lèi)
HttpLocust子類(lèi)中的host屬性為被測(cè)試系統(tǒng)的host,當(dāng)命令行中沒(méi)有指定--host參數(shù)時(shí),此屬性會(huì)生效
HttpLocust子類(lèi)中的min_wait、max_weight為最大等待時(shí)間和最小等待時(shí)間,每個(gè)請(qǐng)求會(huì)從這兩個(gè)時(shí)間間隔中隨機(jī)取一個(gè)時(shí)間等待,相當(dāng)用戶實(shí)際操作系統(tǒng)時(shí)每個(gè)動(dòng)作的思考時(shí)間。若測(cè)試單個(gè)接口則對(duì)應(yīng)的值都設(shè)置為0即可。如果Taskset類(lèi)中定義了min_wait、max_weight則會(huì)覆蓋Locust子類(lèi)中定義的值。單位ms,默認(rèn)值1000ms
HttpLocust子類(lèi)中的stop_timeout屬性為執(zhí)行測(cè)試的時(shí)間,單位為s
HttpLocust子類(lèi)中的weight為該類(lèi)執(zhí)行的權(quán)重,當(dāng)有多個(gè)子類(lèi)時(shí)生效,如WebsiteUser、WebsiteU兩個(gè)HttpLocust子類(lèi)中weight值分別為3和1.
Locust默認(rèn)單機(jī)單進(jìn)程運(yùn)行,此模式下并不能充分利用單機(jī)的多處理器,可使用分布式運(yùn)行,即開(kāi)啟一個(gè)master,n個(gè)slave(n為處理器個(gè)數(shù)),master負(fù)責(zé)啟動(dòng)Locust的web服務(wù)和任務(wù)分發(fā),不會(huì)產(chǎn)生壓力,slave主要負(fù)責(zé)產(chǎn)生壓力
運(yùn)行模式:
no-web模式
no_web模式指在命令行中直接運(yùn)行
locust -f load_test.py -c 1 -r 1 -n 1
- -f 指定要運(yùn)行的Locust性能測(cè)試文件
- -c 指定模擬的并發(fā)用戶數(shù)
- -r 指定每秒的啟動(dòng)用戶數(shù)
- -n 指定運(yùn)行次數(shù)
- -t 指定運(yùn)行的時(shí)間,例如300s,1m,1h
寫(xiě)完腳本調(diào)試時(shí)可在該模式下運(yùn)行
單機(jī)單進(jìn)程運(yùn)行
locust -f load_test.py
分布式運(yùn)行
分布式運(yùn)行,有單機(jī)多進(jìn)程運(yùn)行和多機(jī)多進(jìn)程運(yùn)行兩種
locust -f load_test.py --master master模式下啟動(dòng)locust
locust -f load_test.py --slave 啟動(dòng)一個(gè)locust slave節(jié)點(diǎn),單機(jī)多進(jìn)程模式
locust -f load_test.py --slave --master-host=192.168.105.11 啟動(dòng)一個(gè)locust slave節(jié)點(diǎn),多機(jī)模式下
no_web模式下運(yùn)行

web模式

- Number of users to simulate:需要模擬的虛擬用戶個(gè)數(shù)
-
Hatch rate (users spawned/second):?jiǎn)?dòng)虛擬用戶的速率,每秒產(chǎn)生出多少個(gè)用戶數(shù)
QQ圖片20171109190334.png - 顯示并發(fā)數(shù)、響應(yīng)時(shí)間、異常率、每秒請(qǐng)求數(shù)等
-
reqs/sec(每秒請(qǐng)求數(shù))為根據(jù)最近2s請(qǐng)求數(shù)據(jù)計(jì)算得到的數(shù)據(jù),即瞬時(shí)值
QQ圖片20171109190430.png -
顯示rps、響應(yīng)時(shí)間、并發(fā)數(shù)在整個(gè)測(cè)試運(yùn)行中的走勢(shì)圖
QQ圖片20171109190455.png - 顯示測(cè)試過(guò)程中出現(xiàn)的所有失敗的請(qǐng)求
Exceptions顯示測(cè)試過(guò)程中出現(xiàn)的異常
Download Date提供測(cè)試結(jié)果csv文件的下載
測(cè)試數(shù)據(jù):
- locust子類(lèi)中設(shè)置的數(shù)據(jù)是全局的,為list時(shí),使用自增方式取數(shù)據(jù),并發(fā)運(yùn)行時(shí)會(huì)出現(xiàn)取到的數(shù)據(jù)重復(fù)的情況,如果對(duì)數(shù)據(jù)唯一性有要求,使用python的queue隊(duì)列的數(shù)據(jù)結(jié)構(gòu)即可
- taskset子類(lèi)中設(shè)置的數(shù)據(jù)是局部的,即每一個(gè)虛擬用戶都會(huì)有一個(gè)屬于自己的這個(gè)變量
queue數(shù)據(jù)結(jié)構(gòu)
隊(duì)列形式
q=queue.Queue(maxsize=3) 先進(jìn)先出隊(duì)列
q.put(1) 向隊(duì)列中存數(shù)據(jù)
q.get() 向隊(duì)列中取數(shù)據(jù)
q.put_nowait() 相當(dāng)于q.put(1, block=False),當(dāng)q隊(duì)列滿了之后put會(huì)觸發(fā)queue.Full異常
q.get_nowait() 相當(dāng)與q.get(block=False),當(dāng)q隊(duì)列為空之后get會(huì)觸發(fā)queue.Empty異常


