大道至简,知易行难;知行合一,止于至善。
谨以此文,献给在测试路上前进的各位同学~~
(ps:我们先来了解下性能测试理论方面知识)
性能测试的定义:
通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。负载测试和压力测试都属于性能测试,两者可以结合进行。
性能测试的手段:
是通过模拟真实业务从而向服务器发送大量并发请求进而对被测系统产生负载,分析被测系统在不同压力下的表现。
我们进行性能测试的常见目的如下:
a:评估系统的性能(在局域网测试环境或生产环境下,通过测试结果的分析评估当前系统的服务级别)。
b: 定位性能瓶颈(通过性能测试找出影响系统整体性能的关键步骤或过程,为系统调优提供方向性依据)。
c:验证调优结果(通过比对优化后和优化前的测试结果,确认性能优化策略是否生效)。
1.2.1压力测试:
通过逐步增加系统负载,测试系统性能的变化,并最终确定在什么负载条件下系统性能处于失效状态来获得系统能提供的最大服务级别的测试。
压力测试是逐步增加负载,使系统某些资源达到临界点。
1.2.2负载测试:
通过逐步增加系统负载,测试系统性能的变化,并最终确定在满足性能指标的前提下,系统所能够承受的最大负载量的测试。
1.2.3稳定性测试:
通过给系统加载一定的业务压力(如CPU资源在70%~90%的使用率)的情况下,运行一段时间,检查系统是否稳定。因为运行时间较长,所以通常可以测试出系统是否有内存泄露等问题。
1.2.4:容量测试:
在一定的软、硬件条件下,在数据库中构造不同数量级的记录数量,通过运行一种或多种业务场景,在一定虚拟用户数量的情况下,获取不同数量级别的性能指标,从而得到数据库能够处理的最大会话能力、最大容量等。
1.2.5配置测试:
通过对被测试软件的软硬件配置的测试。配置测试能充分利用有限的软硬件资源,发挥系统的最佳处理能力,同时可以将其与其他性能测试类型联合应用,为系统调优提供参考。
(PS:在实施性能测试的过程中,整体工作流程是1:分析性能测试需求--2:设计性能测试方案3:开发性能测试脚本-4:搭建性能测试环境-5:执行测试-:6:分析结果后多轮测试进行验证优化-7:编写性能测试报告-8:编写性能测试总结报告)
(ps:以我之前做过的一个小需求逐步开始吧~~ )
目前公司开发人员15名,测试人员7名;使用TeamFoundation进行文档和测试用例以及bug的管理,团队考虑使用开源版禅道系统代替现有的teamfoundation。
此次性能测试活动目标如下:
1:能否使用禅道开源版(8.0.1)代替TeamFoundation进行项目活动管理。
2:评估禅道开源版(8.0.1)在一定的服务器硬件配置(cpui5 3.3GHz+内存8G)下的最大负载。
(ps:分析团队历史数据)
每版本测试用例数统计:
(ps:在12个迭代版本中 最多新建用例1832个)
单日提交bug数统计:
每小时提交Bug数统计:
以近一年来12个版本的数据进行分析,团队现状如下:
版本最大Case数:1832 单个版本最大Bug数:113
每月Release一个新版本,即:每22个工作日进行一次发布。
在每次releae过程中 编写测试用例的时间为:3天 执行测试的时间为 7天(一轮回归测试)。
按峰值大致算出团队目前各场景的事务数如下:
(ps:测试过程中对峰值tps也需要留20%的富余)
团队成员对业务及禅道环境的要求:
(ps:性能需求分析是性能测试流程中的第一步,如果这一步做好了 接下来的测试方案设计,脚本开发,测试执行,测试报告都会轻松很多; 反推也是成立的,如果不清楚需求是什么 后面多的一切都是白做!
另外:收集需求数据的途径有1:运维拉取生产环境的历史数据。2:参考竞品。3:对数据增量可以进行容量建模。
切记一点:千万别用什么所谓的二八原则,没有数据依据一切都是胡扯!)
(ps:完整的性能测试方案 直接拿去用吧~ 嘿嘿~)
本次性能测试的主要目的在于:
测试已完成系统的综合性能表现,检验交易或系统的处理能力是否满足系统运行的性能要求; 发现交易中存在的性能瓶颈,并对性能瓶颈进行修改; 模拟发生概率较高的单点故障,对系统得可靠性进行验证;
序号 | 业务名称 | 优先级 | 备注 | |
---|---|---|---|---|
1 | 登录系统 | 中 | ||
2 | 添加测试用例 | 高 | ||
3 | 执行用例 | 高 | ||
4 | 提交Bug | 高 | ||
5 | 解决Bug | 高 | ||
6 | 关闭Bug | 高 | ||
8 | 确认Bug | 高 | ||
9 | ||||
10 |
/***明确列出说明本次测试需要关注的测试指标的定义及范围,不需要关注的测试指标也应列出。下面的内容供参考。***/
本次性能测试需要获得的性能指标如下:
交易的响应能力:即在单交易负载和模拟生产交易情况的混合场景负载压力情况下,系统的响应时间。 每秒处理事务数:即应用系统在单位时间内完成的交易量(TPS)。 系统可支持的并发用户数量。
本次性能测试的限制性指标为:
系统资源使用情况:在正常压力下,应用服务器和数据库服务器的CPU、Memory占用率应分别低于80%、80%,数据库存储空间和文件系统空间占用率应低于80%。 交易的成功率:交易成功率不低于99.5%。
本次性能测试不需要关注的指标:
业务流程/路径覆盖率。 业务数据的完整、正确性。 其他诸如系统易用性、可管理性等属于专项测试的内容。
///***明确本次测试各功能项的测试指标需要达到的测试目标,该目标须由项目组提出或最终确认。***///
针对不同类型交易的单业务事务平均响应时间 针对不同类型交易的单业务事务TPS值 在负载情况下的单业务事务平均响应时间 在负载情况下的单业务事务TPS值 在负载情况下的系统综合TPS值
///***说明本项目生产环境的物理架构,可以以物理架构图或网络拓扑图的方式。***///
///*说明本项目性能测试环境的物理架构,可以以物理架构图的方式。*///
可使用Visio画出测试环境和生产环境的网络拓扑图。
测试环境的网络拓扑图(单Web服务器+单数据库服务器)很简单在此省略。
说明本项目测试环境与生产环境的差异,确定性能测试环境的软硬件资源,包括待测系统各组成部分的配置。下表供参考,非强制使用。
服务器 | 性能测试环境(规划) | 生产环境(规划) | ||
---|---|---|---|---|
硬件配置 | 软件配置及IP | 硬件配置 | 软件配置 | |
Web&DB服务器 | Cpu:i5 Disk:500G Memory:8G | 192.168.10.206 | Cpu:i3 Disk:500G Memory:8G | Os:Win7(64位) WebServer:Apache2.4 DB:MySql5.5 PHP: 5.4.19 |
负载生成服务器 | Cpu:i3 Disk:500G Memory:8G Net:100Mb局域网 | 192.168.10.188 OS:Win7(64位) LR 11 | -- | -- |
/*说明本次测试的测试环境安装情况。*/
XAMPP集成环境:
控制面板 1.2.6 phpmyadmin版本:Version 4.0.8 php版本:PHP 5.4.19 (cli) (built: Aug 21 2013 01:12:03) apache版本:Server version: Apache/2.4.4 (Win32) mysql版本:Ver 5.5.32 |
---|
负载机:LoadRunner 11(patch3+patch4)(192.168.10.206)
/*说明本次测试使用到的测试工具和监控工具。*/
浏览器:IE11,Chrome43
协议抓包:HttpWatch9.3
性能脚本:LoadRunner 11
监控工具:Monyog MySQL和LoadRunner11 Controller。
测试数据图表生成:ECharts
/**说明本次测试的测试脚本、测试数据以及混合场景的交易配比情况等。**/
系统用户:开发角色的用户40个 测试角色的用户20个。
项目:3个项目(Storage,Training,Clinical)
HttpWatch抓去登录禅道的请求:
登录请求返回的内容中 没有标识登录是否成功的字符
所以:插入检查点时检查登录请求中从服务器返回的内容里是否包括/zentao/index字符串。
登录禅道事务中参数UserName取值为测试+开发用户名。
成功添加测试用例时 会从服务器返回添加用例是否成功的标识
插入检查点 手动判断添加用例事务是否成功
HttpWatch抓取执行用例请求:
执行用例的请求中 从服务器端并未返回事务是否成功的标识
所以插入检查点验证是否返回selfClose来手动判断执行用例事务的成功与否
每个测试用例可多次执行 无权限问题
说明本次测试的测试方法(内容)及测试案例、测试场景设计。下面章节供参考。
系统登录场景设计如下:
场景1 | 登陆系统 | |||||
---|---|---|---|---|---|---|
目的 | 测试多人同时登陆系统的性能情况 | |||||
方法 | ||||||
Vusers和资源 | ||||||
并发用户数 | CPU/Mem/Disk数据 | Apache数据 | MySql数据 | 网络使用率 | 事务平均响应时间 | |
10人 | ||||||
20人 |
添加测试用例场景设计如下:
场景2 | 添加测试用例 | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
目的 | 测试多人同时添加测试用例的性能情况 | ||||||||||||
方法 | |||||||||||||
Vusers和资源 | |||||||||||||
并发用户数 | CPU/Mem/Disk数据 | Apache数据 | MySql数据 | 网络使用率 | 事务平均响应时间 | ||||||||
10人 |
提交Bug场景设计如下:
场景3 | 提交Bug | ||||||
---|---|---|---|---|---|---|---|
目的 | 测试多人同时提交Bug的性能情况 | ||||||
方法 | |||||||
Vusers和资源 | |||||||
并发用户数 | CPU/Mem/Disk数据 | Apache数据 | MySql数据 | 网络使用率 | 事务平均响应时间 | ||
10人 |
解决Bug 场景设计如下:
场景4 | 解决Bug | ||||||
---|---|---|---|---|---|---|---|
目的 | 测试多人同时更改Bug为fixed状态的性能情况 | ||||||
方法 | |||||||
Vusers和资源 | |||||||
并发用户数 | CPU/Mem/Disk数据 | Apache数据 | MySql数据 | 网络使用率 | 事务平均响应时间 | ||
15人 | |||||||
30人 |
关闭Bug场景设计如下:
场景5 | 关闭Bug | ||||||
---|---|---|---|---|---|---|---|
目的 | 测试多人同时关闭Bug的性能情况 | ||||||
方法 | 10个Vusers并发(测试人员才使用关闭Bug功能) | ||||||
Vusers和资源 | |||||||
并发用户数 | CPU/Mem/Disk数据 | Apache数据 | MySql数据 | 网络使用率 | 事务平均响应时间 | ||
10人 |
确认bug场景设计如下:
场景6 | 确认bug | ||||||
---|---|---|---|---|---|---|---|
目的 | 测试多人同时确认Bug的性能情况 | ||||||
方法 | 20个Vusers并发(测试人员才使用关闭Bug功能) | ||||||
Vusers和资源 | |||||||
并发用户数 | CPU/Mem/Disk数据 | Apache数据 | MySql数据 | 网络使用率 | 事务平均响应时间 | ||
20人 |
登录负载测试:
场景7 | 登陆系统 | |||||
---|---|---|---|---|---|---|
目的 | 评估登录场景最大TPS | |||||
方法 | 120-270人登陆系统的TPS | |||||
Vusers和资源 | ||||||
并发用户数 | CPU使用率 | Mem使用率 | TPS | 网络使用率 | 事务平均响应时间 | |
100-140人 | ||||||
100-180人 | ||||||
180-220人 |
添加用例最大TPS:
场景8 | 添加用例 | |||||
---|---|---|---|---|---|---|
目的 | 评估添加用例场景最大TPS | |||||
方法 | 120-270人添加用例的TPS | |||||
Vusers和资源 | ||||||
并发用户数 | CPU使用率 | Mem使用率 | TPS | 网络使用率 | 事务平均响应时间 | |
100-140人 | ||||||
100-180人 | ||||||
180-220人 |
执行用例最大TPS:
场景9 | 执行用例 | |||||
---|---|---|---|---|---|---|
目的 | 评估执行用例场景最大TPS | |||||
方法 | 120-270人执行用例的TPS | |||||
Vusers和资源 | ||||||
并发用户数 | CPU使用率 | Mem使用率 | TPS | 网络使用率 | 事务平均响应时间 | |
100-140人 | ||||||
100-180人 | ||||||
180-220人 |
提交Bug最大TPS:
场景10 | 提交Bug | |||||
---|---|---|---|---|---|---|
目的 | 评估提交Bug场景最大TPS | |||||
方法 | 120-270人提交bug的TPS | |||||
Vusers和资源 | ||||||
并发用户数 | CPU使用率 | Mem使用率 | TPS | 网络使用率 | 事务平均响应时间 | |
100-140人 | ||||||
100-180人 | ||||||
180-220人 |
解决Bug最大TPS:
场景11 | 解决Bug | |||||
---|---|---|---|---|---|---|
目的 | 评估解决Bug场景最大TPS | |||||
方法 | 120-270人解决bug的TPS | |||||
Vusers和资源 | ||||||
并发用户数 | CPU使用率 | Mem使用率 | TPS | 网络使用率 | 事务平均响应时间 | |
100-140人 | ||||||
100-180人 | ||||||
180-220人 |
关闭Bug最大TPS:
场景12 | 关闭Bug | |||||
---|---|---|---|---|---|---|
目的 | 评估关闭Bug场景最大TPS | |||||
方法 | 120-270人关闭bug的TPS | |||||
Vusers和资源 | ||||||
并发用户数 | CPU使用率 | Mem使用率 | TPS | 网络使用率 | 事务平均响应时间 | |
100-140人 | ||||||
100-180人 | ||||||
180-220人 |
在测试环境经过确认,脚本预验证之后对本次测试涉及的全部联机交易做基准测试。目的是验证测试脚本及后台环境、初步检查交易本身是否存在性能缺陷。
测试方法:
使用LoadRunner测试工具向192.168.10.188服务器发送交易请求,接收并分析返回结果。拟采用10Vuser负载执行,取交易的平均响应时间作为衡量指标,并计算吞吐量
对本次测试涉及的全部联机交易完成基准测试后,分别执行单交易负载测试。目的是获得交易本身的性能表现,诊断交易是否存在性能缺陷。
测试方法:
使用LoadRunner测试工具向206服务器发送交易请求,接收并分析返回结果。
多场景压测系统7x24小时
/*说明在测试完成后需要输出的阶段性成果,作为检验测试的衡量标准。
当测试完成以后,需提交的主要文档包括,但不仅限于:
《禅道开源版v8.0.1性能测试方案》 《禅道开源版v8.0.1性能测试记录及问题跟踪表》 《禅道开源版v8.0.1性能测试报告》
在测试工作量估算数据的基础上,考虑现有的资源情况,对资源进行具体安排,根据项目整体进度计划,列出进度表,即是谁在什么时间内完成什么任务。下表供参考,非强制使用。
序号 | 名称 | 责任人 | 工期 | 开始时间 | 完成时间 |
---|---|---|---|---|---|
1 | 禅道开源版v8.0.1性能测试 | X工作日 | 2016-2-22 | 2016-2-28 | |
1.1 | 测试准备 | x工作日 | 2016-2-22 | 2016-2-22 | |
1.1.1 | 测试实施方案制定 | x 工作日 | 2016-2-22 | 2016-2-22 | |
1.1.2 | 测试主机、数据库环境就绪 | x 工作日 | 2016-2-22 | 2016-2-22 | |
1.1.4 | 性能测试业务数据就绪 | x 工作日 | 2016-2-22 | 2016-2-22 | |
1.1.5 | 服务部署就绪 | x 工作日 | 2016-2-22 | 2016-2-22 | |
1.1.6 | 测试脚本编制、参数就绪 | x 工作日 | 2016-2-23 | 2016-2-23 | |
1.2 | 基准、单交易负载测试 | x工作日 | 2016-2-23 | 2016-2-24 | |
1.2.1 | 单交易基准测试 | x 工作日 | 2016-2-23 | 2016-2-24 | |
1.4 | 负载测试 | x工作日 | 2016-2-24 | 2016-2-26 | |
1.5 | 测试总结 | x 工作日 | 2016-2-27 | 2016-2-28 |
/*风险管理是对影响项目测试的各种可能发生的风险进行估计,以及对风险的发生几率和严重程度进行估计,并按照估计结果对风险进行排序*/
风险描述 | 风险发生的可能性 | 风险对项目的影响 | 责任人 | 规避方法 |
---|---|---|---|---|
测试环境与运行环境差距较大,通过测试得到的运行参数偏差。在试运行阶段需要重新进行参数验证。 | 中 | 低 | 根据测试结果反推生产环境,由运维同学监控数据并作及时调整。 | |
测试数据量和数据库中预埋数据量较小,通过测试时间推算的批量处理交易的运行时间满足要求,生产环境下数据不能满足。 | 中 | 高 | 等比缩放,减小系统数据容量。 | |
由于发现较严重缺陷引发较长时间的程序修改,或因环境准备、数据等原因造成测试进度延迟。 | 高 | 中 | 预留测试环境维护时间,不定时备份测试环境和数据 | |
(PS:不管是录制还是手工开发脚本,对协议报文体系的了解是基本功 必不可少的,我们先熟悉下http协议的报文体系哈)
抓包工具有很多,重量级的有wireshark 次之有fiddler,charles和Omnipeek,轻量级的小插件当然也可以 比如firebug和httpwatch都很轻巧好用 抓包必备神器。
我们现在从上向下依次看下请求报文里都是些什么鬼
POST 表示http的方法
(ps:http的方法有以下几种
GET 请求获取Request-URI所标识的资源 POST 在Request-URI所标识的资源后附加新的数据 HEAD 请求获取由Request-URI所标识的资源的响应消息报头 PUT 请求服务器存储一个资源,并用Request-URI作为其标识 DELETE 请求服务器删除Request-URI所标识的资源 TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断 CONNECT 保留将来使用 OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求)
/WebServices/TrainTimeWebService.asmx/getStationAndTimeByStationName 请求的资源对象(这里是一个webservice接口中的一个方法)
HTTP/1.1 表示所使用的http协议版本号
请求报文头部的Accept表示浏览器端可接受的媒体类型。
(PS: Accept: */* 代表浏览器可以处理所有返回的媒体类型; Accept: text/html 代表浏览器可以接受服务器回发的类型为 text/html ;如果服务器无法返回text/html类型的数据,服务器应该返回一个406错误(non acceptable) )
告诉服务器当前请求是从哪个页面链接过来的,服务器可以获得一些信息用于处理。
(PS:
我们抓包可以看到每次请求的Header中,基本上都有Referer值,值就是当前页面的url。
浏览器所做的工作相当简单。如果在页面A上点击一个链接,然后进入B页面,浏览器就会在请求中插入一个带有页面A的Referer首部;自己输入的URL中不会保护Referer首部)。
指定客户端可接受或优选哪些语言(比如,内容所使用的自然语言)
客户端应用程序信息(浏览器内核,操作系统内核信息等)
指定请求报文中对象的媒体类型(MIME)。
(PS:我们常见的媒体类型如下
1. text:用于标准化地表示的文本信息,文本消息可以是多种字符集和或者多种格式的;默认是text/plain;
2. multipart:用于连接消息体的多个部分构成一个消息,这些部分可以是不同类型的数据;默认是multipart/mixed;
3. application:用于传输应用程序数据或者二进制数据;默认是application/octet-stream;
4. message:用于包装一个E-mail消息;
5. image:用于传输静态图片数据;
6. audio:用于传输音频或者音声数据;
7. video:用于传输动态影像数据,可以是与音频编辑在一起的视频数据格式。)
指定客户端接受的编码方式。通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate,compress;q=0.5 这不是指字符编码)
声明与服务器的连接机制,如keep-alive等
主要用于有代理网络环境,这样服务器或其他代理就可以指定不应传递的逐跳首部了。
请求的实体报文长度
表示是否开启DNT(Do not track)功能,1表示开启,0表示关闭。
声明需要请求URI的主机信息。
Pragma头域用来包含实现特定的指令,最常用的是Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。
(PS:这里是请求的body(主体)部分,我也把=号后面的字符串解码后是杭州)
http协议的请求头部总结:
常用的标准请求头包括下面几个:
非标准请求头包括下面几个:
响应报文头部信息
http协议的版本,服务器返回的响应状态码,
报文创建的日期和时间。
Web服务器的信息
声明该响应是通过哪种语言生成的
指定响应遵循的缓存机制
PS:Cache-Control主要有以下几种类型:
响应报文的媒体类型。
Content-Length: 7325
服务器响应报文body长度。
常用的http协议的响应头信息总结:
非标准的响应头包括下面几个:
状态码: | 原因短语: | 含义: |
---|---|---|
100 | Continue(继续) | 收到请求的起始部分,客户端应该继续请求 |
101 | Switching Protocols(切换协议) | 服务器正根据客户端的指示协议切换成update首部列出的协议 |
200 | OK | 服务器已经成功处理请求 |
201 | Created(已创建) | 对那些要服务器创建对象的请求来说 资源已创建完毕 |
202 | Accept(已接受) | 请求已接受,但服务器尚未处理 |
203 | Non-Authoritative Informatino(非权威信息) | 服务器成功处理,只是实体头部来自资源的副本 |
204 | No Content(没有内容) | 响应报文包含一些首部和一个状态行 但不包含实体的主题内容 |
205 | Reset Content(重置内容) | 浏览器应该重置当前页面上所有的HTML表单 |
206 | Partial Content(部分内容) | 部分请求成功 |
300 | Multiple Choices(多项选择) | 客户端请求了实际指向多个资源的URL |
301 | Moved Permanently(永久搬离) | 请求的URL已移走,响应中应包含一个Location URL说明资源新位置 |
302 | Found(已找到) | 请求的URL临时性移走。 |
303 | See Other(参考其他) | 告诉客户端应用另一个URL |
304 | Not Modified(未修改) | 资源未发生过改变 |
305 | Use Proxy(使用代理) | 必须通过代理访问资源,代理的位置在Location首部 |
307 | Temporary Redirect(临时重定向) | |
400 | Bad Request(坏请求) | 服务器不能理解收到的请求,即发出了异常请求。 |
401 | Unauthorized(未授权) | 在客户端获得资源访问权之前,先要进行身份认证 |
403 | Forbidden(禁止) | 服务器拒绝了请求 |
404 | Not Found(未找到) | 服务器无法找到所请求的URL |
405 | Method Not Allowed(不允许使用的方法) | 请求中有一个所请求的URI不支持的方法 |
406 | Not Acceptable(无法接受) | |
407 | Proxy Authentication Required(要求进行代理认证) | |
408 | Request Timeout(请求超时) | |
409 | Conflict(冲突) | 发出的请求在资源上造成了一些冲突 |
410 | Gone(已消失) | 请求的资源曾经在服务器存在 |
411 | Length Required(要求长度指示) | 服务器要求在请求报文中包含Content-Length头部 |
412 | Precondition Failed(先决条件失败) | 服务器无法满足请求的条件 |
413 | Request Entity Too Large(请求实体太大) | 客户端发送的请求所携带的请求URL超过了服务器能够或者希望处理的长度 |
414 | Request URI Too Long(请求URI太长) | |
415 | Unsupported Media Type(不支持的媒体类型) | 服务器无法理解或不支持客户端所发送的实体的内容类型 |
416 | Requested Range Not Satisfiable(所请求的范围未得到满足) | |
417 | Expectation Failed(无法满足期望) | |
500 | Internal Server Error(服务器内部错误) | |
501 | Not Implemented(未实现) | |
502 | Bad Gateway(网关故障) | 作为代理或网关使用的服务器遇到了来自响应链中上游的无效响应 |
503 | Service Unavailable(未提供次服务) | 服务器目前无法为请求提供服务 |
504 | Gateway Timeout(网关超时) | |
505 | HTTP Version Not Supported(不支持的HTTP版本) |
1:配置浏览器网络代理(以Chrome51为例)
”设置””网络””更改代理服务器设置”
代理地址设置为127.0.0.1 端口要使用未被占用的端口(此处为9999)
2:配置LoadRunner Record Options中的Port Mapping
Target Server:目标服务器的IP地址
比如:我想访问118主机上的Easystudy服务 这里IP地址填写如上图。
Traffic Forwarding:勾选允许通过本地端口,这里的本地端口要填写一个没被占用的(和浏览器端的设置一致9999)。
注意事项如下:
1: Application type要设置为Win32 Applications
通过Port Mapping录制脚本的时候
Application type已经不是Internet Application了,这里需要选择为Win32 Applications,以为录制的时候 LR会启动一个”LoadRunner Socket Proxy Starter”的小窗口。
2:Program to record
必须填写为LR安装目录下bin\wplus_init_wsock.exe文件的绝对路径。
3:Capture level的设置
默认情况下是Socket level data,如果录制完成后脚本为空,
需要更改为Socket level and WinNet level data 然后重新录制即可。
1:共享笔记本上的wifi(我这里用的是猎豹的wifi共享小工具)。
2:设置手机端wifi代理
3:开启Fiddler抓包(下图以滴滴出行为例)
(PS:好好学下Fiddler这款神器哈~)
(ps:了解了前面的http报文的结构,接着要乘胜追击 一起来看下如何根据抓包内容手工开发性能脚本吧)
为下一个请求添加头部信息:
为后续的所有请求添加头部信息:
我也也可以在录制脚本的时候通过设置来确定哪些请求头的信息需要保存:
Record Options---Advanced---Headers
另外:web_cleanup_auto_headers()停止向后面的 HTTP 请求中添加自定义标头。
web_revert_auto_header | 停止向后面的 HTTP 请求中添加特定的标头,但是生成隐性标头。 |
---|
设置下一个请求中包含或排除的URL
为后续所有请求设置包含或排除的URL
增加一个新的cookie或修改现有的cookie
添加自定义的cookie
清空当前用户保存的所有cookie
Web_url函数格式:
Web_submit_data函数格式:
HTTP协议post方法常见的有4种方式,所以脚本的格式也不尽相同:
这应该是最常见的 POST 提交数据的方式了。浏览器的原生 <form> 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。
首先,Content-Type 被指定为 application/x-www-form-urlencoded;其次,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码。大部分服务端语言都对这种方式有很好的支持。
application/x-www-form-urlencoded 格式的post请求脚本如下:
这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 <form> 表单的 enctyped 等于 multipart/form-data。
我们接着看下另一个非常常用的函数
Web_custome_request()
这个函数可以模拟get和post请求,所以 如果我们手工开发脚本的时候 可以用这个函数搞定所有类型的请求报文发送的模拟。
3:application/json
对HTTP或者HTTPS协议的报文模拟来说,使用web_url,web_submit_data,web_custome_request这三个函数足够了。
事务的开始标识
事务的结束标识
(PS: 这里需要注意:对于所有事务都不要使用LR自带的LR_AUTO来判断事务成功与否,
为什么?
因为 LR_AUTO只判断发送到服务器端的请求格式是否正确 而不会去判断我们的业务,LR还没智能到清楚地知道每个使用者的业务逻辑这个地步)。
检查点:web_reg_find
注册一个保存动态数据为参数的请求(关联):
注册一个将动态数据边界值为参数的请求(这个函数是web_reg_save_param的升级):
字符串转换函数:
atoi() 函数用来将字符串转换成整数(int)
atoi读取字符串的起始部分,通过在第一个非数值字符停止
返回脚本中的一个参数当前的值:
lr_eval_string
将程序中的常量或变量保存为lr中的参数:
lr_save_string
将两个char类型字符串连接:
strcat
字符串编码转换
lr_convert_string_encoding
(PS:LR的函数还有很多其他的函数,各位同学可通过F1来查看使用方法)
讲个自己在之前开发脚本的过程中遇到一个小故事吧~~
测试环境配置:
LoadRunner 11(patch3+patch4)
HttpWatch 9.3
Web服务器:IIS6.0
OS:win7 64位
浏览器:火狐28,IE11
业务操作:添加病理评估检查(分三步:1:保存病人基本信息 2:填写保存原发灶信息 3:填写保存转移灶信息)
初始版脚本:通过FireFox28 用LoadRunner进行录制。
回放问题:进行参数化,关联动态数据后,卡在保存原发灶这一步,脚本截图如下:
回放日志信息:需要进行关联的字段analysepk不存在
第一想法就是把这一步骤从服务器返回的信息打印出来。
修改这一步之前的注册函数web_reg_save_param中左右边界分别为”{“和”}” 然后lr_output_message输出服务器返回的信息,结果如下:
看到flag字段对应的值为false个人猜测很可能是业务失败。
使用HttpWatch抓包 查看保存原发灶时 服务器正确的返回格式,如下图:
看到flag字段对应值为true 追问开发同学后 确定:flag=false是业务没处理成功。
再三检查Body数据(经历了URL解码,随机字符串生成俩坑) 确认无误~~在此Run脚本 错误依然存在。
思虑再三,决定找开发同学要源码看看 这一步操作调用哪个方法? 接收几个参数?参数的类型是什么?对接收的参数做什么判断处理?
为方便开发跟进,我把脚本里的服务地址改到开发同学的机器上 有ta打断点 步步跟进。
开发定位第一步:发现报出异常信息,数据库里某个pk字段违反了唯一性约束
我检查自己的脚本发现问题所在
我在httpwatch里查找这些pk值只有保存原发灶的请求时才出现,个人臆测为可以传入为相同的随机32位字符串。
追着开发继续跟进 这些pk不是从服务器端返回的 是不是可以随机生成?可不可以不写?
开发定位第二步:最终确定这些pk值不传也是可以的,于是将该请求中这些pk字段值置为空,重新Run脚本。Bingo~~~! 搞定!
也希望各位同学在性能测试脚本开发的过程中 把理解业务这个点作为重中之重!
Run区块中可指定整个Run逻辑的迭代次数,这里设置的迭代次数不会对Initial和End生效。
Block中可包含多个Action,并且可设置Action的执行概率。Block中可包含Block。
Pacing:可设置迭代间的时间间隔。
(PS:调试的时候可以勾选上Enable logging, 若需要查看服务端返回的信息 我们可以选择Extended log,用到的最多场景是勾选Parameter substitution)
切记 执行测试的时候把log关掉,否则硬盘里被塞满日志文件。
(PS:思考时间用来模拟实际操作过程中的等待时间;如果只是想评估在一定环境配置下服务器能承受多大并发则可以忽略思考时间;另:思考时间一定不要放到事务内)
simulate browser cache 选项 这个选项是指虚拟用户使用缓存模拟浏览器,缓存是用来将经常使用的文件保存到本地,这样可以减少网络连接时间,默认情况下,缓存模拟是启用的,当缓存被设置为禁用后,虚拟用户将忽略所有的缓存功能并且在每一次请求的时候下载所有的资源。(PS:即使设置了禁用缓存模拟,对于页面上的每个资源仅被下载一次,即使该资源出现了多次。一种资源可以是一张图片、一个框架或者其他类型的脚本文件)。当使用LoadRunner进行并发测试的时候,每个用户都使用自己的缓存并且从缓存中检索图片。如果禁用了缓存选项,所有的虚拟用户都不会使用缓存来模拟浏览器。
Cache URLs requiring content (HTML) 选项这个选项是指Vugen仅缓存网页的一些必要信息,这些信息可以是一些必须的验证信息、分析数据或者关联数据,当你勾选了这项后,这些信息自动被缓存(默认是启用)。
Check for newer versions of stored pages every visit to the page 选项这个选项是指浏览器会将存储在cache中的网页信息和最新浏览的页面进行比较,当勾选此项时,vugen会增加"If-modified-since"到HTTP包头,在场景执行过程中这个选项可以显示最新的网页信息,但是也增加了更多的网络流量,通常配置这个选项是用来匹配浏览器设置来达到模拟浏览器的目的。 Download non-HTML resources 选项这个选项是指虚拟用户在回放期间访问网站时加载图片的过程,这里图片是指随着页面录制的图片和那些没有随页面录制下来的图片。(PS:禁用此选项后,可能会遇到图片验证失败,因为在访问网站的时候有些图片是会发生变化的)。
Simulate a new user each iteration 选项这个选项是指VuGen在迭代过程中重置了所有的HTTP内容,此设置允许虚拟用户能够更准确的模拟用户开始进行新的会话,它删除了所有的Cookie,关闭了所有的TCP连接(包含保活包),清除了模拟浏览器的缓存,重置了HTML框架,并且清除了用户名和密码。
Clear cache on each iteration 选项在每次迭代过程中清除浏览器中缓存来达到模拟一个真实用户第一次访问网页,清除该复选框以禁用此选项,允许虚拟用户使用缓存来存储用户信息,来模拟一个已经访问过网页的用户。
(PS:在运行时设置中 切换到Download Filters页,选择Exclude address in list,添加外网URL即可不下载外网资源)
设置后再次运行脚本 日志中无报错信息,结果如下:
规则组合1:Sequential + Each iteration + Continue with last value
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Sequential | Each iteration | Continue with last value |
取值结果如下图 在一次迭代中取同一个值;在不同迭代中按列表顺序取值;当参数不足时,进行下一轮循环参数列表。
规则组合2:Sequential + Each occurence+ Continue with last value
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Sequential | Each occurence | Continue with last value |
取值结果:在一次迭代中,参数每出现一次则按顺序取一个新的参数值;不同迭代中则按顺序取新的值;参数不足时则循环参数列表顺序取值。
规则组合3:Sequential + Once + Continue with last value
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Sequential | Once | Continue with last value |
取值结果:无论多少次迭代,无论多少个虚拟用户进行并发,只取一个值。
规则组合4:Random + Each iteration + Continue with last value
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Random | Each iteration | Continue with last value |
取值结果:在一次迭代中,无论参数出现几次都根据随机取同一个值;不同迭代中则取新的随机值。
规则组合5:Random + Each occurence + Continue with last value
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Random | Each occurence | Continue with last value |
取值结果:在Actions中每次出现参数时都会随机取值。
规则组合6:Random + Once+ Continue with last value
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Random | Once | Continue with last value |
取值结果:在Actions中只会随机取一个值。
规则组合7:Unique + Each iteration + Continue with last value
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Unique | Each iteration | Continue with last value |
取值结果:每次迭代都会取唯一的一个参数值;当参数不足时会取最后一个,但是log中会报错。
规则组合8: Unique + Each iteration + Continue in a cyclic manner
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Unique | Each iteration | Continue in a cyclic manner |
取值结果:每次迭代会取唯一的一个参数值;当参数不足时循环参数列表。
规则组合9:Unique + Each iteration + abort Vuser
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Unique | Each iteration | abort Vuser |
取值结果:每次迭代会取唯一的一个参数值;当参数不足时中止Vuser执行。
规则组合10:Unique + Each occurence + Continue with last value
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Unique | Each occurence | Continue with last value |
取值结果:每次参数出现会取唯一的一个参数值;当参数不足时日志中报错。
规则组合11:Unique + Each occurence + Continue in a cyclic manner
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Unique | Each occurence | Continue in a cyclic manner |
取值结果:每次参数出现会取唯一的一个参数值;当参数不足时循环参数列表。
规则组合12:Unique + Each occurence + abort Vuser
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Unique | Each occurence | abort Vuser |
取值结果:每次参数出现会取唯一的一个参数值;当参数不足时中止Vuser执行。
规则组合13:Random + Once + Continue with last value
Select Next Value | Update Value On | When Out Of Values |
---|---|---|
Random | Once | Continue with last value |
取值结果:每个Vuser只会取唯一的一个参数值。
万法归一:可以通过simulate parameters来查看取参的结果。
关联是指获取服务器端返回的动态数据,将客户端的数据与服务器端的数据建立联系。
什么时候使用关联:
当我们需要使用从服务器端返回的动态数据时,我们需要使用web_reg_save_param这个注册函数将服务器端返回的数据保存到参数中。
下表列出web_reg_save_param可用的属性。
搭建windows下的禅道环境很简单,官网下载压缩包,解压后启动
即可。
(ps:禅道集成环境试用mysql数据库,并集成了phpmyadmin来通过浏览器对数据库进行方便的管理)
手动场景设计可以通过建立组并指定脚本、负载生成器和每个组中包括的Vuser数来建立手动场景;
还可以通过百分比模式建立手动场景,使用此方法建立场景可以指定场景中将使用的Vuser的总数,并为每个脚本分配负载生成器和占总数一定百分比的Vuser。
脚本执行模式:
(PS:我们常用的场景设置对应我们的性能测试需求时如下:
1) 基准测试:
采用一个虚拟用户迭代执行N次,取测试结果的平均值作为基准参考值。
2) 单交易负载
采用多个用户并发的方式对单交易进行负载测试,分析性能指标是否满足需求。 使用峰值加压,可以测试特定峰值下系统性能情况。 使用梯队加压,可以测试系统的最大并发用户数和最佳并发用户数。
建议:在单交易负载测试时,采用峰值加压和梯度加压相结合的方式进行测试。
3) 综合场景负载
模拟真实交易场景,分析正常业务交易量,选取日交易量最多的几个交易,合理分配并发用户数,持续运行一段时间,分析测试结果是否满足需求。
4) 稳定性测试
根据需求选取几个常用交易,按照峰值压力的百分比进行加压,稳定运行一段时间,查看系统运行是否正常,系统资源利用率是否在最佳状态。
)
在面向目标的场景中,可以定义要实现的测试目标,LoadRunner会根据这些目标自动构建场景。
可以在一个面向目标的场景中定义希望场景达到的下列5种类型的目标:虚拟用户数、每秒单击次数(仅Web Vuser)、每秒事务数、每分钟页面数(仅Web Vuser)或事务响应时间。可以通过单击【Edit Scenario Goal...】设置,如目标类型、最小用户数、最大用户数、运行时间等。
(PS:当我们的性能测试需求非常明确的时候,我们可以采用面向目标的模式来验证系统服务的级别;也可来验证我们的优化结果是否满足要求)
(PS:当我们需要模拟大量Vusers时需要多台负载机来生成负载,避免负载机成为瓶颈,从而更精确地模拟并发场景)
1、安装,在需要添加为负载机的计算机上安装loadrunner 11
2 启动agent HP loadrunner-advanced setting-loadrunner agaent process启动LR agaent。
3、关闭负载机的防火墙
4、添加负载机,在场景所在的机器添加负载机为当前场景压力机,录入负载机的ip、操作系统等信息(操作系统和临时目录可以不录入采用默认),点击more:出现‘负载生成器设置选项卡’
5、连接负载机,点击connect按钮连接负载机,status列变为ready表示负载机可用,列头表示的是负载机的资源使用情况,如果表示为绿色表示有空余的资源,红色表示服务器忙碌。
6、为脚本配置对应压力机,在group里面可以为每一个脚本配置对应的压力机,配置方法是点击load generator选择压力机。
(PS:极力推荐spotlight系列的工具,高端专业监控Oracle/MySQL数据库。
当然我们也可以自己写shell或者python脚本来监控数据库)。
我们一起来看下Oracle数据库的常用性能监控指标:
--监控事例的等待--
--回滚段的争用情况--
--监控表空间的 I/O 比例--
--监控文件系统的 I/O 比例--
--在某个用户下找所有的索引--
--监控 SGA 的命中率--
--监控 SGA 中字典缓冲区的命中率--
--监控 SGA 中共享缓存区的命中率,应该小于1%--
--监控SGA中重做日志缓存区的命中率,应该小于1%--
--监控内存和硬盘的排序比率 增加 sort_area_size--
--监控当前数据库谁在运行什么SQL语句--
--监控字典缓冲区--
----此值大于0.5时,参数需加大--
servers_highwater接近mts_max_servers时,参数需加大
---碎片程度--
--查看碎片程度高的表--
---表、索引的存储情况检查--
--检测数据库中的事件和等待--
--查询会话中的事件和等待时间--
--查询等待进程--
---当前 sql 语句--
--查询高速缓存中的命中率--
--查看表锁--
--监控事例的等待--
--查看前台正在发出的SQL语句--
--数据表占用空间大小情况--
--查看表空间碎片大小--
--查看表空间占用磁盘情况--
--查看session使用回滚段--
--查看SGA区剩余可用内存--
---非系统用户建在SYSTEM表空间中的表--
---性能最差的SQL--
---读磁盘数超100次的sql--
---最频繁执行的sql--
---查询使用CPU多的用户session--
Windows上的可以使用python脚本来监控,Linux上的极力推荐nmon 谁用谁知道哈~
至于OS的性能指标,大家自行谷歌即可~
强调一点:我们没必要非要用LoadRunner进行OS的监控~ 方法很多 自行摸索
Linux OS的常用性能指标:
度 量 | 描 述 |
---|---|
Average load | 上一分钟同时处于“就绪”状态的平均进程数 |
Collision rate | 每秒钟在以太网上检测到的冲突数 |
Context switches rate | 每秒钟在进程或线程之间的切换次数 |
CPU utilization | CPU的使用时间百分比 |
Disk rate | 磁盘传输速率 |
Incoming packets error rate | 接收以太网数据包时每秒钟接收到的错误数 |
Incoming packets rate | 每秒钟传入的以太网数据包数 |
Interrupt rate | 每秒内的设备中断数 |
Outgoing packets errors rate | 发送以太网数据包时每秒钟发送的错误数 |
Outgoing packets rate | 每秒钟传出的以太网数据包数 |
Page-in rate | 每秒钟读入到物理内存中的页数 |
Page-out rate | 每秒钟写入页面文件和从物理内存中删除的页数 |
Paging rate | 每秒钟读入物理内存或写入页面文件的页数 |
Swap-in rate | 正在交换的换入进程数 |
Swap-out rate | 正在交换的换出进程数 |
System mode CPU utilization | 在系统模式下使用CPU的时间百分比 |
User mode CPU utilization | 在用户模式下使用CPU的时间百分比 |
Windows OS常用的性能指标:
对 象 | 度 量 | 描 述 | ||
---|---|---|---|---|
System | % Total Processor Time | 系统上所有处理器都忙于执行非空闲线程的时间的平均百分比。 在多处理器系统上,如果所有处理器始终繁忙,此值为100%,如 果所有处理器为50%繁忙,此值为50%,而如果这些处理器中的四分 之一是100%繁忙的,则此值为25%。它反映了用于有用作业上的时间 的比率。每个处理器将分配给空闲进程中的一个空闲线程,它将消耗 所有其他线程不使用的那些非生产性处理器周期 | ||
Processor | % Processor Time (Windows 2000) | 处理器执行非空闲线程的时间百分比。该计数器设计为处理器活动的 一个主要指示器。它是通过测量处理器在每个采样间隔中执行空闲进 程的线程所花费的时间,然后从100%中减去此时间值来进行计算的 (每个处理器都有一个空闲线程,它在没有其他线程准备运行时消 耗处理器周期)。它可以反映有用作业占用的采样间隔的百分比。 该计数器显示在采样期间所观察到的繁忙时间的平均百分比。 它是通过监控服务处于非活动状态的时间值, 然后从100%中减去此值来进行计算的 | ||
对 象 | 度 量 | 描 述 | ||
System | File Data Operations/sec | 计算机对文件系统设备执行读取和写入操作的速率。 这不包括文件控制操作 | ||
System | Processor Queue Length | 以线程数计的处理器队列的即时长度。如果不同时监控线 程计数,则此计数始终为0。所有处理器都使用一个队列, 而线程在该队列中等待处理器进行循环调用。此长度不包括 当前正在执行的线程。一般情况下,如果处理器队列的长度一 直超过2,则可能表示处理器堵塞。此值为即时计数,不是一段时间的平均值 | ||
Memory | Page Faults/sec | 此值为处理器中的页面错误的计数。当进程引用特定的虚拟内存 页,该页不在主内存的工作集当中时,将出现页面错误。如果某页 位于待机列表中(因此它已经位于主内存中),或者它正在被共享该 页的其他进程所使用,则页面错误不会导致该页从磁盘中提取出 | ||
PhysicalDisk | % Disk Time | 选定的磁盘驱动器对读写请求提供服务的已用时间所占百分比 | ||
Memory | Pool Nonpaged Bytes | 非分页池中的字节数,指可供操作系统组件完成指定任务后从其中获得 空间的系统内存区域。非分页池页面不可以退出到分页文件中。它们自 分配以来就始终位于主内存中 | ||
Memory | Pages/sec | 为解决引用时不在内存中的页面的内存引用,从磁盘读取的或写入磁盘的 页数。这是Pages Input/sec和Pages Output/sec的和。此计数器中包括的 页面流量代表着用于访问应用程序的文件数据的系统缓存。此值还包括 存入/取自非缓存映射内存文件的页数。如果关心内存压力过大问题(即 系统失效)和可能产生的过多分页,则这是值得观察的主要计数器 | ||
System | Total Interrupts/sec | 计算机接收并处理硬件中断的速度。可能生成中断的设备有系统时钟、鼠 标、数据通信线路、网络接口卡和其他外围设备。此计数指示这些 设备在计算机上所处的繁忙程度 | ||
Objects | Threads | 计算机在收集数据时的线程数。注意,这是一个即时计数,不是一 段时间的平均值。线程是能够执行处理器指令的基础可执行实体 | ||
Process | Private Bytes | 专为此进程分配,无法与其他进程共享的当前字节数 |
使用LoadRunner自带的lr_user_data_point监控tomcat
1、 配置Tomcat登录用户,找到tomcat安装目录下的/conf/ tomcat-users.xml,添加配置如下:
(配置Tomcat登录用户后,建议测试一下配置的用户登录能否登录进入Tomcat管理页面)
2、 在Action脚本中,使用web_set_user("用户名","密码","tomcat服务器所在的IP地址:端口");
3、 脚本中编写web_url(); 模拟访问Tomcat的url 并登录
4、 利用关联函数web_reg_save_parm()动态地捕获想要的数据
5、 最后利用打点函数lr_user_data_point(“监控指标名”,”监控指标值”);记录用户自定义的数据样本
VuGen脚本代码如下:
(PS: 需要注意的是,我们有两种方法来使用lr_user_data_point,1:将自定义时间和我们的业务请求放到一个Action中;2:将自定义时间单独放在一个Action中,并将这个action放到业务处理的Action之后)
使用 ngxtop 监控 Nginx
ngxtop 默认会从其配置文件 (/etc/nginx/nginx.conf) 中查找 Nginx 日志的地址。所以,监控 Nginx ,运行以下命令即可:
1 | $ ngxtop |
---|
这将会列出10个 Nginx 服务,按请求数量排序。
显示前20个最频繁的请求:
1 | $ ngxtop -n 20 |
---|
获取Nginx基本信息:
1 | $ ngxtop info |
---|
你可以自定义显示的变量,简单列出需要显示的变量。使用 “print” 命令显示自定义请求。
1 | $ ngxtop print request http_user_agent remote_addr |
---|
显示请求最多的客户端IP地址
1 | $ ngxtop top remote_addr |
---|
显示状态码是404的请求
1 | $ ngxtop -i 'status == 404' print request status |
---|
除了Nginx,ngtop 还可以处理其他的日志文件,比如 Apache 的访问文件。使用以下命令监控 Apache 服务器
1 | $ tail -f /var/log/apache2/access.log | ngxtop -f common |
---|
个人推荐Zabbix
zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。它能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。
zabbix由两部分构成,zabbix server与可选组件zabbix agent。
zabbix server可以通过SNMP,zabbix agent,ping,端口监视等方法提供对远程服务器/网络状态的监视,数据收集等功能,它可以运行在Linux,Solaris,HP-UX,AIX,Free BSD,Open BSD,OS X等平台上。
Zabbix主要功能:
- CPU负荷
- 内存使用
-磁盘使用
- 网络状况
- 端口监视
- 日志监视。
Zabbix的使用啥的 这儿不多说了 谁用谁知道~~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。