1. 介紹

性能工具對比
LoadRunner 是非常有名的商業性能測試工具,功能非常強大。使用也比較復雜,目前大多介紹性能測試的書籍都以該工具為基礎,甚至有些書整本都在介紹 LoadRunner 的使用。
Jmeter 同樣是非常有名的開源性能測試工具,功能也很完善,在本書中介紹了它作為接口測試工具的使用。但實際上,它是一個標准的性能測試工具。關於Jmeter相關的資料也非常豐富,它的官方文檔也很完善。
Locust 同樣是性能測試工具,雖然官方這樣來描述它 “An open source load testing tool.” 。但其它和前面兩個工具有着較大的不同。相比前面兩個工具,功能上要差上不少,但它也並非優點全無。
- Locust 完全基本 Python 編程語言,采用 Pure Python 描述測試腳本,並且 HTTP 請求完全基於 Requests 庫。除了 HTTP/HTTPS 協議,Locust 也可以測試其它協議的系統,只需要采用Python調用對應的庫進行請求描述即可。
- LoadRunner 和 Jmeter 這類采用進程和線程的測試工具,都很難在單機上模擬出較高的並發壓力。Locust 的並發機制摒棄了進程和線程,采用協程(gevent)的機制。協程避免了系統級資源調度,由此可以大幅提高單機的並發能力。
正是基於這樣的特點,使我選擇使用Locust工具來做性能測試,另外一個原因是它可以讓我們換一種方式認識性能測試,可能更容易看清性能測試的本質。
我想已經成功的引起了你的興趣,那么接下來就跟着來學習Locust的使用吧。
2. Locust 安裝
方式一:通過 pip 命令安裝
pip install locust
python setup.py install

3. Locust 使用入門
編寫第一個性能測試腳本
from locust import HttpLocust, TaskSet, task
# 定義用戶行為
class UserBehavior(TaskSet):
def baidu_index(self):
self.client.get("/")
class WebsiteUser(HttpLocust):
task_set = UserBehavior
min_wait = 3000
max_wait = 6000
WebsiteUser類用於設置性能測試。
- task_set :指向一個定義的用戶行為類。
- min_wait :執行事務之間用戶等待時間的下界(單位:毫秒)。
- max_wait :執行事務之間用戶等待時間的上界(單位:毫秒)。
執行性能測試腳本
locust -f ./locustfile.py --host=https://www.baidu.com
#[2019-07-04 15:40:11,348] tsbc.leaptocloud/INFO/locust.main: Starting web monitor at *:8089
#[2019-07-04 15:40:11,348] tsbc.leaptocloud/INFO/locust.main: Starting Locust 0.11.1
- -f 指定性能測試腳本文件。
- --host 指定被測試應用的URL的地址,注意訪問百度使用的HTTPS協議。
通過瀏覽器訪問:http://127.0.0.1:8089(Locust啟動網絡監控器,默認為端口號為: 8089)

設置模擬用戶數。
設置孵化率 每秒產生(啟動)的虛擬用戶數。
點擊 “啟動” 按鈕,開始運行性能測試。

