性能测试实战流程
本章内容,实战班学员请联系老师 获取视频讲解
安装示例被测系统
要讲解 性能测试,需要一个 示例 被测系统
。
我们使用 白月SMS2系统
作为被测系统。
该系统要进行性能测试,正式安装的环境是 CentOS7。
VIP 学员
请联系 老师,获取 该系统安装好的虚拟机 CentOS镜像,根据教程安装启动被测系统。
非VIP学员
可以使用 Windows版本(注意:Windows版本的性能不够,进行性能测试时,会有大量超时错误)
Windows版本,点击百度网盘链接 ,下载 白月SMS2系统 压缩包 bysms2.zip
下载解压bysms2.zip后,进入bysms2目录,双击运行 runserver.bat 即可启动 白月SMS2 系统。
要通过API接口对服务系统进行性能测试,必须要了解系统的接口。
上面说的百度网盘链接里面,有个文档名为 bycrm_api.docx
,是 该系统的 API接口文档,请下载后仔细学习接口文档。
然后,观看上面链接视频里面对 该系统的 功能介绍
性能测试概述
性能测试通常比较复杂,要真正做好很不容易。
需要有产品视野,明白真实场景下,用户是怎样使用产品的,这样才能知道哪些场景是用户大量使用的。
需要有开发视野,明白产品架构,甚至一些实现细节,这样才能对哪些 使用场景 会带来性能问题 了然于胸。
需要有测试经验, 结合前面的知识,写出良好的性能测试用例。
需要有开发技能,灵活使用各种测试工具,有的测试工具需要二次开发,甚至市场上没有现成可以使用的测试工具,必须得自己开发 测试工具。
所以通常 性能测试 和 自动化测试 能力 是高级测试人员的 必备技能。
这里做个小广告, 如果想系统学习 自动化测试、和性能测试,可以报 白月黑羽的 VIP 班。详情参见文章末尾广告栏。
前面我们学习了性能测试工具 黑羽压测 的使用,但是要做好性能测试这还不够。
必须要明白 整个性能测试的流程。
性能测试 和 功能测试一样,通常需要经历如下3个过程
-
分析需求、确定性能测试场景
-
编写测试计划、测试用例
-
执行测试
在我们开始前,还有个很重要的问题:
也就是什么时候着手 启动
性能测试的工作,包括上面说的3个阶段流程?
你可能要说,那肯定越早越好啊。
但是白月黑羽的经验,不能太早。
因为性能测试的需求指标,随着产品的开发过程会非常容易产生变动。
比如:
原来估计的性能瓶颈场景, 随着开发过程,会发生改变,导致早早写好的测试用例没有用。
再比如,原来计划的产品 生产环境(包括运行设备、操作系统等),到了后来,也会发生改变,导致原来准备的硬件软件环境没有意义,产生金钱和时间上的浪费。
所以白月黑羽建议,在产品功能测试 完成几轮, 产品相对比较稳定,再启动性能测试。
当然,参与性能测试的人员,预先做一些准备是必要的。 比如:对测试工具的熟悉、相关基础知识的学习等。
需求分析
确定性能测试场景
功能测试需要测试系统的所有功能点,而性能测试只需要关注 系统功能点中 比较容易成为系统性能瓶颈的部分
比如,一个商城系统, 通常系统的瓶颈 在于 大量客户登录浏览商品,秒杀抢购某个商品 的业务场景。
而 管理员 后台操作 通常不会成为性能瓶颈,因为管理员就寥寥数人而已。
所以性能测试的场景应该是这些客户浏览商品、秒杀抢购,而不是去测试管理员 后台操作。
确定性能测试场景, 测试人员需要了解 产品的所有功能点,并且了解业务使用,甚至了解系统的实现细节。
往往测试工程师很难做到所有这些要求, 这就需要 和 产品团队、开发团队 多方面的合作。
性能测试工程师应该仔细分析 产品的功能,并且和系统的设计者交流,细致深入思考,才能 合理、全面的选定性能测试场景。
性能场景确定后,就应该和 产品部门和开发部门一起确定 硬件环境、软件环境 和 性能指标。
性能指标
如果我们要测试 的是 白月crm 系统 这样的一个 API服务系统,
常见的性能指标有:
- 支持并发连接数量 (使用业务用户数量)
- 单位时间处理请求数量
- 响应正确的数量、百分比
- 响应错误的数量、百分比
- 响应超时的数量、百分比
- 平均响应时间
而且还应该包括,在执行性能测试的过程中,被测系统对硬件的资源占用情况,
最重要的是:
- CPU 占用率
- 内存使用率
有时也需要观察如下指标:
- 磁盘访问量
- 网络吞吐量
运行环境、数据配置
给出测试性能指标,却不提 在什么样的 运行环境、数据配置
下测试的,是没有意义的。
- 运行环境
运行环境
就是 被测系统在服务客户时,所运行的 硬件环境
和 软件环境
。
脱离 被测系统的运行环境 谈指标是毫无意义的。
进行 测试时,要尽量 使测试环境 贴近 实际的 运行环境。
运行环境,包括:
- 硬件环境
包括: 服务器机型、CPU配置、内存配置、网卡配置、硬盘配置 等
有些被测系统 运行在集群系统,就需要指明集群的整体环境配置。
有些被测系统 运行在云平台上,也需要指明相应的 环境配置。
- 软件环境
包括 : 操作系统、数据库 和 被测系统运行时所依赖的其他第三方组件服务,比如:消息队列系统、缓存系统、异步任务系统、反向代理系统等。
重要系统的设置项也应该 指明,比如 缓存的内存大小分配,数据库系统的参数设置等。
- 数据配置
数据配置 是性能测试的 业务数据设置,不同的系统有各自的业务数据。
比如 白月CRM 系统 包括:多少条注册用户、多少药品数据、多少业务订单等。
业务数据配置 对 测试结果 影响非常大。
VIP 学员请看白月crm 系统的视频介绍,思考一下,如果你来测试性能,会选取哪些功能点做性能测试。
编写测试计划、测试用例
确定 测试资源,包括:测试工程师人选、测试工具、所需测试硬件(包括何时采购到位)、测试预计启动时间、结束时间。
这和功能测试计划没有太大差异,不赘述了。
性能测试用例,就是根据前面需求分析阶段得出的测试场景来写 ,当然要注意包括 如下信息
-
运行硬件、软件环境、数据配置
-
对被测系统的输入,通过工具 测试出的 系统性能指标
-
对资源的占用指标
因为性能测试通常执行比较复杂, 为了方便执行, 测试用例中可以附加上 测试工具的对应 操作步骤 和 测试代码等。
下面是 对 白月SMS2 系统的 一个性能测试用例 示例
- 测试用例场景分析
在月末结算时,大量代理商(销售) 会登陆 白月SMS2 系统,查询 自己的销售数据, 对系统的造成性能冲击。
根据系统的架构设计、和数据库设计, 代理商查询销售数据 会涉及到 服务端对 订单表,用户表、药品表、代理商表 进行联合查询, 比较容易成为性能瓶颈。
尤其是 月末 结算时, 他们会在同一时间段,查询销售数据, 前期系统曾经出现,查询卡死现象。
本用例针对该场景进行测试。
目前系统共有 :
客户 2000 左右, 性能测试 模拟 3000
代理商账户 1800 左右, 性能测试 模拟 2000
药品 800 左右, 性能测试 模拟 1000
历史订单数量 40000 左右, 性能测试 模拟 50000
- 运行主机
主机 : 惠普 HPE ProLiant DL580 Gen10 Server
CPU : Intel® Xeon® Scalable 5220 (8 core, 2.2 GHz, 24.75 MB, 125 W)
内存 : 32 GB (2x 16 GB) RDIMM
硬盘 : 1TB SSD ( 三星970 Pro M2 SSD)
网络 : 千兆网口 连接 千兆 交换机 连接 千兆网口测试主机
- 操作系统
- 其他软件系统
- 测试工具
- 数据配置:
- 测试过程
使用 黑羽压测 模拟高峰期 代理商登录查询场景。
单个 代理商的 模拟操作如下:
1. 从登录网页 登录网站
2. 查询自己的所有订单(25条,分5页展示)
每隔30秒,点击下一页订单,到底后,每隔30秒,点击前一页订单
这样循环2次
预估 单个代理商操作总时长 (30*4*2)*2 = 480秒 (8分钟)
整个性能场景如下:
每秒2个代理商登录, 直到 2000 人全部登录操作完毕。预计耗时 1000秒
每个代理商各自进行上述流程的操作.
整个测试预计耗时 1000+480=1480秒 。
注意:
-
测试前,进入黑羽压测监控统计界面,先点击 右上角
清除
按钮,确保重新产生统计文件 -
测试前,使用 黑羽压测 部署资源统计软件到被测主机,删除老的资源统计数据,并启动 对 被测主机的 资源统计进程
-
测试完成后, 停止对 被测主机的 资源统计进程,获取资源统计数据
-
预期结果
并发连接数量 : 系统要求最大支持10000代理商客户同时访问,
所以该场景 不应该出现 HTTP 连接超时错误
请求 正确响应率 = 100%
平均响应时长 <= 500ms
响应时长 [500ms,1000ms] 不超过 20个
响应时长 [1000ms,2000ms] 不超过 10个
响应时长 [2000ms,5000ms] 不超过 5个
在执行性能测试的过程中,被测系统对硬件的资源占用情况,
执行测试
运行环境搭建
首先要搭建 硬件运行环境,包括 运行主机、网络环境 等。 根据产品规划,可能简单到 一台主机即可,也可能复杂到 搭建 大型的集群环境、云集群环境。
现在很多公司产品都部署到云服务器上, 这种情况我们做性能测试,有两种方式:
- 本地测试
需要购买 几台 主机,放到公司实验室里。
这些主机 一部分是组建被测系统, 一部分是安装测试工具。
然后在本地进行测试。
- 云端测试
需要购买 几台云主机, 一部分是组建被测系统, 一部分是安装测试工具。
然后直接 在 云端环境进行测试。
千万不要 只是被测系统 在 云端, 自己本地用测试工具进行测试。
因为 本地到云端 网络 节点很多,时延较长, 而且带宽往往也是受限的,失去了性能测试的意义。
硬件环境准备好后, 要在其上安装 软件运行环境,包括:操作系统、数据库 等 系统依赖的软件。
用例执行
创建数据环境
执行性能测试用例,通常需要准备大量的测试数据。
大家不要小看性能测试的 数据准备,往往是非常麻烦的。
特别是首次创建数据环境。需要自己编写、调试创建环境的 脚本,程序,很考验开发技能。
比如,白月SMS2 系统, 上面给出的示例用例 就需要 准备大量客户、销售、药品、订单数据。这些数据怎么创建?
可以写Python程序,调用API插入。
当然也可以直接用黑羽压测,导入数据。
上面给出的 测试用例,用黑羽压测进行初始化,代码如下。具体的代码讲解,请观看视频。
# 定义 创建 客户、代理商、药品的数量
NUM_CUSTOMER = 3000
NUM_SALES = 2000
NUM_MEDICINE = 1000
# 每个代理商创建多少订单
ORDERS_PER_SALES = 25
from urllib.parse import quote
from base64 import b64encode
# 创建客户端
client = HttpClient('127.0.0.1', # 目标地址:端口
timeout=10 # 超时时间,单位秒
)
# 管理员登录
username = 'byhy'
password = '88888888'
up = b64encode(quote(username+'#$%'+password).encode())
print('管理员登录')
response = client.sendAndRecv(
'POST',
'/api/mgr/signin',
data={
'up':up
})
# 记录管理员 token,后面直接使用
admin_jwt = response.getheader('jwt')
pprint(response.json('utf8'))
# 添加客户
print(f'添加{NUM_CUSTOMER}客户')
# 记录 客户 id,后面添加订单时要用到
customerids = []
for i in range(NUM_CUSTOMER):
username = f'hospital_{i}'
print(f'添加客户 {username}')
response = client.sendAndRecv(
'POST',
'/api/mgr/users',
headers={'Authorization':admin_jwt},
json= {
"action":"add_customer",
"data":{
"username": username,
"password": "88888888",
"realname": f"测试医院{i}",
"desc": f"测试医院{i}",
"phone": f"1380000{i:04d}"
}
}
)
ret = response.json('utf8')
if ret["retcode"] != 0:
print('添加失败')
exit()
customerids.append(ret["id"])
# 添加代理商
print(f'添加{NUM_SALES}代理商')
for i in range(NUM_SALES):
username = f'sales_{i}'
print(f'添加代理商 {username}')
response = client.sendAndRecv(
'POST',
'/api/mgr/users',
headers={'Authorization':admin_jwt},
json= {
"action":"add_distributor",
"data":{
"username": username,
"password": "88888888",
"realname": f"测试代理商{i}",
"desc": f"测试代理商{i}",
"phone": f"1380001{i:04d}"
}
}
)
ret = response.json('utf8')
if ret["retcode"] != 0:
print('添加失败')
exit()
# 添加药品
print(f'添加{NUM_MEDICINE}药品')
# 记录 药品 id,后面添加订单时要用到
medicineids = []
for i in range(NUM_MEDICINE):
medicinename = f'青霉素_{i:04d}'
print(f'添加药品 {medicinename}')
response = client.sendAndRecv(
'POST',
'/api/mgr/medicines',
headers={ 'Authorization':admin_jwt },
json= {
"action":"add_one",
"data":{
"name": medicinename,
"desc": "青霉素 国字号",
"sn": f"SN00001{i:04d}"
}
}
)
ret = response.json('utf8')
if ret["retcode"] != 0:
print('添加失败')
exit()
medicineids.append(ret["id"])
# 添加订单
print(f'每个代理商 创建{ORDERS_PER_SALES}条订单')
for i in range(NUM_SALES):
# 代理商登录
username = f'sales_{i}'
print(f'代理商{username}登录')
password = '88888888'
up = b64encode(quote(username+'#$%'+password).encode())
response = client.sendAndRecv(
'POST',
'/api/distributor/signin',
data={
'up':up
})
sales_jwt = response.getheader('jwt')
# 创建订单
for j in range(ORDERS_PER_SALES):
time1 = time.time()
# 随机挑选 客户 和 药品, 作为订单的采购内容
from random import randint
customerid = customerids[randint(0,len(customerids)-1)]
medicineid = medicineids[randint(0,len(medicineids)-1)]
ordername = f"测试订单_{username}_{j}"
print(f'创建订单 {ordername}')
response = client.sendAndRecv(
'POST',
'/api/distributor/orders',
headers={'Authorization':sales_jwt},
json={
"action": "wf_order",
"wf_action": "wf_submit_order",
"data": {
"name": ordername,
"desc": ordername,
"customer_id": customerid,
"medicinelist": [
{
"id": medicineid,
"v": "20000",
"name": "青霉素盒装"
}
]
}
}
)
ret = response.json('utf8')
if ret["retcode"] != 0:
print('创建订单失败')
exit()
orderid = ret["id"]
# 审批订单
# print('审批订单')
response = client.sendAndRecv(
'POST',
'/api/mgr/orders',
headers={'Authorization':admin_jwt},
json={
"action": "wf_order",
"wf_action": "wf_approve_order",
"data": {
"id": orderid,
"comment": "同意"
}
}
)
ret = response.json('utf8')
if ret["retcode"] != 0:
print('审批订单失败')
exit()
# 签收订单
# print('签收订单')
response = client.sendAndRecv(
'POST',
'/api/distributor/orders',
headers={'Authorization':sales_jwt},
json={
"action": "wf_order",
"wf_action": "wf_confirm_receive",
"data": {
"id": orderid,
"comment": "收到"
}
}
)
ret = response.json('utf8')
if ret["retcode"] != 0:
print('签收订单失败')
exit()
takes = time.time()-time1
print(f'一个订单耗时{takes} 秒')
调用API插入的方式可能耗时很长(为什么?自己思考一下)。
比如上面的代码,在普通个人电脑上执行,创建完数据,要耗时 1小时左右。
如果有更大量的测试数据要导入,就会花费更多时间。
实际项目中,调用API插入数据慢到不可接受。
所以往往 我们采用的方法是: 通过SQL语句直接往数据库插入数据。
这种方式,会快很多。
当然, 前提是 写程序的人 需要 充分了解产品的数据库定义
。
还是上面的用例,同样的数据,用下面的代码,导入MySQL
数据库 ,只需不到1分钟,即可完成数据导入。比用API插入的程序快 几十倍!!
# 定义 创建 客户、代理商、药品的数量
NUM_CUSTOMER = 3000
NUM_SALES = 2000
NUM_MEDICINE = 1000
# 每个代理商创建多少订单
ORDERS_PER_SALES = 25
# 客户id范围,用户表开始有3条数据
ID_CUSTOMER = [4, 4 + NUM_CUSTOMER - 1]
# 代理商id范围
ID_SALES = [4 + NUM_CUSTOMER, 4 + NUM_CUSTOMER + NUM_SALES - 1]
# 药品id范围,药品表开始有1条数据
ID_MEDICINE = [1, 1 + NUM_MEDICINE - 1]
from random import randint
import MySQLdb
# 创建一个 Connection 对象,代表了一个数据库连接
conn = MySQLdb.connect(
host="192.168.1.100",# 数据库IP地址
user="user2", # mysql用户名
passwd="Mima123$", # mysql用户登录密码
db="bycrm" , # 数据库名
charset = "utf8")
c = conn.cursor()
# 添加客户
print('添加客户')
tplt = '''INSERT INTO `by_user` (`password`, `last_login`, `is_superuser`, `username`, `first_name`, `last_name`, `email`, `is_staff`, `is_active`, `date_joined`, `usertype`, `realname`, `desc`, `phone`, `avatar_url`) VALUES
('md5$eaLJpard8wB4$136393387ec65b9e4a4cc5e4bb46ada9', NULL, 0, 'hospital_%s', '', '', '', 1, 1, '2018-10-07 13:26:16.355364', 3000, '测试医院%s', '测试医院%s', NULL, NULL) ; '''
for i in range(NUM_CUSTOMER):
sql = tplt % (i, i, i)
c.execute(sql)
conn.commit()
# 添加代理商
print('添加代理商')
tplt = '''INSERT INTO `by_user` (`password`, `last_login`, `is_superuser`, `username`, `first_name`, `last_name`, `email`, `is_staff`, `is_active`, `date_joined`, `usertype`, `realname`, `desc`, `phone`, `avatar_url`) VALUES
('md5$eaLJpard8wB4$136393387ec65b9e4a4cc5e4bb46ada9', NULL, 0, 'sales_%s', '', '', '', 1, 1, '2018-10-07 13:26:16.355364', 4000, '测试代理商%s', '测试代理商%s', NULL, NULL) ; '''
for i in range(NUM_SALES):
sql = tplt % (i, i, i)
c.execute(sql)
conn.commit()
# 添加药品
print('添加药品')
tplt = '''INSERT INTO `by_medicine` ( `name`, `sn`, `desc`) VALUES
( '青霉素_%s', 'snb000001', '青霉素_%s'); '''
for i in range(NUM_MEDICINE):
sql = tplt % (i, i)
c.execute(sql)
conn.commit()
# 添加订单
print('添加订单')
tplt1 = '''
INSERT INTO `by_order` ( `name`, `create_date`, `desc`, `creator_id`, `customer_id`, `workflow_ver`, `workflow_cur_statename`, `workflow_cur_statecode`, `fee`,`medicinelist`, `workflow_rec`) VALUES
('$订单名$', '2019-10-13 16:33:33.140735', '$订单名$', $代理商id$, $客户id$, '1.0', '订单完成', 's3', $金额$,
'[{"id": $药品id$, "v": "200", "name": "测试药品"}]',
'{"steps": [{"statecode": "init", "statename": "创建", "time": "2019-10-13 16:33:33", "action": "wf_submit_order", "actionname": "提交订单", "actor": $代理商id$, "actorname": "$代理商姓名$"}, {"statecode": "s1", "statename": "审核", "time": "2019-10-13 16:33:33", "action": "wf_approve_order", "actionname": "批准订单", "actor": 1, "actorname": "白月黑羽", "comment": ""}, {"statecode": "s5", "statename": "发货", "time": "2019-10-13 16:33:33", "action": "wf_confirm_receive", "actionname": "确认收货", "actor": $代理商id$, "actorname": "$代理商姓名$", "comment": ""}, {"statecode": "s3"}]}')
'''
# 订单id,因为数据库中已经有一条记录,所以从2开始
orderid = 2
medicine_order_recs = []
for i in range(NUM_SALES):
salesname = f'sales_{i}' # 代理商登录名
salesrealname = f'测试代理商_{i}' # 代理商真实姓名
sales_uid = ID_SALES[0] + i # 代理商id
print(salesrealname)
for j in range(ORDERS_PER_SALES):
# 随机挑选 客户 和 药品, 作为订单的采购内容
customerid = randint(ID_CUSTOMER[0], ID_CUSTOMER[1])
medicineid = randint(ID_MEDICINE[0], ID_MEDICINE[1])
sql = tplt1.replace('$订单名$', f'测试订单_{salesrealname}_{j}') \
.replace('$代理商id$', f'{sales_uid}') \
.replace('$客户id$', f'{customerid}') \
.replace('$药品id$', f'{medicineid}') \
.replace('$药品名称$', f'{medicineid}') \
.replace('$代理商姓名$', f'{salesrealname}') \
.replace('$金额$', f'{randint(50000, 90000)}.00')
# 订单表插入记录
c.execute(sql)
medicine_order_recs.append((medicineid, orderid))
orderid += 1
conn.commit()
# by_order_medicine 表插入记录
tplt2 = '''INSERT INTO `by_order_medicine` ( `amount`, `medicine_id`, `order_id`) VALUES
( 2000, '%s', '%s'); '''
print('by_order_medicine 表插入记录 ')
for rec in medicine_order_recs:
sql = tplt2 % rec
c.execute(sql)
conn.commit()
conn.close()
print('=== 完成 ====')
但是这种 方式 有一定风险,要确保你的插入数据格式 和 当前发布的产品 插入的 数据 格式 一致。
因为,你要测试的新版本,有可能数据格式已经有了改变,你必须做出相应的调整。
执行测试步骤
请大家点击下面两个链接,边看 讲解视频,边学习后面的内容
执行测试,主要就是根据测试用例的描述进行测试。
先写好对应测试用例的 测试代码。
实战班学员请联系 老师获取 上面 用例 对应的代码。
我使用 黑羽压测 测试完 该 后,得到如下的 性能统计图
和 如下的 被测主机 资源 占用图
具体请参考视频讲解。