引言
当我们做Web系统性能测试方案时,压力模拟工具的选择通常是一个绕不开的环节。对于大部分互联网公司的业务规模和测试资源投入,JMeter这个老牌开源性能测试工具能够满足大部分测试需求,它也可能是世面上书籍、博客教程丰富程度仅次于LoadRunner的性能测试工具。然而当我们的场景需要模拟的并发用户数以千为单位时,使用JMeter的成本越来越大,甚至超出我们掌握的资源。此时,我们开始寻找更低成本的方案,而Locust,为这样的方案带来了一种可能。
简介
Locust是开源、使用Python开发、基于事件、支持分布式并且提供Web UI进行测试执行和结果展示的性能测试工具。而它之所以能够在资源占用方面明显优于JMeter,一个关键点在于两者模拟虚拟用户的方式不同,JMeter通过线程来作为虚拟用户,而Locust借助gevent库对协程的支持,以greenlet来实现对用户的模拟,相同配置下Locust能支持的并发用户数相比JMeter可以达到一个数量级的提升。
Locust使用Python代码定义测试场景,目前支持Python 2.7, 3.3, 3.4, 3.5, 和3.6。它自带一个Web UI,用于定义用户模型,发起测试,实时测试数据,错误统计等,在最新未正式发布的v0.8a2(当前最新发布版本v0.8a1),还提供QPS、评价响应时间等几个简单的图表。
本文不会介绍Locust最基础的部署、运行等Quick start式内容,这部分内容请直接参照官网Quick start或者搜索Locust入门的博客,本文主要介绍一些目前网络上还比较缺少的,真正要用Locust来做Web系统性能测试时通常需要用到的内容或者可能遇到的问题
v0.8a2<a id="a2"></a>
如前文所说,当前官方最新发布的版本为v0.8a1,还不包含图表特性,而在官方Github上已经在v0.8a2完成了图表特性的合并,想要提前体验可以直接从Github获取master分支的代码,覆盖`[PythonHome]\Lib\site-packages`中的locust目录即可。
指定Web host
在Linux系统多网卡情况下,Locust自动选择网卡时可能会遇到error: [Errno 97] Address family not supported by protocol
错误,此时可以通过直接指定web host来解决问题,使用选项--web-host
来指定可用的地址,例:
locust -f xxx.py --web-host=127.0.0.1
locust -f xxx.py --web-host=192.168.1.2
locust -f xxx.py --web-host=localhost
断言
当我们没有自定义断言时,测试请求结果的状态(success/fail)取决于Http请求是否有异常出现,而在对我们的Web系统实施性能测试时,当我们需要更准确的业务成功率数据时,就需要通过对响应状态码、Response body等数据进行校验来给出结果,此时,可以通过ResponseContextManager来实现。首先在场景代码的发起请求参数中通过catch_response=True
来捕获响应数据,然后对响应数据进行校验,最后使用success()/failure()两个方法来标识请求结果的状态。例:
from locust import HttpLocust, TaskSet,task
class UserBehavior(TaskSet):
@task(2)
def foo(self):
with self.client.get("/", catch_response=True) as response:
if response.status_code == 200:
response.success()
@task(1)
def bar(self):
reqBody = '{"username":"ellen_key", "password":"education"}'
with self.client.post("/login", reqBody, catch_response=True) as response:
if response.content == "":
response.failure("No data")
class WebsiteUser(HttpLocust):
task_set = UserBehavior
host = "http://foo.bar.com"
min_wait = 0
max_wait = 0
Json解析
Json作为一种轻量级的数据交换格式,以及被如今的互联网系统广泛采用。上一节的示例中,我们使用content获取完整的响应内容,实际测试实施中,对于动态的响应结果,可能更多的采用校验关键字段的方式对于Json格式的响应数据,要获取特定字段的值,可以直接使用内置的Json解析实现,例:
对于如下的响应结果:
{
'code':0,
'data':[
{
'id':123
}
]
}
可以通过如下方式获取其中的关键数据:
with self.client.post("/login", reqBody, catch_response=True) as response:
json_resp = response.json()
code = json_resp["code"]
data_len = len(json_resp["data"])
id = json_resp["data"][0]["id"]
自定义标签
从简介的Summary report图中可以看到,Locust的结果展示中,请求的默认名称是url的path部分,而为了报告更直观,或者当同一个业务有动态的path(如/user/[userid]),需要聚合时,可以通过name
参数来自定义标签实现,例:
with self.client.get("/account/{accountID}", catch_response=True, name = "getAccount") as resp:
分布式运行
Locust的分布式运行,master和slave节点都需要有场景脚本,分别以如下命令启动:
- master:
locust -f locustfile.py --master --web-host=x.x.x.x
- slave:
locust -f locustfile.py --slave --master-host=x.x.x.x
master节点将运行Locust的Web UI服务,不会承担任何施压任务(不会模拟虚拟用户)。
如前面的简介,Locust模拟并发用户是使用协程,也因此对于多核CPU的服务器,为良好的利用多核能力,建议一台slave服务器运行与CPU核数相当的slave。
总结
Locust作为一个年轻的、轻量级的性能测试工具,网络上相关的应用文献特别是中文文献相对较少,而且多数是Quick start式的指引,对于一些实施的细节信息还比较欠缺,本文根据作者的实际应用经验,列举了部分使用细节,希望能为需要的朋友提供一点有用的信息。