性能測試參數分析
- Type: 請求的類型,例如GET/POST。
- Name:請求的路徑。這里為百度首頁,即:https://www.baidu.com/
- request:當前請求的數量。
- fails:當前請求失敗的數量。
- Median:中間值,單位毫秒,一半的服務器響應時間低於該值,而另一半高於該值。
- Average:平均值,單位毫秒,所有請求的平均響應時間。
- Min:請求的最小服務器響應時間,單位毫秒。
- Max:請求的最大服務器響應時間,單位毫秒。
- Content Size:單個請求的大小,單位字節。
4. Locust 腳本開發進階
Locust 中類的定義和使用
HttpLocust 類
在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
步驟,直至測試任務終止。
from locust import HttpLocust, TaskSet, task
class UserTask(TaskSet):
def tc_index(self):
self.client.get("/")
class UserOne(HttpLocust):
task_set = UserTask
weight = 1
min_wait = 1000
max_wait = 3000
stop_timeout = 5
host = "https://www.baidu.com"
class UserTwo(HttpLocust):
weight = 2
task_set = UserTask
host = "https://www.baidu.com"
一個Locust實例被挑選執行的權重,數值越大,執行頻率越高。在一個 locustfile.py 文件中可以同時定義多個 HttpLocust 子類,然后分配他們的執行權重,例如:
然后在終端啟動測試:
locust -f locustfile.py UserOne UserTwo
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):
1) (
def test_job1(self):
self.client.get('/job1')
2) (
def test_job2(self):
self.client.get('/job2')
tasks屬性
定義任務信息時,描述形式如下:
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)] # 兩種方式等價
test_job2
的頻率是test_job1
的兩倍。1:1
。from locust import TaskSet, task
class UserBehavior(TaskSet):
def test_job1(self):
self.client.get('/job1')
def test_job2(self):
self.client.get('/job2')
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, test_job2]
TaskSequence類
class MyTaskSequence(TaskSequence):
1) (
def first_task(self):
pass
2) (
def second_task(self):
pass
3) (
10) (
def third_task(self):
pass
@seq_task
使用@task
裝飾器進行組合,當然您也可以在TaskSequences中嵌套TaskSet,反之亦然。公共庫
sys.path.append(os.getcwd())
在導入任何公共庫之前寫入您的locust文件 - 這將使項目成為root(即當前正在工作)目錄)可導入。
__init__.py
common/
__init__.py
config.py
auth.py
locustfiles/
__init__.py
web_app.py
api.py
ecommerce.py
sys.path.append(os.getcwd())
import common.auth
HttpSession類
每個發出請求的方法還需要兩個額外的可選參數,這些參數是特定於Locust的,並且在python-requests中不存在。這些是:
- name - (可選)可以指定在Locust的統計信息中用作標簽而不是URL路徑的參數。這可用於將請求的不同URL分組到Locust統計信息中的單個條目中。
- catch_response - (可選)布爾參數,如果設置,可用於發出請求,返回上下文管理器作為with語句的參數。這將允許根據響應內容將請求標記為失敗,即使響應代碼正常(2xx)。相反也有效,可以使用catch_response來捕獲請求,然后將其標記為成功,即使響應代碼不是(即500或404)。
request
(
方法
,
網址
,
名稱=無
,
catch_response = False
,
** kwargs
)
構造並發送一個requests.Request
。返回requests.Response
對象。
參數: |
|
---|
腳本增強
設置響應斷言
from locust import HttpLocust, TaskSet, task
class UserTask(TaskSet):
def job(self):
with self.client.get('/', catch_response = True) as response:
if response.status_code == 200:
response.failure('Failed!')
else:
response.success()
class User(HttpLocust):
task_set = UserTask
min_wait = 1000
max_wait = 3000
host = "https://www.baidu.com"
catch_response = True :布爾類型,如果設置為 True, 允許該請求被標記為失敗。
通過 client.get() 方法發送請求,將整個請求的給 response, 通過 response.status_code 得請求響應的 HTTP 狀態碼。如果不為 200 則通過 response.failure('Failed!') 打印失敗!
Locust關聯
在某些請求中,需要攜帶之前從Server端返回的參數,因此在構造請求時需要先從之前的Response中提取出所需的參數。from lxml import etree
from locust import TaskSet, task, HttpLocust
class UserBehavior(TaskSet):
def get_session(html):
tree = etree.HTML(html)
return tree.xpath("//div[@class='btnbox']/input[@name='session']/@value")[0]
10) (
def test_login(self):
html = self.client.get('/login').text
username = 'user@compay.com'
password = '123456'
session = self.get_session(html)
payload = {
'username': username,
'password': password,
'session': session
}
self.client.post('/login', data=payload)
class WebsiteUser(HttpLocust):
host = 'http://debugtalk.com'
task_set = UserBehavior
min_wait = 1000
max_wait = 3000
Locust 參數化
from locust import TaskSet, task, HttpLocust
import queue
class UserBehavior(TaskSet):
def test_register(self):
try:
data = self.locust.user_data_queue.get()
except queue.Empty:
print('account data run out, test ended.')
exit(0)
print('register with user: {}, pwd: {}'\
.format(data['username'], data['password']))
payload = {
'username': data['username'],
'password': data['password']
}
self.client.post('/register', data=payload)
class WebsiteUser(HttpLocust):
host = 'http://debugtalk.com'
task_set = UserBehavior
user_data_queue = queue.Queue()
for index in range(100):
data = {
"username": "test%04d" % index,
"password": "pwd%04d" % index,
"email": "test%04d@debugtalk.test" % index,
"phone": "186%08d" % index,
}
user_data_queue.put_nowait(data)
min_wait = 1000
max_wait = 3000
HttpRunner使用(v2.x)
介紹
YAML/JSON
腳本,即可實現自動化測試、性能測試、線上監控、持續集成等多種測試需求。
安裝
pip install httprunner
在 HttpRunner 安裝成功后,系統中會新增如下 5 個命令:
httprunner、hrun、ate 三個命令完全等價,功能特性完全相同,個人推薦使用hrun
命令。
運行如下命令,若正常顯示版本號,則說明 HttpRunner 安裝成功。
(locust) [root@tsbc api]# hrun -V
2.1.3
快速使用
抓包

生成測試用例
為了簡化測試用例的編寫工作,HttpRunner 實現了測試用例生成的功能。
首先,需要將抓取得到的數據包導出為 HAR 格式的文件,如:名稱為 demo-quickstart.har。
然后,在命令行終端中運行如下命令,即可將 demo-quickstart.har 轉換為 HttpRunner 的測試用例文件。
har2case docs/data/demo-quickstart.har -2y
INFO:root:Start to generate testcase.
INFO:root:dump testcase to YAML format.
INFO:root:Generate YAML testcase successfully: docs/data/demo-quickstart.yml
config
name testcase description
variables
test
name /api/get-token
request
headers
Content-Type application/json
User-Agent python-requests/2.18.4
app_version2.8.6
device_sn FwgRiO7CNA50DSU
os_platform ios
json
sign 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
method POST
url http //127.0.0.1 5000/api/get-token
validate
eq status_code 200
eq headers.Content-Type application/json
eq content.success true
eq content.token baNLX1zhFYP11Seb
test
name /api/users/1000
request
headers
Content-Type application/json
User-Agent python-requests/2.18.4
device_sn FwgRiO7CNA50DSU
token baNLX1zhFYP11Seb
json
name user1
password'123456'
method POST
url http //127.0.0.1 5000/api/users/1000
validate
eq status_code 201
eq headers.Content-Type application/json
eq content.success true
eq content.msg user created successfully.
現在我們只需要知道如下幾點:
- 每個 YAML/JSON 文件對應一個測試用例(testcase)
- 每個測試用例為一個list of dict結構,其中可能包含全局配置項(config)和若干個測試步驟(test)
- config 為全局配置項,作用域為整個測試用例
- test 對應單個測試步驟,作用域僅限於本身
如上便是 HttpRunner 測試用例的基本結構。
demo-quickstart.json
[
{
"config": {
"name": "testcase description",
"variables": {}
}
},
{
"test": {
"name": "/api/get-token",
"request": {
"url": "http://127.0.0.1:5000/api/get-token",
"method": "POST",
"headers": {
"User-Agent": "python-requests/2.18.4",
"device_sn": "FwgRiO7CNA50DSU",
"os_platform": "ios",
"app_version": "2.8.6",
"Content-Type": "application/json"
},
"json": {
"sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"
}
},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["headers.Content-Type", "application/json"]},
{"eq": ["content.success", true]},
{"eq": ["content.token", "baNLX1zhFYP11Seb"]}
]
}
},
{
"test": {
"name": "/api/users/1000",
"request": {
"url": "http://127.0.0.1:5000/api/users/1000",
"method": "POST",
"headers": {
"User-Agent": "python-requests/2.18.4",
"device_sn": "FwgRiO7CNA50DSU",
"token": "baNLX1zhFYP11Seb",
"Content-Type": "application/json"
},
"json": {
"name": "user1",
"password": "123456"
}
},
"validate": [
{"eq": ["status_code", 201]},
{"eq": ["headers.Content-Type", "application/json"]},
{"eq": ["content.success", true]},
{"eq": ["content.msg", "user created successfully."]}
]
}
}
]
首次運行測試用例
運行測試用例的命令為hrun
,后面直接指定測試用例文件的路徑即可。
$ hrun docs/data/demo-quickstart-1.yml
locusts -f docs/data/demo-quickstart-1.yml