上一章介绍项目的部署也是为性能测试做铺垫,只有对已经部署的项目做性能测试才有意义,因为中间价Nginx、uWSGI是影响系统性能的重要一环。
LoadRunner、JMeter都是非常好用的性能测试工具。
Locust同样也是性能测试工具,完全基于Python语言,采用Pure Python描述测试脚本,并且HTTP请求完全基于Requests库,除了HTTP/HTTPS协议外,Locust还可以测试其他协议的系统,只需要采用Python调用对应的库进行请求描述即可。
LoadRunner、JMeter采用进程、线程的方式,这种方式很难在单机上模拟出较高的并发压力。
Locust的并发机制摒弃了进程和线程,采用协程(gevent)的机制。协程避免了系统级资源的调度,因此可以大幅度提高单机的并发能力。
如果是python 2.*版本,则使用pip进行安装;
如果是python 3版本,则建议从GitHub克隆岛本地进行安装。
Locust安装目录下的setup.py文件的要求:
install_requires=["gevent>=1.1.2", "flask>=0.10.1", "requests>=2.9.1", "msgpack-python>=0.4.2", "six>=1.10.0", "pyzmq==15.2.0"]
gevent:在Python中实现协程的一个第三方库,协程,又称微线程(Coroutine),使用gevent可以获得极高的并发性能;
flask:Python的一个Web开发框架,它与Djanog的地位相当;
requests:做HTTP接口测试的库;
msgpack-python:一种快速、紧凑的二进制序列化格式,适用于类似JSON的数据;
six:它提供了一些简单的工具来封装Python 2和Python 3之间的差异性;
pyzmq:如果你打算把Locust运行在多个进程/机器,建议安装pyzmq;
试运行一下locust,查看帮助信息:
root@TEST:~# locust --help
Usage: locust [options] [LocustClass [LocustClass2 ... ]]
Options:
-h, --help show this help message and exit
-H HOST, --host=HOST Host to load test in the following format:
http://10.21.32.33
--web-host=WEB_HOST Host to bind the web interface to. Defaults to '' (all
interfaces)
-P PORT, --port=PORT, --web-port=PORT
Port on which to run web host
-f LOCUSTFILE, --locustfile=LOCUSTFILE
Python module file to import, e.g. '../other.py'.
Default: locustfile
--csv=CSVFILEBASE, --csv-base-name=CSVFILEBASE
Store current request stats to files in CSV format.
--master Set locust to run in distributed mode with this
process as master
--slave Set locust to run in distributed mode with this
process as slave
--master-host=MASTER_HOST
Host or IP address of locust master for distributed
load testing. Only used when running with --slave.
Defaults to 127.0.0.1.
--master-port=MASTER_PORT
The port to connect to that is used by the locust
master for distributed load testing. Only used when
running with --slave. Defaults to 5557. Note that
slaves will also connect to the master node on this
port + 1.
--master-bind-host=MASTER_BIND_HOST
Interfaces (hostname, ip) that locust master should
bind to. Only used when running with --master.
Defaults to * (all available interfaces).
--master-bind-port=MASTER_BIND_PORT
Port that locust master should bind to. Only used when
running with --master. Defaults to 5557. Note that
Locust will also use this port + 1, so by default the
master node will bind to 5557 and 5558.
--expect-slaves=EXPECT_SLAVES
How many slaves master should expect to connect before
starting the test (only when --no-web used).
--no-web Disable the web interface, and instead start running
the test immediately. Requires -c and -r to be
specified.
-c NUM_CLIENTS, --clients=NUM_CLIENTS
Number of concurrent Locust users. Only used together
with --no-web
-r HATCH_RATE, --hatch-rate=HATCH_RATE
The rate per second in which clients are spawned. Only
used together with --no-web
-t RUN_TIME, --run-time=RUN_TIME
Stop after the specified amount of time, e.g. (300s,
20m, 3h, 1h30m, etc.). Only used together with --no-
web
-L LOGLEVEL, --loglevel=LOGLEVEL
Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL.
Default is INFO.
--logfile=LOGFILE Path to log file. If not set, log will go to
stdout/stderr
--print-stats Print stats in the console
--only-summary Only print the summary stats
--no-reset-stats Do not reset statistics once hatching has been
completed
-l, --list Show list of possible locust classes and exit
--show-task-ratio print table of the locust classes' task execution
ratio
--show-task-ratio-json
print json data of the locust classes' task execution
ratio
-V, --version show program's version number and exit
对于Web应用来说,它本质上是由一个个的Web页面构成,一般我们可以通过不同的URL地址来得到不同的页面。
使用Locust编写一个简单性能测试行为表述脚本,创建文件:locustfile.py
from locust import HttpLocust, TaskSet, task
# 定义用户行为
class UserBehavior(TaskSet):
@task
def baidu_page(self):
self.client.get("/")
class WebsiteUser(HttpLocust):
task_set = UserBehavior
min_wait = 3000
max_wait = 6000
UserBehavior类继承TaskSet类,用于描述用户行为;
baidu_page()方法表示一个用户行为,访问百度首页,使用@task装饰该方式为一个事务;
client.get()用于指定请求的路径“/”,因为是百度首页,所以指定为根路径。
WebsiteUser类用于设置性能测试:
task_set:指向一个定义的用户行为类;
min_wait:执行事务之间用户等待时间的下界,单位:毫秒;
max_wait:执行事务之间用户等待时间的上界,单位:毫秒;
首先,启动性能测试:
root@TEST:~# locust -f locustfile.py --host=https://www.baidu.com
[2017-11-12 22:38:23,417] CSG-TEST/INFO/locust.main: Starting web monitor at *:8089
[2017-11-12 22:38:23,418] CSG-TEST/INFO/locust.main: Starting Locust 0.8
其中:
-f:指定性能测试脚本文件;
–host:指定被测试应用的URL地址,注意访问百度使用的HTTPS协议;
通过浏览器访问:http://127.0.0.1:8089(Locust启动网络监控器,默认端口号为8089),如图:
其中:
Number of users to simulate:设置模拟用户数;
Hatch rate(users spawned/second):每秒产生(启动)的虚拟用户数;
单击“Start swarming”按钮,开始运行性能测试,各个参数如下:
Type:请求的类型,例如GET/POST;
Name:请求的路径,这里为百度首页,即https://www.baidu.com/;
request:当前请求的数量;
fails:当前请求失败的数量;
Median:中间值,单位毫秒,一半的服务器响应时间低于该值,而另一半高于该值;
Average:平均值,单位毫秒,所有请求的平均响应时间;
Min:请求的最小服务器响应时间,单位毫秒;
Max:请求的最大服务器响应时间,单位毫秒;
Content Size:单个请求的大小,单位字节;
reqs/sec:每秒钟请求的个数。
性能测试涉及的知识点非常多,包括:
性能测试的需求分析: 客户需求、新系统性能验证、旧系统扩容、优化系统瓶颈等;
性能测试工具的选型: 商业工具LoadRunner、开源工具JMeter、Locust,或者自研性能工具;
性能测试环境准备: 软件环境、硬件环境、网络环境;
性能测试业务分析: 针对哪些业务做性能测试;
性能测试数据准备: 准备性能测试所需要的基础数据;
性能测试执行策略: 不同业务的用户分配比例,运行时长、思考时间、集合点的设置等;
性能测试监控: 中间件的监控、数据库服务器的监控、系统服务器的监控;
性能测试分析与调优: 分析整个系统各个部分的监控结果;对程序处理过程优化,程序算法优化,中间件各种配置参数的调整,数据库SQL语句、索引、表结构的优化;
性能测试目的: 发布会签到系统、新系统能力验证;
业务分析: 根据发布会签到系统的应用场景,主要包括发布会管理页面、嘉宾管理页面、嘉宾查询功能和发布会签到功能;
性能测试环境:
测试数据准备:
发布会数据:10条
嘉宾数据:3000条
待签到嘉宾:3000条
测试数据构造:
ALTER TABLE `sign_event` CHANGE `create_time` `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE `sign_guest` CHANGE `create_time` `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
f = open("guests.txt", 'w')
for i in range(1, 3001):
str_i = str(i)
realname = "jack" + str_i
phone = 13800110000 + i
email = "jack" + str_i + "@mail.com"
sql = 'INSERT INTO sign_guest (realname, phone, email, sign, event_id) VALUES ("'+realname+'", '+str(phone)+', "'+email+'", 0, 1);'
f.write(sql)
f.write("\n")
f.close()
使用Locust编写性能测试脚本:locustfile.py
#! /usr/bin/python
# -*- coding:utf-8 -*-
from locust import HttpLocust, TaskSet, task
# Web性能测试
class UserBehavior(TaskSet):
def on_start(self):
"""
on_start is called when a Locust start before any task is scheduled
"""
self.login()
def login(self):
self.client.post("/login_action", {"username":"admin", "password":"admin123456"})
@task(2)
def event_manage(self):
self.client.get("/event_manage/")
@task(2)
def guest_manage(self):
self.client.get("/guest_manage/")
@task(1)
def search_manage(self):
self.client.get("/search_phone/", params={"phone":"13800112451"})
class WebsiteUser(HttpLocust):
task_set = UserBehavior
min_wait = 3000
max_wait = 6000
通过@task()装饰的方法为一个事务,方法的参数用于指定该行为的执行权重。参数越大,每次被虚拟用户只需的概率越高,如果不设置,则默认为1,发布会管理页、嘉宾管理页和嘉宾搜索功能的执行权重比例为2:2:1。
min_wait、max_wait用于指定用户执行事务之间暂停的下限和上限,即3-6秒;
每个事务的请求路径、是GET请求还是POST请求、是否需要传参数等,都可以根据Django项目中对视图函数的定义来决定,调用方法与Requests库基本相同。
启动性能测试:
>locust -f locustfile.py --host=http://127.0.0.1:8000
通过浏览器访问Locust工具:http://127.0.0.1:8000
Number of users to simulate:设置模拟用户数为100
Hatch rate(users spawned/second):每秒产生(启动)的用户数为10,即每秒启动10个模拟用户
单击“Start swarming”按钮,运行性能测试,单击“New test”按钮,重新设置虚拟用户数并允许性能测试。
接口性能测试相比系统性能测试要简单许多,不用考虑业务场景和用户行为,只需要模拟调用接口,验证接口的最大处理能力即可。
嘉宾签到系统里,发布会现场需要多通道并行对嘉宾进行签到,所以,需要充分验证签到接口的并发签到处理能力。
locustfile.py
#!/usr/bin/python
# -*- coding:utf-8 -*-
from locust import HttpLocust, TaskSet, task
from random import randint
# Web接口测试
class UserBehavior(TaskSet):
@task()
def user_sign(self):
number = randint(1, 3001)
phone = 13800110000 + number
str_phone = str(phone)
self.client.post("/api/user_sign/", data={"eid":"1", "phone":str_phone})
class WebsiteUser(HttpLocust):
task_set = UserBehavior
min_wait = 0
max_wait = 0
使用locust命令启动性能测试,通过参数设置运行测试:
locust -f locustfile.py --host=http://127.0.0.1:8089 --no-web -c 10 -r 10 -n 3000
其中:
–no-web:表示不使用web界面运行测试
-c:设置虚拟用户数
-r:设置每秒启动虚拟用户数
-n:设置请求个数
重复签到和签到失败是两个不同的服务器处理过程,注意不能混淆。
如果需要对3000个嘉宾计算多长时间内可以完成全部签到,那么可以使用Python的多线程技术来实现这个需求:
thread_if_test.py
#!/usr/bin/python
# -*- coding:utf-8 -*-
import requests
import threading
from time import time
# 定义接口基本地址
base_url = "http://127.0.0.1:8089"
# 签到线程
def sign_thread(start_user, end_user):
for i in range(start_user, end_user):
phone = 13800110000 + i
datas = {"eid":"1", "phone":phone}
r = requests.post(base_url+'/api/user_sing/', data=datas)
result = r.json()
try:
assert result['message'] == "sign success"
except AssertionError as e:
print "phone:" + str(phone) + ", user sign fail!"
# 设置用户分组(即5个线程)
lists = {1:601, 601:1201, 1201:1801, 1801:2401, 2401:3001}
# 创建线程数组
threads = []
# 创建线程
for start_user, end_user in lists.items():
t = threading.Thread(target=sign_thread, args=(start_user, end_user))
threads.append(t)
if __name__ == '__main__':
# 开始时间
start_time = time()
# 启动线程
for i in range(len(lists)):
threads[i].start()
for i in range(len(lists)):
threads[i].join()
# 结束时间
end_time = time()
print "start time: " + str(start_time)
print "end time: " + str(end_time)
print "run time: " + str(end_time - start_time)
将3000个数平均分为5组,放到字典中,其中每一组数通过线程类Thread,调用sign_thread()函数生成一个线程。所以,是5个线程(可以理解为“虚拟用户数”)并发调用接口测试。
start()方法用于启动线程,join()方法用于守护线程。
相比专业性能测试工具,这个多线程测试程序要简陋得多,但是直接编程的灵活性也是工具所不具备的。
本章主要以介绍Locust性能测试工具的使用,在实际项目中,我们会遇到各种性能需求,如何去验证性能是否满足需求,工具只是一种达成目标的手段,选取适合的工具和方法,会事半功倍。
工具有工具的好处,直接编写脚本有脚本的好处,两者可以取长补短,针对不同的场景,结合使用。
至此,本书也就全部完成了,这是自己进入IT行业以来,可以惭愧的说,是第一本完整读完的书,很感谢虫师,带自己进入了一个全新(起码对自己这个一直是手工测试的懒人来说)的世界,现在自己大部分的时间都是在写脚本,想着如何能省事的去做,而不是单单的傻傻的去手工执行用例了。我觉得这就是这本书最大的受益之处了,改变了一个人的做事思维方式,提升效率,也提升了自己技能水平。
最后还是要说一句感谢!在这个感恩节的日子,感谢自己的坚持,感谢虫师这本书,感谢自己愛的人,感谢愛自己的人。