背景:
目前網上的教程基本都是1.0之前的,locust叢1.0版本就發生了較多的變化,網上的教程基本不可用了。本文基於locust最新版本2.5.1,作為筆記也作為入門教程分享。
(備注:只講 框架的使用 ,不涉及性能測試理論知識)
一、什么是Locust
Locust 是一種易於使用、可直接使用pyhton編寫腳本運行且可擴展的性能測試工具。
二、特點
- 直接使用python 編寫測試腳本
- 支持分布式和可擴展-支持數十萬並發用戶
- 提供友好的web界面
- 可以測試任何系統
- 小且靈活
locust 的web 運行界面
locust 提供了友好的web界面用於監控性能測試過程中的相關數據
啟動頁
測試監控界面
type | 請求類型 | |
Name | 請求名稱 | 這個可以自定義,傳name參數即可 |
Requests | 當前已完成的請求數量 | |
Fails | 失敗的請求數量 | |
Requests | 當前已完成的請求數量 | |
Median | 響應時間的中間值,即50%的響應時間在這個數值范圍內,單位為毫秒 | |
90%ile | 90%的響應時間在正態分布平均值下方,即小於這個值 | |
Min | 最小響應時間,單位為毫秒 | |
Max | 最大響應時間,單位為毫秒 | |
average Size | 平均每個請求的數據量,單位為字節 | |
current RPS | 每秒鍾處理請求的數量 |
RPS(TPS)
響應時間
Users
測試任務統計
支持下載測試報告
進程信息,這里啟動了5個進程,用戶數為500,被平均分給了5個進程,
三、編寫任務
名詞解釋:
任務:簡單的理解就是,你想要你腳本的虛擬用戶去做哪些事,比如請求某一個接口,或者執行某一個事件。
用戶:可以理解為,執這個任務的實例主體,或者在locust 中,也可以認為是一群蝗蟲
舊版本使用的是TaskSet進行任務或者請求編寫,然后在使用HttpLocust進行任務或者請求聲明,以及基礎屬性配置,一般會進行思考時間、host、task_set設置任務類等;新版本將HttpLocust重寫成User,並且HttpUser繼承於User,所以推薦使用HttpUser進行任務聲明;另外TaskSet中的task_set已被刪除,如果仍要使用TaskSet,則需要在HttpUser中使用tasks進行存儲
TaskSet 類 進行任務編寫
TaskSet是定義用戶將執行的一組任務的類,然后通過虛擬用戶調用並執行這一組任務
單個任務集合:
from locust import TaskSet, task,User import os ''' 通過TaskSet 編寫任務
''' class Task_1(TaskSet): @task def task1(self): print('task1----------') @task def task2(self): print('task2----------') @task def task3(self): print('task3----------') class task_conf(User): tasks = [Task_1] if __name__ == '__main__': os.system('locust -f TaskSet定義用戶任務.py --web-host "127.0.0.1"')
多個任務集合
切記 不要忘記調用調用 self.interrupt() 以跳出當前任務集
from locust import TaskSet, task,User import os ''' TaskSet類定義了每個用戶的任務集合, 注意:如果存在多個任務集合,需要在每個任務集合中調用interrupt()方法, 否則用戶一旦進入到這個用戶,則無法跳出這個任務集合(會一直執行這個任務集) ''' class Task_1(TaskSet): @task def task1(self): print('task1----------') @task def task2(self): print('task2----------') @task def task3(self): print('task3----------') #調用interrupt() 非常重要,否則用戶將難以自拔 @task def logout(self): self.interrupt() class Task_2(TaskSet): @task def task21(self): print('task21----------') @task def task22(self): print('task22----------') @task def task23(self): print('task23----------') @task def logout(self): self.interrupt() class task_conf(User): tasks = [Task_1,Task_2]
if __name__ == '__main__': os.system('locust -f TaskSet定義用戶任務_多個.py --web-host "127.0.0.1"')
User類 編寫任務
User 執行測試任務的主體類,用戶的行為由其任務定義
from locust import TaskSet, task,User import os ''' 通過TaskSet 定義任務 ''' class Task_1(User): @task def task1(self): print('task1----------') @task def task2(self): print('task2----------') @task def task3(self): print('task3----------') if __name__ == '__main__': os.system('locust -f User定義用戶任務.py --web-host "127.0.0.1"')
任務屬性
測試開始時,locust將為每一個模擬用戶創建一個User類的實例,每個實例都在他們自己的微線程中執行,當這些用戶運行時,開始選中要執行的任務,休眠一段時間,然后選一個新的任務,繼續執行,以此類推。
這里我們為用戶添加任務的方式有兩種
- 使用@task 裝飾器
- 設置tasks屬性
@task
通過@task 定義用戶任務 任務 ,同時還可以通過@task(int) 設置每個任務的權重
from locust import task,User
import os # 通過@task 定義用戶任務 任務 ,同時還可以通過@task(int) 設置每個任務的權重 class task_2(User): @task(2) def task1(self): print('執行任務1') @task(2) def task2(self): print('執行任務2') @task(1) def task3(self): print('執行任務3') if __name__ == '__main__': os.system('locust -f task裝飾器聲明任務.py --web-host="127.0.0.1"')
tasks
tasks屬性可以是一個 Task 列表,也可以是一個 {'Task':int} 的dict, Task可以是 一個測試方法或者函數,也可以是任務集合TakSet類
以User類為例
from locust import User
import os ''' 繼承TaskSet,不按順序執行任務, ''' # 通過tasks 定義測試任務 class task_1(User): def task1(self): print('執行任務1') def task2(self): print('執行任務2') def task3(self): print('執行任務3') # 通過tasks 定義測試任務 並設置對應任務執行權重 tasks = [task1, task2, task3] # tasks = {task1:1,task2:2,task3:2} if __name__ == '__main__': os.system('locust -f tasks屬性聲明用戶任務.py --web-host="127.0.0.1"')
任務標記@tag
可以通過標記的方式,執行負載測試時,讓虛擬用戶只執行指定的測試任務
from locust import User,tag
'''
設置tag,用於設置用戶 執行指定標記的任務
'''
class myUser(User): @tag('回歸') def task_1(self): print('回歸任務') @tag('預發') def task_2(self): print('任務2') @tag('回歸','預發') def task_3(self): print('回歸任務+預發任務') tasks = [task_1,task_2,task_3] if __name__ == '__main__': import os os.system('locust -f locust_user_tag.py --tags 回歸 --web-host="127.0.0.1"')
User類的一些通用屬性
Locust 將為每個用戶生成一個 User 類的實例。用戶類可以定義一些通用屬性。
wait_time 屬性:
用於模擬用戶每次執行任務后的延遲時間(等同思考時間),如果未指定,則下一個任務將在完成后立即執行。
- constant(x) 固定時間
- between(x,y) 最小值和最大值之間的隨機時間
- constant_packing(x) 每x秒 最多運行一次任務
- constant_throughtput(x) 每秒最多運行x次
constant
from locust import constant, task,User import os ''' wait_time = constant() 等待一個固定的時間 ''' class Task_1(User): @task def task1(self): print('task1----------') @task def task2(self): print('task2----------') @task def task3(self): print('task3----------') # 用戶執行每個任務,會固定等待5s wait_time = constant(5) if __name__ == '__main__': os.system('locust -f task_wait_constant.py --web-host "127.0.0.1"')
between
from locust import between, task, User import os ''' wait_time = between() 等待一個最小值和最大值之間的隨機時間 不含最大時間 ''' class Task_1(User): @task def task1(self): print('task1----------') @task def task2(self): print('task2----------') @task def task3(self): print('task3----------') # 用戶執行每個任務,會固定等待[1,5) 秒 wait_time = between(1,5) if __name__ == '__main__': os.system('locust -f task_wait_between.py --web-host "127.0.0.1"')
weight權重屬性
用於設置特定類型的用戶的 權重值,在未設置的情況下,locust將為每個用戶類生產相同數量的用戶實例
比如app用戶是web用戶的3倍
from locust import User # app用戶數量是web用戶數量的3倍 class appUser(User): weight = 3 def task_1(self): print('app任務1') def task_2(self): print('app任務2') tasks = [task_1,task_2] class webUser(User): weight = 1 def task_A(self): print('web任務1') def task_B(self): print('web任務2') tasks = [task_A, task_B] if __name__ == '__main__': import os os.system('locust -f locust_user_weight.py --web-host="127.0.0.1"')
host 主機屬性:
用於加載主機的url 前綴,(比如:http://baidu.com)
也可以在 webui中指定,或者命令參數中指定--host
from locust import task,HttpUser import os ''' 主機屬性 host ''' class task_s(HttpUser): host = 'http://wthrcdn.etouch.cn' @task def task_1(self): url = '/weather_mini?city=杭州' # body = {'city':'杭州'} res = self.client.request(method='get',url=url) print(res.json()) if __name__ == '__main__': os.system('locust -f locust_host.py --web-host="127.0.0.1"')
前后置方法 on_start和on_stop方法
用戶將 on_start在它開始運行時調用它的方法
on_stop在它停止運行時調用它的方法。
from locust import User,constant ''' 每個虛擬用戶 執行前 會執行一次該方法(在一次測試結束后,只會執行一次) ''' class myuser(User): wait_time = constant(2) def on_start(self): print('登錄') def on_stop(self): print('登出') def task_1(self): print('任務1') tasks = [task_1] if __name__ == '__main__': import os os.system('locust -f locust_start_stop.py --web-host="127.0.0.1"')
HttpUser類
HttpUser是最常用的 User ,它通過client 發送Http請求,client是httpsession的一個實例(requests模塊的session),故在locust中,通過client 發送的請求,具有天然的保持會話能力。
發送http請求
from locust import HttpUser,task
import os class TaskA(HttpUser):
#登錄 def on_start(self): url = 'https://www.processon.com/login/quick_login' data = {'type': 'account_login', 'login_email': 12345678901, 'login_password': '123456', } r = self.client.request('post', url=url, data=data) #登錄成功調用個人信息接口 @task def test_login(self): url = 'https://www.processon.com/setting/account' r = self.client.request('post', url=url) print(r.text) if __name__ == '__main__': os.system('locust -f http_通過session請求.py --web-host="127.0.0.1"')
響應檢查
HttpUser 支持設置響應檢查點
使用catch_response參數、with語句和對response.failure() 標記為失敗
from locust import HttpUser, task
import os from json import JSONDecodeError import jsonpath class task_s(HttpUser): @task def task_1(self): get_url = 'http://wthrcdn.etouch.cn/weather_mini?city=杭州' with self.client.request(method='get', url=get_url,catch_response=True) as response: try: # 獲取響應內容的status 字段值 status = response.json()['status'] # 獲取響應時間 # rt = response.elapsed.total_seconds() # print(rt) # 如果status為1000則為成功,否則為失敗 if status == 1000: response.success() # if rt <= 0.001: # response.success() # else: # response.failure('響應超時') else: response.failure('非法的參數') except JSONDecodeError: response.failure('Response could not be decoded as JSON') if __name__ == '__main__': os.system('locust -f locust_HttpUse_檢查響應.py --web-host="127.0.0.1"')
FastHttpUser (locust大殺器)
Locust 的默認 HTTP 客戶端使用python-requests,而requests庫是一個同步阻塞庫(每個請求發送完成之后都在等待響應的回來,這在高並發場景下是致命的)
正因為如此,Locust 還附帶FastHttpUser使用geventhttpclient代替。它提供了一個非常相似的 API,並且使用的 CPU 時間顯着減少,通常情況下將給定硬件上每秒的最大請求數增加了 5 到 6 倍。
使用上:只需繼承 FastHttpUser 就可以了,其他和 HttpUser 沒有什么太大的差別
from locust import task,FastHttpUser import os import logging as log class task_s(FastHttpUser): host = 'http://wthrcdn.etouch.cn' @task def task_1(self): get_url = '/weather_mini?city=杭州' self.client.request(method='get', path=get_url) # with self.client.request(method='get', path=get_url,catch_response=True) as response: # status = response.json()['status'] # if status ==1000: # response.success() # # else: # response.failure('請求失敗') if __name__ == '__main__': os.system('start locust -f 通過FastHttpUser多進程發送請求.py --master --web-host="127.0.0.1"') for i in range(8): os.system('start locust -f 通過FastHttpUser多進程發送請求.py --worker')
並發數1500,tps在9000左右
對比jmeter,並發數1500, tps在 8000左右
四、運行方式
命令參數運行
#進入當前模塊文件目錄,直接輸入 locust #非當前文件目錄 locust -f xx/xx.py #指定host locust -f xx/xx.py --host=https://www.cnblogs.com #指定locust webui 界面地址, locust -f xx/xx.py --web-host ="127.0.0.1" #不帶ui界面運行, locust -f xx/xx.py --headless -u 1000 -r 100 --run-time 5m --headless 沒有webui的情況下運行 -u 要生成的用戶數 -r 每秒啟動的用戶數 --run-time 5m 設置測試執行時間 這里是5分鍾,時間到了就會停止運行
#多進程運行 主模式啟動 8089端口用於web界面 ,5557用於從機交互 locust -f xx/xx.py --master 啟動一個進行執行 locust -f xx/xx.py --worker --master-host=192.168.0.14 (如果單機器運行,則可以省略后面的參數 --master-host=xxx.xxx.xxx.xxx)
配置文件方式運行
locust 支持配置文件方式運行
(本質上也是命令行運行,只不過是把命令參數寫在了配置文件中,防止每次輸的時候 寫錯)
默認情況下,locust在~./.locust.conf 和 ./locust.conf ,還可以使用 --config 來指定配置文件運行
# -*- coding: UTF-8 -* ''' # locust 默認讀取配置文件的優先級順序(覆蓋讀取) # ~/locust.conf -> ./locust.conf ->(file specified using --conf) # 注意:如果同時存在 兩個配置文件,locust.conf 和 diy.conf # 此時使用 locust --config = diy.conf 讀取的將還是locust.conf ''' # 要運行的py文件 locustfile = ./my_locust_file.py # 設置是否帶webui運行 headless = false # 設置weiui的地址 web-host= 127.0.0.1 # 設置 host host = http://wthrcdn.etouch.cn # 設置虛擬用戶數 users = 500 # 設置每秒增加的用戶數 spawn-rate = 10 # 設置運行時間,滿足設置的運行時間后,將停止運行 run-time = 1m