locustfile是什麼?
locustfile是Locust效能測試工具的使用者指令碼,描述了單個使用者的行為。
locustfile是個普通的Python模組,如果寫作locustfile.py
,那麼路徑切換到檔案所在目錄,直接執行命令就能執行:
$ locust
如果換個名字,那麼只能通過-f
引數指定檔名執行:
$ locust -f locust_files/my_locust_file.py
與一般Python模組不同的是:locustfile必須至少定義一個類,且繼承自User類。
User類
User類表示效能測試的模擬使用者,Locust會在執行時建立User類的例項。
wait_time屬性
設定等待時間,預設值不等待,立即執行。
Locust支援4種方式設定wait_time屬性。
為了便於理解實際意義,我把原始碼貼在了下面。
-
constant
函式,常量,任務執行完畢等待X秒開始下一任務。def constant(wait_time): """ Returns a function that just returns the number specified by the wait_time argument Example:: class MyUser(User): wait_time = constant(3) """ return lambda instance: wait_time
-
between
函式,區間隨機值,任務執行完畢等待X-Y秒(中間隨機取值)開始下一任務。def between(min_wait, max_wait): """ Returns a function that will return a random number between min_wait and max_wait. Example:: class MyUser(User): # wait between 3.0 and 10.5 seconds after each task wait_time = between(3.0, 10.5) """ return lambda instance: min_wait + random.random() * (max_wait - min_wait)
-
constant_pacing
函式,自適應,若任務耗時超過該時間,則任務結束後立即執行下一任務;若任務耗時不超過該時間,則等待達到該時間後執行下一任務。def constant_pacing(wait_time): """ Returns a function that will track the run time of the tasks, and for each time it's called it will return a wait time that will try to make the total time between task execution equal to the time specified by the wait_time argument. In the following example the task will always be executed once every second, no matter the task execution time:: class MyUser(User): wait_time = constant_pacing(1) @task def my_task(self): time.sleep(random.random()) If a task execution exceeds the specified wait_time, the wait will be 0 before starting the next task. """ def wait_time_func(self): if not hasattr(self, "_cp_last_run"): self._cp_last_wait_time = wait_time self._cp_last_run = time() return wait_time else: run_time = time() - self._cp_last_run - self._cp_last_wait_time self._cp_last_wait_time = max(0, wait_time - run_time) self._cp_last_run = time() return self._cp_last_wait_time return wait_time_func
-
自定義
wait_time
方法,比如每次等待時間1秒2秒3秒遞增:class MyUser(User): last_wait_time = 0 def wait_time(self): self.last_wait_time += 1 return self.last_wait_time ...
weight屬性
設定建立類例項的權重,預設每個類建立相同數量的例項。
locustfile中可以有多個繼承了User類的類。
命令列可以指定執行哪些類:
$ locust -f locust_file.py WebUser MobileUser
通過weight屬性可以讓類更大概率建立例項,比如:
class WebUser(User):
weight = 3
...
class MobileUser(User):
weight = 1
...
WebUser類比MobileUser類多三倍概率建立例項。
host屬性
設定URL字首。
一般是在Locust的Web UI或者命令列,通過--host
指定URL字首。如果沒有通過--host
指定,並且類中設定了host屬性,那麼類的host屬性才會生效。
environment屬性
對使用者執行環境的引用。
比如在task方法中通過environment屬性終止執行:
self.environment.runner.quit()
注意,單機會終止所有執行,分散式只會終止單個worker節點。
on_start和on_stop方法
測試前初始化和測試後清理。
HttpUser類
開篇文章的示例指令碼,沒有繼承User類,而是繼承了它的子類HttpUser:
它比User類更常用,因為它新增了一個client
屬性,用來傳送HTTP請求。
client屬性/HttpSession
HttpUser類的client屬性是HttpSession類的一個例項:
HttpSession是requests.Session
的子類,requests就是常用來做介面測試的那個requests庫:
HttpSession沒有對requests.Session
做什麼改動,主要是傳遞請求結果給Locust,比如success/fail,response time,response length,name。
示例:
response = self.client.post("/login", {"username":"testuser", "password":"secret"})
print("Response status code:", response.status_code)
print("Response text:", response.text)
response = self.client.get("/my-profile")
由於requests.Session
會暫存cookie,所以示例中登入/login
請求後可以繼續請求/my-profile
。
斷言響應結果
可以使用with語句和catch_response引數對響應結果進行斷言:
with self.client.get("/", catch_response=True) as response:
if response.text == "Success":
response.success()
elif response.text != "Success":
response.failure("Got wrong response")
elif response.elapsed.total_seconds() > 0.5:
response.failure("Request took too long")
或者直接丟擲異常:
from locust.exception import RescheduleTask
...
with self.client.get("/does_not_exist/", catch_response=True) as response:
if response.status_code == 404:
raise RescheduleTask()
name引數
name引數用於把不同api按同一分組進行統計,比如:
for i in range(10):
self.client.get("/blog?id=%i" % i, name="/blog?id=[id]")
會按/blog/?id=[id]
統計1條資料,而不是分成10條資料。
HTTP代理
Locust預設設定了requests.Session的trust_env為False,不查詢代理,以提高執行效能。如果需要可以設定locust_instance.client.trust_env
為True。
示例程式碼
請求REST API並斷言:
from json import JSONDecodeError
...
with self.client.post("/", json={"foo": 42, "bar": None}, catch_response=True) as response:
try:
if response.json()["greeting"] != "hello":
response.failure("Did not get expected value in greeting")
except JSONDecodeError:
response.failure("Response could not be decoded as JSON")
except KeyError:
response.failure("Response did not contain expected key 'greeting'")
小結
locustfile是個普通Python模組,必須繼承User類或其子類HttpUser等。本文對User類和HttpUser類的屬性和方法進行了介紹,使用它們可以編寫效能測試的使用者指令碼。locustfile還有另外一個重要組成元素,@task
。
參考資料: