小试牛刀—完整实例带你探究LR性能测试(PartA)

大道至简,知易行难;知行合一,止于至善。

谨以此文,献给在测试路上前进的各位同学~~

一:性能测试理论知识

(ps:我们先来了解下性能测试理论方面知识)

1.1:性能测试及其目的

性能测试的定义:

通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。负载测试和压力测试都属于性能测试,两者可以结合进行。

性能测试的手段:

是通过模拟真实业务从而向服务器发送大量并发请求进而对被测系统产生负载,分析被测系统在不同压力下的表现。

我们进行性能测试的常见目的如下:

a:评估系统的性能(在局域网测试环境或生产环境下,通过测试结果的分析评估当前系统的服务级别)。

b: 定位性能瓶颈(通过性能测试找出影响系统整体性能的关键步骤或过程,为系统调优提供方向性依据)。

c:验证调优结果(通过比对优化后和优化前的测试结果,确认性能优化策略是否生效)。

1.2性能测试的种类细分

1.2.1压力测试:

通过逐步增加系统负载,测试系统性能的变化,并最终确定在什么负载条件下系统性能处于失效状态来获得系统能提供的最大服务级别的测试。

压力测试是逐步增加负载,使系统某些资源达到临界点。

1.2.2负载测试:

通过逐步增加系统负载,测试系统性能的变化,并最终确定在满足性能指标的前提下,系统所能够承受的最大负载量的测试。

1.2.3稳定性测试:

通过给系统加载一定的业务压力(如CPU资源在70%~90%的使用率)的情况下,运行一段时间,检查系统是否稳定。因为运行时间较长,所以通常可以测试出系统是否有内存泄露等问题。

1.2.4:容量测试:

在一定的软、硬件条件下,在数据库中构造不同数量级的记录数量,通过运行一种或多种业务场景,在一定虚拟用户数量的情况下,获取不同数量级别的性能指标,从而得到数据库能够处理的最大会话能力、最大容量等。

1.2.5配置测试:

通过对被测试软件的软硬件配置的测试。配置测试能充分利用有限的软硬件资源,发挥系统的最佳处理能力,同时可以将其与其他性能测试类型联合应用,为系统调优提供参考。

1.3性能测试的实施流程

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\}FD@BTZ3M2_P2424IB4@`R5.png

(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:分析团队历史数据)

每版本测试用例数统计:

C:\Users\IP-206\Downloads\用例和缺陷统计.png

(ps:在12个迭代版本中 最多新建用例1832个)

单日提交bug数统计:

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\MM25QR%WN4P(J@WBH_GO`0I.png

每小时提交Bug数统计:

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\$NW5KKVSU5(G)YP$W[59`]V.png

以近一年来12个版本的数据进行分析,团队现状如下:

版本最大Case数:1832 单个版本最大Bug数:113

每月Release一个新版本,即:每22个工作日进行一次发布。

在每次releae过程中 编写测试用例的时间为:3天 执行测试的时间为 7天(一轮回归测试)。

按峰值大致算出团队目前各场景的事务数如下:

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\09%WH9GEHS2ITTTV[@QE6{R.png

(ps:测试过程中对峰值tps也需要留20%的富余)

团队成员对业务及禅道环境的要求:

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\B]AL%2TD9GX2P5W8VY$SD{6.png

(ps:性能需求分析是性能测试流程中的第一步,如果这一步做好了 接下来的测试方案设计,脚本开发,测试执行,测试报告都会轻松很多; 反推也是成立的,如果不清楚需求是什么 后面多的一切都是白做!

另外:收集需求数据的途径有1:运维拉取生产环境的历史数据。2:参考竞品。3:对数据增量可以进行容量建模。

切记一点:千万别用什么所谓的二八原则,没有数据依据一切都是胡扯!)

三:性能测试方案设计

(ps:完整的性能测试方案 直接拿去用吧~ 嘿嘿~)

3.1. 测试目的、范围与目标

3.1.1 测试目的

本次性能测试的主要目的在于:

 测试已完成系统的综合性能表现,检验交易或系统的处理能力是否满足系统运行的性能要求;  发现交易中存在的性能瓶颈,并对性能瓶颈进行修改;  模拟发生概率较高的单点故障,对系统得可靠性进行验证;

3.1.2. 测试功能范围

序号

业务名称

优先级

备注

1

登录系统

2

添加测试用例

3

执行用例

4

提交Bug

5

解决Bug

6

关闭Bug

8

确认Bug

9

10

3.1.3. 测试指标范围

/***明确列出说明本次测试需要关注的测试指标的定义及范围,不需要关注的测试指标也应列出。下面的内容供参考。***/

本次性能测试需要获得的性能指标如下:

 交易的响应能力:即在单交易负载和模拟生产交易情况的混合场景负载压力情况下,系统的响应时间。  每秒处理事务数:即应用系统在单位时间内完成的交易量(TPS)。  系统可支持的并发用户数量。

本次性能测试的限制性指标为:

 系统资源使用情况:在正常压力下,应用服务器和数据库服务器的CPU、Memory占用率应分别低于80%、80%,数据库存储空间和文件系统空间占用率应低于80%。  交易的成功率:交易成功率不低于99.5%。

本次性能测试不需要关注的指标:

 业务流程/路径覆盖率。  业务数据的完整、正确性。  其他诸如系统易用性、可管理性等属于专项测试的内容。

3.1.4. 测试目标

///***明确本次测试各功能项的测试指标需要达到的测试目标,该目标须由项目组提出或最终确认。***///

  针对不同类型交易的单业务事务平均响应时间   针对不同类型交易的单业务事务TPS值   在负载情况下的单业务事务平均响应时间   在负载情况下的单业务事务TPS值   在负载情况下的系统综合TPS值

3.2 测试资源

3.2.1. 系统生产环境物理架构

///***说明本项目生产环境的物理架构,可以以物理架构图或网络拓扑图的方式。***///

3.2.2. 性能测试环境物理架构

///*说明本项目性能测试环境的物理架构,可以以物理架构图的方式。*///

 可使用Visio画出测试环境和生产环境的网络拓扑图。

测试环境的网络拓扑图(单Web服务器+单数据库服务器)很简单在此省略。

3.2.3. 性能测试环境与生产环境资源对比

说明本项目测试环境与生产环境的差异,确定性能测试环境的软硬件资源,包括待测系统各组成部分的配置。下表供参考,非强制使用。

服务器

性能测试环境(规划)

生产环境(规划)

硬件配置

软件配置及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

--

--

3.3. 测试准备

3.3.1. 测试环境安装

/*说明本次测试的测试环境安装情况。*/

 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)

3.3.2. 测试工具

/*说明本次测试使用到的测试工具和监控工具。*/

浏览器:IE11,Chrome43

协议抓包:HttpWatch9.3

性能脚本:LoadRunner 11

监控工具:Monyog MySQL和LoadRunner11 Controller。

测试数据图表生成:ECharts

3.3. 测试脚本、数据及其预验证

/**说明本次测试的测试脚本、测试数据以及混合场景的交易配比情况等。**/

3.3.1:基础测试数据

系统用户:开发角色的用户40个 测试角色的用户20个。

项目:3个项目(Storage,Training,Clinical)

3.3.2:脚本及其验证
Script1:登录禅道(zentao)脚本

HttpWatch抓去登录禅道的请求:

登录请求返回的内容中 没有标识登录是否成功的字符

所以:插入检查点时检查登录请求中从服务器返回的内容里是否包括/zentao/index字符串。

登录禅道事务中参数UserName取值为测试+开发用户名。

代码语言:javascript
复制
Action()
{
lr_think_time(4);
lr_start_transaction("登录禅道");
//插入检查点
web_reg_find("Text=/zentao/index ","SaveCount=is_contain_server",LAST);
web_submit_data("user-login.html_2",
"Action=http://192.168.10.188/zentao/user-login.html",
"Method=POST",
"TargetFrame=",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/user-login.html",
"Snapshot=t2.inf",
"Mode=HTML",
ITEMDATA,
"Name=account", "Value={UserName}", ENDITEM,
"Name=password", "Value=123456", ENDITEM,
"Name=referer", "Value=", ENDITEM,
LAST);
//手动判断登录事务是否成功
if(atoi(lr_eval_string("{is_contain_server}"))>0)
{
lr_end_transaction("登录禅道",LR_PASS);
}
else
{
lr_end_transaction("登录禅道",LR_FAIL);
}
web_url("index.html",
"URL=http://192.168.10.188/zentao/index.html",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/user-login.html",
"Snapshot=t3.inf",
"Mode=HTML",
LAST);
return 0;
}

Script2:添加用例

成功添加测试用例时 会从服务器返回添加用例是否成功的标识

插入检查点 手动判断添加用例事务是否成功

代码语言:javascript
复制
Action()
{
//点击测试 进入测试Tab页
web_url("qa",
"URL=http://192.168.10.188/zentao/qa/",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/my/",
"Snapshot=t4.inf",
"Mode=HTML",
LAST);
//点击用例 进入用例页面
web_url("用例",
"URL=http://192.168.10.188/zentao/testcase-browse-3.html",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/bug-browse.html",
"Snapshot=t5.inf",
"Mode=HTML",
LAST);
//点击新建用例
web_url("建用例",
"URL=http://192.168.10.188/zentao/testcase-create-3-0-0.html",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/testcase-browse-3.html",
"Snapshot=t6.inf",
"Mode=HTML",
LAST);
web_url("story-ajaxGetProductStories-3-0-11-0-false-noclosed-50.html", "URL=http://192.168.10.188/zentao/story-ajaxGetProductStories-3-0-11-0-false-noclosed-50.html",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/testcase-create-3-0-0.html",
"Snapshot=t7.inf",
"Mode=HTML",
LAST);
lr_think_time(39);
//添加事务-添加测试用例
lr_start_transaction("添加测试用例");
//添加检查点
web_reg_find("Text=success","SaveCount=addcase_result",LAST);
web_submit_data("testcase-create-3-0-0.html",
"Action=http://192.168.10.188/zentao/testcase-create-3-0-0.html",
"Method=POST",
"EncType=multipart/form-data",
"TargetFrame=",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/testcase-create-3-0-0.html",
"Snapshot=t8.inf",
"Mode=HTML",
ITEMDATA,
"Name=product", "Value=3", ENDITEM,
"Name=module", "Value=11", ENDITEM,
"Name=type", "Value=feature", ENDITEM,
"Name=stage[]", "Value=", ENDITEM,
"Name=stage[]", "Value=system", ENDITEM,
"Name=title", "Value=testcase{CaseNum}", ENDITEM,
"Name=pri", "Value=3", ENDITEM,
"Name=precondition", "Value=", ENDITEM,
"Name=steps[]", "Value=step1", ENDITEM,
"Name=expects[]", "Value=expectresult{CaseNum}", ENDITEM,
"Name=steps[]", "Value=", ENDITEM,
"Name=expects[]", "Value=", ENDITEM,
"Name=steps[]", "Value=", ENDITEM,
"Name=expects[]", "Value=", ENDITEM,
"Name=keywords", "Value=", ENDITEM,
"Name=files[]", "Value=", ENDITEM,
"Name=labels[]", "Value=", ENDITEM,
LAST);
//手动判断添加测试用例是否成功
if(atoi(lr_eval_string("{addcase_result}"))>0)
{
lr_end_transaction("添加测试用例",LR_PASS);
}
else
{
lr_end_transaction("添加测试用例",LR_FAIL);
}
web_url("testcase-browse-3--byModule-11.html",
"URL=http://192.168.10.188/zentao/testcase-browse-3--byModule-11.html",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/testcase-create-3-0-0.html",
"Snapshot=t9.inf",
"Mode=HTML",
LAST);
return 0;
}

Script3:执行用例

HttpWatch抓取执行用例请求:

执行用例的请求中 从服务器端并未返回事务是否成功的标识

所以插入检查点验证是否返回selfClose来手动判断执行用例事务的成功与否

每个测试用例可多次执行 无权限问题

代码语言:javascript
复制
Action()
{
lr_think_time(8);
//添加事务-执行测试用例
lr_start_transaction("执行用例");
//添加检查点
web_reg_find("Text=selfClose","SaveCount=execuatecase_result",LAST);
web_submit_data("execuate_case",
"Action=http://192.168.10.188/zentao/testtask-runCase{case_str}.html",
"Method=POST",
"TargetFrame=",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/testtask-runCase{case_str}.html",
"Snapshot=t8.inf",
"Mode=HTML",
ITEMDATA,
"Name=steps[5]", "Value=pass", ENDITEM,
"Name=reals[5]", "Value=", ENDITEM,
"Name=case", "Value={CaseIndex}", ENDITEM,
"Name=version", "Value=1", ENDITEM,
LAST);
//手动判断事务是否成功
if(atoi(lr_eval_string("{execuatecase_result}"))>0)
{
lr_end_transaction("执行用例",LR_PASS);
}
else
{
lr_end_transaction("执行用例",LR_FAIL);
}
web_url("testcase-browse-3.html",
"URL=http://192.168.10.188/zentao/testcase-browse-3.html",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/testcase-browse-3.html",
"Snapshot=t9.inf",
"Mode=HTML",
LAST);
return 0;
}

Script4:提交Bug
代码语言:javascript
复制
Action()
{
lr_think_time(50);
//添加事务-提交Bug
lr_start_transaction("提交Bug");
//添加检查点
web_reg_find("Text=success","SaveCount=submitbug_result",LAST);
web_submit_data("bug-create-3-0-moduleID=0.html_3",
"Action=http://192.168.10.188/zentao/bug-create-3-0-moduleID=0.html",
"Method=POST",
"EncType=multipart/form-data",
"TargetFrame=",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/bug-create-3-0-moduleID=0.html",
"Snapshot=t15.inf",
"Mode=HTML",
ITEMDATA,
"Name=product", "Value=3", ENDITEM,
"Name=module", "Value=11", ENDITEM,
"Name=project", "Value=1", ENDITEM,
"Name=openedBuild[]", "Value=trunk", ENDITEM,
"Name=assignedTo", "Value=dev{DevNum}", ENDITEM,
"Name=type", "Value=codeerror", ENDITEM,
"Name=os", "Value=win7", ENDITEM,
"Name=browser", "Value=ie11", ENDITEM,
"Name=title", "Value=BugTitle{Num}", ENDITEM,
"Name=severity", "Value=3", ENDITEM,
"Name=pri", "Value=3", ENDITEM,
"Name=steps", "Value=<p>[步骤]</p>\r\n<p>[结果]</p>\r\n<p>[期望]</p>", ENDITEM,
"Name=story", "Value=", ENDITEM,
"Name=task", "Value=", ENDITEM,
"Name=mailto[]", "Value=", ENDITEM,
"Name=keywords", "Value=", ENDITEM,
"Name=files[]", "Value=", ENDITEM,
"Name=labels[]", "Value=", ENDITEM,
"Name=case", "Value=0", ENDITEM,
"Name=caseVersion", "Value=0", ENDITEM,
"Name=result", "Value=0", ENDITEM,
"Name=testtask", "Value=0", ENDITEM,
LAST);
//手动判断提交Bug是否成功
if(atoi(lr_eval_string("{submitbug_result}"))>0)
{
lr_end_transaction("提交Bug",LR_PASS);
}
else
{
lr_end_transaction("提交Bug",LR_FAIL);
}
web_url("bug-browse-3.html",
"URL=http://192.168.10.188/zentao/bug-browse-3.html",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/bug-create-3-0-moduleID=0.html",
"Snapshot=t16.inf",
"Mode=HTML",
LAST);
return 0;
}

Script5:解决Bug
代码语言:javascript
复制
Action()
{
web_url("bug-resolve-12.html",
"URL=http://192.168.10.188/zentao/bug-resolve-12.html?onlybody=yes",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/bug-browse.html",
"Snapshot=t19.inf",
"Mode=HTML",
EXTRARES,
"Url=js/kindeditor/themes/default/default.png", "Referer=http://192.168.10.188/zentao/js/kindeditor/themes/default/default.css", ENDITEM,
"Url=theme/zui/css/min.css", "Referer=http://192.168.10.188/zentao/bug-resolve-12.html?onlybody=yes", ENDITEM,
LAST);
lr_think_time(10);
//添加事务-解决Bug
lr_start_transaction("解决Bug");
//添加检查点
web_reg_find("Text=selfClose","SaveCount=fixbug_result",LAST);
web_submit_data("bug-resolve-12.html_2",
"Action=http://192.168.10.188/zentao/bug-resolve-12.html?onlybody=yes",
"Method=POST",
"EncType=multipart/form-data",
"TargetFrame=hiddenwin",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/bug-resolve-12.html?onlybody=yes",
"Snapshot=t20.inf",
"Mode=HTML",
ITEMDATA,
"Name=resolution", "Value=fixed", ENDITEM,
"Name=duplicateBug", "Value=", ENDITEM,
"Name=resolvedBuild", "Value=trunk", ENDITEM,
"Name=resolvedDate", "Value={TimeNow}", ENDITEM,
"Name=assignedTo", "Value={TesterList}", ENDITEM,
"Name=files[]", "Value=", "File=Yes", ENDITEM,
"Name=labels[]", "Value=", ENDITEM,
"Name=comment", "Value=已经解决 请验证", ENDITEM,
LAST);
//手动判断解决Bug事务是否成功
if(atoi(lr_eval_string("{fixbug_result}"))>0)
{
lr_end_transaction("解决Bug",LR_PASS);
}
else
{
lr_end_transaction("解决Bug",LR_FAIL);
}
web_url("bug-browse.html",
"URL=http://192.168.10.188/zentao/bug-browse.html",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/bug-browse.html",
"Snapshot=t21.inf",
"Mode=HTML",
LAST);
return 0;
}

Script6:关闭Bug
代码语言:javascript
复制
Action()
{
web_url("bug-close.html",
"URL=http://192.168.10.188/zentao/bug-close-{Bug_Index}.html?onlybody=yes",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/bug-browse-3-0-assignToMe-0.html",
"Snapshot=t6.inf",
"Mode=HTML",
EXTRARES,
"Url=js/kindeditor/themes/default/default.png", "Referer=http://192.168.10.188/zentao/js/kindeditor/themes/default/default.css", ENDITEM,
"Url=theme/zui/css/min.css", "Referer=http://192.168.10.188/zentao/bug-close-{Bug_Index}.html?onlybody=yes", ENDITEM,
LAST);
lr_think_time(19);
//开始事务-关闭Bug
lr_start_transaction("关闭Bug");
//添加检查点
web_reg_find("Text=selfClose","SaveCount=closebug_result",LAST);
web_submit_data("bug-close",
"Action=http://192.168.10.188/zentao/bug-close-{Bug_Index}.html?onlybody=yes",
"Method=POST",
"TargetFrame=hiddenwin",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/bug-close-{Bug_Index}.html?onlybody=yes",
"Snapshot=t7.inf",
"Mode=HTML",
ITEMDATA,
"Name=comment", "Value=关闭Bug", ENDITEM,
LAST);
//手动判断事务是否成功
if(atoi(lr_eval_string("{closebug_result}"))>0)
{
lr_end_transaction("关闭Bug",LR_PASS);
}
else
{
lr_end_transaction("关闭Bug",LR_FAIL);
}
web_url("bug-browse-3-0-assignToMe-0.html",
"URL=http://192.168.10.188/zentao/bug-browse-3-0-assignToMe-0.html",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.188/zentao/bug-browse-3-0-assignToMe-0.html",
"Snapshot=t8.inf",
"Mode=HTML",
LAST);
return 0;
}

Script7:确认 Bug
代码语言:javascript
复制
//开始事务
lr_start_transaction("确认Bug");
//插入检查点
web_reg_find("Text=selfClose","SaveCount=SureBugResult",LAST);
web_submit_data("bug-confirmBug.html_2",
"Action=http://192.168.10.206:81/zentao/bug-confirmBug-{BugId}.html?onlybody=yes",
"Method=POST",
"TargetFrame=hiddenwin",
"RecContentType=text/html",
"Referer=http://192.168.10.206:81/zentao/bug-confirmBug-{BugId}.html?onlybody=yes",
"Snapshot=t9.inf",
"Mode=HTML",
ITEMDATA,
"Name=assignedTo", "Value=dev{DevIndex}", ENDITEM,
"Name=pri", "Value=3", ENDITEM,
"Name=mailto[]", "Value=", ENDITEM,
"Name=comment", "Value=可重现 确认为Bug&nbsp;", ENDITEM,
LAST);
//手动判断事务结果
if(atoi(lr_eval_string("{SureBugResult}"))>0)
{
lr_end_transaction("确认Bug",LR_PASS);
}
else
{
lr_end_transaction("确认Bug",LR_FAIL);
}
web_url("bug-browse.html",
"URL=http://192.168.10.206:81/zentao/bug-browse.html",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=http://192.168.10.206:81/zentao/bug-browse.html",
"Snapshot=t10.inf",
"Mode=HTML",
LAST);

4. 测试方法及案例设计

说明本次测试的测试方法(内容)及测试案例、测试场景设计。下面章节供参考。

系统登录场景设计如下:

场景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人

4.1. 基准测试

在测试环境经过确认,脚本预验证之后对本次测试涉及的全部联机交易做基准测试。目的是验证测试脚本及后台环境、初步检查交易本身是否存在性能缺陷。

测试方法:

使用LoadRunner测试工具向192.168.10.188服务器发送交易请求,接收并分析返回结果。拟采用10Vuser负载执行,取交易的平均响应时间作为衡量指标,并计算吞吐量

4.2. 单场景负载测试

对本次测试涉及的全部联机交易完成基准测试后,分别执行单交易负载测试。目的是获得交易本身的性能表现,诊断交易是否存在性能缺陷。

测试方法:

使用LoadRunner测试工具向206服务器发送交易请求,接收并分析返回结果。

4.3:稳定性测试

多场景压测系统7x24小时

5. 测试输出

/*说明在测试完成后需要输出的阶段性成果,作为检验测试的衡量标准。

当测试完成以后,需提交的主要文档包括,但不仅限于:

 《禅道开源版v8.0.1性能测试方案》  《禅道开源版v8.0.1性能测试记录及问题跟踪表》  《禅道开源版v8.0.1性能测试报告》

6. 测试进度计划

在测试工作量估算数据的基础上,考虑现有的资源情况,对资源进行具体安排,根据项目整体进度计划,列出进度表,即是谁在什么时间内完成什么任务。下表供参考,非强制使用。

序号

名称

责任人

工期

开始时间

完成时间

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

7. 实施风险及规避措施

/*风险管理是对影响项目测试的各种可能发生的风险进行估计,以及对风险的发生几率和严重程度进行估计,并按照估计结果对风险进行排序*/

风险描述

风险发生的可能性

风险对项目的影响

责任人

规避方法

测试环境与运行环境差距较大,通过测试得到的运行参数偏差。在试运行阶段需要重新进行参数验证。

根据测试结果反推生产环境,由运维同学监控数据并作及时调整。

测试数据量和数据库中预埋数据量较小,通过测试时间推算的批量处理交易的运行时间满足要求,生产环境下数据不能满足。

等比缩放,减小系统数据容量。

由于发现较严重缺陷引发较长时间的程序修改,或因环境准备、数据等原因造成测试进度延迟。

预留测试环境维护时间,不定时备份测试环境和数据

四:脚本开发详解

(PS:不管是录制还是手工开发脚本,对协议报文体系的了解是基本功 必不可少的,我们先熟悉下http协议的报文体系哈)

4.1协议报文体系

抓包工具有很多,重量级的有wireshark 次之有fiddler,charles和Omnipeek,轻量级的小插件当然也可以 比如firebug和httpwatch都很轻巧好用 抓包必备神器。

4.1.1 http协议报文体系
4.1.1.2 http协议的请求格式
C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\T_]BU[AB7`1I6G6V%B4FH]D.png

我们现在从上向下依次看下请求报文里都是些什么鬼

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协议版本号

代码语言:javascript
复制
Accept: text/html, application/xhtml+xml, */*

请求报文头部的Accept表示浏览器端可接受的媒体类型。

(PS: Accept: */* 代表浏览器可以处理所有返回的媒体类型;  Accept: text/html 代表浏览器可以接受服务器回发的类型为 text/html ;如果服务器无法返回text/html类型的数据,服务器应该返回一个406错误(non acceptable) )

代码语言:javascript
复制
Referer: http://ws.webxml.com.cn/WebServices/TrainTimeWebService.asmx?op=getStationAndTimeByStationName

告诉服务器当前请求是从哪个页面链接过来的,服务器可以获得一些信息用于处理。

(PS:

我们抓包可以看到每次请求的Header中,基本上都有Referer值,值就是当前页面的url。

浏览器所做的工作相当简单。如果在页面A上点击一个链接,然后进入B页面,浏览器就会在请求中插入一个带有页面A的Referer首部;自己输入的URL中不会保护Referer首部)。

代码语言:javascript
复制
Accept-Language: zh-CN

指定客户端可接受或优选哪些语言(比如,内容所使用的自然语言)

代码语言:javascript
复制
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko

客户端应用程序信息(浏览器内核,操作系统内核信息等)

代码语言:javascript
复制
Content-Type: application/x-www-form-urlencoded

指定请求报文中对象的媒体类型(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:用于传输动态影像数据,可以是与音频编辑在一起的视频数据格式。)

代码语言:javascript
复制
Accept-Encoding: gzip, deflate

指定客户端接受的编码方式。通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate,compress;q=0.5 这不是指字符编码) 

代码语言:javascript
复制
Connection: Keep-Alive

声明与服务器的连接机制,如keep-alive等

主要用于有代理网络环境,这样服务器或其他代理就可以指定不应传递的逐跳首部了。

代码语言:javascript
复制
Content-Length: 30

请求的实体报文长度

代码语言:javascript
复制
DNT: 1

表示是否开启DNT(Do not track)功能,1表示开启,0表示关闭。

代码语言:javascript
复制
Host: www.webxml.com.cn

声明需要请求URI的主机信息。

代码语言:javascript
复制
Pragma: no-cache

Pragma头域用来包含实现特定的指令,最常用的是Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。

代码语言:javascript
复制
theCityName=%E6%9D%AD%E5%B7%9E

(PS:这里是请求的body(主体)部分,我也把=号后面的字符串解码后是杭州)

http协议的请求头部总结:

常用的标准请求头包括下面几个:

  1. Accept,声明哪种相应是可接受的,如text\plain、application\json等。
  2. Cache-Control,声明缓存控制机制,如no-cache声明不做缓存。
  3. Connection,声明与服务器的连接机制,如keep-alive等。
  4. Cookie,声明Cookie信息。
  5. Content-Type,声明请求体的MIME类型。
  6. Host,声明需要请求URI的主机信息。
  7. If-Match,声明匹配这个请求的Key,如果服务器的ETag与这个Key一致,则认为这个请求的资源没有发生改变,客户端可以选择从还从中加载这个请求。
  8. Origin,声明跨域请求的时候支持什么域名进行访问。
  9. User-Agent,声明发出这个请求的客户端的描述,如果是浏览器发出的请求,可以根据这个头判断是哪个浏览器的哪个版本。

非标准请求头包括下面几个:

  1. X-Request-With,通常通过这个头告诉服务器这个请求是XMLHttpRequest发送的。
  2. DNT,表示是否开启DNT(Do not track)功能,1表示开启,0表示关闭。
  3. Front-End-Https,是微软用的一个自定义头,与负载均衡有关。
  4. Proxy-Connection,与标准头Connection一致,是早期HTTP协议的产物。
4.1.1.2 http协议的响应格式
C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\1$K(EP2L9TBI)7$D5T@WR$1.png

响应报文头部信息

代码语言:javascript
复制
HTTP/1.1 200 OK

http协议的版本,服务器返回的响应状态码,

代码语言:javascript
复制
Date: Thu, 18 Aug 2016 06:37:22 GMT

报文创建的日期和时间。

代码语言:javascript
复制
Server: Microsoft-IIS/6.0

Web服务器的信息

代码语言:javascript
复制
X-Powered-By: ASP.NET

声明该响应是通过哪种语言生成的

代码语言:javascript
复制
X-AspNet-Version: 2.0.50727

代码语言:javascript
复制
Cache-Control: private, max-age=0

指定响应遵循的缓存机制

PS:Cache-Control主要有以下几种类型:

代码语言:javascript
复制
(1) 请求Request:
  [1] no-cache  ---- 不要读取缓存中的文件,要求向WEB服务器重新请求
  [2] no-store    ---- 请求和响应都禁止被缓存
  [3] max-age: ---- 表示当访问此网页后的max-age秒内再次访问不会去服务器请求,其功能与Expires类似,只是Expires是根据某个特定日期值做比较。一但缓存者自身的时间不准确.则结果可能就是错误的,而max-age,显然无此问题.。Max-age的优先级也是高于Expires的。
  [4] max-stale  ---- 允许读取过期时间必须小于max-stale 值的缓存对象。 
  [5] min-fresh ---- 接受其max-age生命期大于其当前时间 跟 min-fresh 值之和的缓存对象
  [6] only-if-cached ---- 告知缓存者,希望内容来自缓存且并不关心被缓存响应是否是最新的.
  [7] no-transform   ---- 告知代理,不要更改媒体类型.
(2) 响应Response:
  [1] public    ---- 数据内容皆被储存起来,就连有密码保护的网页也储存,安全性很低
  [2] private    ---- 数据内容只能被储存到私有的cache,仅对某个用户有效,不能共享
  [3] no-cache    ---- 可以缓存,但是只有在跟WEB服务器验证了其有效后,才能返回给客户端
  [4] no-store  ---- 请求和响应都禁止被缓存
  [5] max-age:   ----- 本响应包含的对象的过期时间
  [6] Must-revalidate    ---- 如果缓存过期了,会再次和原来的服务器确定是否为最新数据,而不是和中间的proxy
  [7] max-stale  ----  允许读取过期时间必须小于max-stale 值的缓存对象。 
  [8] proxy-revalidate  ---- 与Must-revalidate类似,区别在于:proxy-revalidate要排除掉用户代理的缓存的。即其规则并不应用于用户代理的本地缓存上。
  [9] s-maxage  ---- 与max-age的唯一区别是,s-maxage仅仅应用于共享缓存.而不应用于用户代理的本地缓存等针对单用户的缓存. 另外,s-maxage的优先级要高于max-age.
  [10] no-transform   ---- 告知代理,不要更改媒体类型,比如jpg,被改成png

代码语言:javascript
复制
Content-Type: text/xml; charset=utf-8

响应报文的媒体类型。

Content-Length: 7325

服务器响应报文body长度。

常用的http协议的响应头信息总结:

代码语言:javascript
复制
1:Access-Control-Allow-Origin,声明这个响应可以参与到哪个域的跨域访问中。*表示可以参与到任何域的跨域访问。
2:Allow,声明这个HTTP响应是使用哪个HTTP方法,如GET、POST等。如果是一个不支持的HTTP方法,则会返回错误码405 Method not allowed。
3:Content-Type,声明这个响应的MIME类型。
4:ETag,声明这个响应版本的key,可以标识一个资源是否有改变过(参考请求头If-Match)。
5:Pragma,声明这个响应是否支持缓存,可以设置no-cache禁用这个响应的缓存。
6:Refresh,声明这个响应在特定时间后刷新或者跳转到新的URL。
7:Status,响应的状态码。

非标准的响应头包括下面几个:

代码语言:javascript
复制
X-Frame-Options,声明防止Clickjacking攻击的参数,如deny就是防止响应被渲染在iframe里面,而sameorigin就是防止响应在非本域的页面中渲染。
X-Content-Type-Options,帮助IE能不识别MIME类型不对的stylesheet和script,也用于Chrome下载其扩展。
X-Powered-By,声明该响应是通过哪种语言生成的。

4.1.1.3 已定义的状态码及其意义

状态码:

原因短语:

含义:

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版本)

4.2 录制生成的脚本

4.2.1 录制PC端应用脚本

1:配置浏览器网络代理(以Chrome51为例)

”设置””网络””更改代理服务器设置”

C:\Users\Administrator\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\BXCGR%1$H83P~YIA[~JX0HG.png

代理地址设置为127.0.0.1 端口要使用未被占用的端口(此处为9999)

2:配置LoadRunner Record Options中的Port Mapping

代码语言:javascript
复制
“Recording Options””Port Mapping””New Entry”

C:\Users\Administrator\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\3~HYVKF~{EGN[4B)37E{KLF.jpg
C:\Users\Administrator\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\T[OTYLG01(9H[VR~OX`G3PC.jpg

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”的小窗口。

C:\Users\Administrator\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\5VDNWN`XYD_3E24XB6[JX[V.png
C:\Users\Administrator\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\6~9C[%P}A6V0G)I3P031)FP.jpg

2:Program to record

必须填写为LR安装目录下bin\wplus_init_wsock.exe文件的绝对路径。

3:Capture level的设置

C:\Users\Administrator\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\JBC%)G)ODRQM[O41))4UI23.png

默认情况下是Socket level data,如果录制完成后脚本为空,

需要更改为Socket level and WinNet level data 然后重新录制即可。

4.2.2抓包移动端APP应用脚本

1:共享笔记本上的wifi(我这里用的是猎豹的wifi共享小工具)。

G:\wifi共享.jpg

2:设置手机端wifi代理

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\NMCHSRX)XTB_EC41X%B77TJ.png

3:开启Fiddler抓包(下图以滴滴出行为例)

G:\Fiddler抓包.jpg

(PS:好好学下Fiddler这款神器哈~)

4.3 全手工开发脚本

4.3.1 C-Vuser脚本开发

(ps:了解了前面的http报文的结构,接着要乘胜追击 一起来看下如何根据抓包内容手工开发性能脚本吧)

4.3.1.1 操作报文头部的函数

为下一个请求添加头部信息:

代码语言:javascript
复制
web_add_header(“Accept-Language”,” zh-CN”)

为后续的所有请求添加头部信息:

代码语言:javascript
复制
web_add_auto_header("Accept–Encoding", "gzip");

我也也可以在录制脚本的时候通过设置来确定哪些请求头的信息需要保存:

Record Options---Advanced---Headers

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\AYX}26R0MDX4]1]Y55Q_QCM.png

另外:web_cleanup_auto_headers()停止向后面的 HTTP 请求中添加自定义标头。

web_revert_auto_header

停止向后面的 HTTP 请求中添加特定的标头,但是生成隐性标头。

4.3.1.2过滤指定URL请求的函数
代码语言:javascript
复制
web_add_filter(”action=Include/Exclude”,” Attribute=xxx”,LAST)

设置下一个请求中包含或排除的URL

代码语言:javascript
复制
web_add_auto_filter(”action=Include/Exclude”,” Attribute=xxx”,LAST)

为后续所有请求设置包含或排除的URL

4.3.1.2操作cookie的函数
代码语言:javascript
复制
web_add_cookie("lang=zh-cn;theme=default;windowWidth=1584;windowHeight=353; sid=0lab6lvctq0hh2flm48qq520h2");

增加一个新的cookie或修改现有的cookie

代码语言:javascript
复制
web_remove_cookie删除指定的cookie(和web_add_cookie对应)
web_add_cookie_ex("Cookie=client_id=China127B; path=/; expires=Wednesday, 09-Nov-2001 23:12:40 GMT; domain=", "Insert=Alt", "AllowEmptyDomain=yes", LAST );

添加自定义的cookie

代码语言:javascript
复制
web_cleanup_cookies()

清空当前用户保存的所有cookie

4.3.1.4 模拟报文发送的函数

Web_url函数格式:

代码语言:javascript
复制
web_url("Login",
"URL=http://localhost/somostorage/UserInfo/Login",
"TargetFrame=",
"Resource=0",
"RecContentType=text/html",
"Referer=",
"Snapshot=t7.inf",
"Mode=HTML",
EXTRARES,
"Url=../Images/Login/logo-center.png", "Referer=http://localhost/somostorage/Content/themes/login/gelogin.css", ENDITEM,
"Url=../Images/Login/gebutton.png", "Referer=http://localhost/somostorage/Content/themes/login/gelogin.css", ENDITEM,
"Url=../images/icomoon/icomoon.ttf", "Referer=http://localhost/somostorage/Content/UIForIpadAndIphone/css/glyphs.css", ENDITEM,
"Url=../Images/Images/user_btn.png", "Referer=http://localhost/somostorage/Content/UIForIpadAndIphone/css/index.css", ENDITEM,
"Url=../Content/UIForIpadAndIphone/images/mainview/jiantou.png", "Referer=http://localhost/somostorage/Content/UIForIpadAndIphone/css/index.css", ENDITEM,
"Url=../Images/Buttons/page_go.png", "Referer=http://localhost/somostorage/Content/themes/bluestyle/cont.css", ENDITEM,
"Url=../Content/UIForIpadAndIphone/images/mainview/bg_fileheader.png", "Referer=http://localhost/somostorage/Content/UIForIpadAndIphone/css/index.css", ENDITEM,
"Url=../Content/UIForIpadAndIphone/images/mainview/4.png", "Referer=http://localhost/somostorage/Content/UIForIpadAndIphone/css/index.css", ENDITEM,
"Url=../Content/UIForIpadAndIphone/images/imageview/mleft.png", "Referer=http://localhost/somostorage/Content/UIForIpadAndIphone/css/index.css", ENDITEM,
"Url=../Content/UIForIpadAndIphone/images/imageview/mright.png", "Referer=http://localhost/somostorage/Content/UIForIpadAndIphone/css/index.css", ENDITEM,
LAST);

Web_submit_data函数格式:

HTTP协议post方法常见的有4种方式,所以脚本的格式也不尽相同:

代码语言:javascript
复制
1:application/x-www-form-urlencoded

这应该是最常见的 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请求脚本如下:

代码语言:javascript
复制
web_submit_data("DoLogin",
"Action=http://localhost/somostorage/UserInfo/DoLogin",
"Method=POST",
"TargetFrame=",
"RecContentType=application/json",
"Referer=http://localhost/somostorage/UserInfo/Login",
"Snapshot=t8.inf",
"Mode=HTML",
ITEMDATA,
"Name=json", "Value={\"UserName\":\"admin\",\"Password\":\"admin\",\"ValidateCode\":\"\",\"IsRemenber\":\"false\",\"LoginErrorCount\":\"0\"}", ENDITEM,
LAST);

代码语言:javascript
复制
2:multipart/form-data

这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 <form> 表单的 enctyped 等于 multipart/form-data。

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\AK$SH%_M8`UIWJ`5JHG1S88.png
代码语言:javascript
复制
web_submit_data(
//step name
"UploadROIFiles",
//请求的URL地址
"Action=http://localhost/somostorage/NoSession/UploadROIFiles",
//模拟的http请求方法
"Method=POST",
//编码类型
"EncType=multipart/form-data",
//包含当前链接 资源的frame名称
"TargetFrame=",
"RecContentType=text/html",
//要提交该页面请求的URL
"Referer=http://localhost/somostorage/NoSession/RoiUploadIndex?studyPk=4c7fb2fe-e817-4068-9513-8d2a94f796a8&uid=UID201101010000010000&d=0.7157794737040377",
//快照名
"Snapshot=t29.inf",
//录制模式 有html和http
"Mode=HTML",
//属性和数据列表的分割标记,JavaVuser中不可用。
ITEMDATA,
"Name=showImport0", "Value=前端工程师技能栈.jpg", "File=Yes", ENDITEM,
//ENDITEM是列表中每个资源的结束标识符
"Name=showImport1", "Value=", "File=Yes", ENDITEM,
"Name=showImport2", "Value=", "File=Yes", ENDITEM,
"Name=showImport3", "Value=", "File=Yes", ENDITEM,
"Name=showImport4", "Value=", "File=Yes", ENDITEM,
"Name=hidMarkerPk", "Value=", ENDITEM,
"Name=hidStudyPk", "Value=4c7fb2fe-e817-4068-9513-8d2a94f796a8", ENDITEM,
"Name=hidStudyUid", "Value=UID201101010000010000", ENDITEM,
"Name=hidPostData", "Value=", ENDITEM,
"Name=hidType", "Value=", ENDITEM,
"Name=casePk", "Value=", ENDITEM,
LAST);

我们接着看下另一个非常常用的函数

Web_custome_request()

这个函数可以模拟get和post请求,所以 如果我们手工开发脚本的时候 可以用这个函数搞定所有类型的请求报文发送的模拟。

3:application/json

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\$(NC7WIG4G7E%JX@IPI8J[P.png
代码语言:javascript
复制
web_custom_request(
//step name这里可以随意命名
"restful_api",
//定义需要模拟的http请求的方法
"Method=POST",
//请求的url
"URL=http://127.0.0.1:5000/todo/create_task",
"Mode=HTTP",
//编码类型
"EncType=application/json",
//接收的内容类型
"RecContentType=application/json",
//请求报文的主体部分
"Body={\"title\": \"Create a Task\"}",
"TargetFrame=",
LAST );

对HTTP或者HTTPS协议的报文模拟来说,使用web_url,web_submit_data,web_custome_request这三个函数足够了。

4.3.1.5事务相关的函数
代码语言:javascript
复制
lr_start_transaction(“事务名称”)

事务的开始标识

代码语言:javascript
复制
lr_end_transaction("事务名称",事务状态)

事务的结束标识

(PS: 这里需要注意:对于所有事务都不要使用LR自带的LR_AUTO来判断事务成功与否

为什么?

因为 LR_AUTO只判断发送到服务器端的请求格式是否正确 而不会去判断我们的业务,LR还没智能到清楚地知道每个使用者的业务逻辑这个地步)。

4.3.1.6其他常用的函数

检查点:web_reg_find

代码语言:javascript
复制
web_reg_find("Text=Welcome", "SaveCount=Welcome_Count", LAST );

注册一个保存动态数据为参数的请求(关联):

代码语言:javascript
复制
web_reg_save_param("outFlightVal", "LB=outboundFlight value=", "RB= checked >", LAST );

注册一个将动态数据边界值为参数的请求(这个函数是web_reg_save_param的升级):

代码语言:javascript
复制
web_reg_save_param_ex("When_Txt", "LB=Where and ", "RB= do", LAST )

字符串转换函数:

atoi() 函数用来将字符串转换成整数(int)

atoi读取字符串的起始部分,通过在第一个非数值字符停止

返回脚本中的一个参数当前的值:

lr_eval_string

将程序中的常量或变量保存为lr中的参数:

lr_save_string

将两个char类型字符串连接:

strcat

字符串编码转换

lr_convert_string_encoding

(PS:LR的函数还有很多其他的函数,各位同学可通过F1来查看使用方法)

4.3.1.7 业务逻辑的重要性

讲个自己在之前开发脚本的过程中遇到一个小故事吧~~

测试环境配置:

LoadRunner 11(patch3+patch4)

HttpWatch 9.3

Web服务器:IIS6.0

OS:win7 64位

浏览器:火狐28,IE11

业务操作:添加病理评估检查(分三步:1:保存病人基本信息 2:填写保存原发灶信息 3:填写保存转移灶信息)

初始版脚本:通过FireFox28 用LoadRunner进行录制。

回放问题:进行参数化,关联动态数据后,卡在保存原发灶这一步,脚本截图如下:

D:\ToolsForTesting\TecentQQ\QQData\834321678\Image\Group\)3W62I97Q{L`H8X}E]D}T0N.png

回放日志信息:需要进行关联的字段analysepk不存在

第一想法就是把这一步骤从服务器返回的信息打印出来。

修改这一步之前的注册函数web_reg_save_param中左右边界分别为”{“和”}” 然后lr_output_message输出服务器返回的信息,结果如下:

D:\ToolsForTesting\TecentQQ\QQData\834321678\Image\C2C\F6DU9$21JMGBE%Q{W329QJ8.png

看到flag字段对应的值为false个人猜测很可能是业务失败。

使用HttpWatch抓包 查看保存原发灶时 服务器正确的返回格式,如下图:

D:\ToolsForTesting\TecentQQ\QQData\834321678\Image\C2C\OY2H77MRW7$7VE9`DG``Q62.png

看到flag字段对应值为true 追问开发同学后 确定:flag=false是业务没处理成功。

再三检查Body数据(经历了URL解码,随机字符串生成俩坑) 确认无误~~在此Run脚本 错误依然存在。

思虑再三,决定找开发同学要源码看看 这一步操作调用哪个方法? 接收几个参数?参数的类型是什么?对接收的参数做什么判断处理?

为方便开发跟进,我把脚本里的服务地址改到开发同学的机器上 有ta打断点 步步跟进。

开发定位第一步:发现报出异常信息,数据库里某个pk字段违反了唯一性约束

我检查自己的脚本发现问题所在

C:\Users\Administrator\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\FGFS@3}101852[XF4RBP{8P.png

我在httpwatch里查找这些pk值只有保存原发灶的请求时才出现,个人臆测为可以传入为相同的随机32位字符串。

追着开发继续跟进 这些pk不是从服务器端返回的 是不是可以随机生成?可不可以不写?

开发定位第二步:最终确定这些pk值不传也是可以的,于是将该请求中这些pk字段值置为空,重新Run脚本。Bingo~~~! 搞定!

C:\Users\Administrator\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\9Z%{LM8U~)51Y_O1H05VG9B.png

也希望各位同学在性能测试脚本开发的过程中 把理解业务这个点作为重中之重!

4.4:运行时设置&调试

4.4.1:Run-Logic规则
C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\)LN5P8}6F7P9ZEYBBCF$[8H.png

Run区块中可指定整个Run逻辑的迭代次数,这里设置的迭代次数不会对Initial和End生效。

Block中可包含多个Action,并且可设置Action的执行概率。Block中可包含Block。

4.4.2:Pacing设置
C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\@I6[`}3X0KFJ2]YT]]8@(TE.png

Pacing:可设置迭代间的时间间隔。

4.4.3:输出日志
C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\U{W4I}D%F@)VJ)J`TJD8C9S.png

(PS:调试的时候可以勾选上Enable logging, 若需要查看服务端返回的信息 我们可以选择Extended log,用到的最多场景是勾选Parameter substitution)

切记 执行测试的时候把log关掉,否则硬盘里被塞满日志文件。

4.4.4:思考时间Think time
C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\[YDT`SMQAPTM74PA68MOQV4.png

(PS:思考时间用来模拟实际操作过程中的等待时间;如果只是想评估在一定环境配置下服务器能承受多大并发则可以忽略思考时间;另:思考时间一定不要放到事务内)

4.4.5:Browser Emulation浏览器模拟
C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\1]NTXWA2UD%GZ[JLNUJW{`G.png

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 选项在每次迭代过程中清除浏览器中缓存来达到模拟一个真实用户第一次访问网页,清除该复选框以禁用此选项,允许虚拟用户使用缓存来存储用户信息,来模拟一个已经访问过网页的用户。

4.4.6:过滤外网的下载请求
C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\60M6SDL~%QJN52TC)RAF9B8.png

(PS:在运行时设置中 切换到Download Filters页,选择Exclude address in list,添加外网URL即可不下载外网资源)

设置后再次运行脚本 日志中无报错信息,结果如下:

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\SFD6TLD(L4NTGUC]_ZX4N61.png
4.4.7:参数化及其取值规则

规则组合1Sequential + Each iteration + Continue with last value

Select Next Value

Update Value On

When Out Of Values

Sequential

Each iteration

Continue with last value

取值结果如下图 在一次迭代中取同一个值;在不同迭代中按列表顺序取值;当参数不足时,进行下一轮循环参数列表。

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\`{(01`}N8NE9%W386HHHJJW.png

规则组合2Sequential + Each occurence+ Continue with last value

Select Next Value

Update Value On

When Out Of Values

Sequential

Each occurence

Continue with last value

取值结果:在一次迭代中,参数每出现一次则按顺序取一个新的参数值;不同迭代中则按顺序取新的值;参数不足时则循环参数列表顺序取值。

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\3T]BU7TUQ9CN2%Y4WR4YW]1.png

规则组合3Sequential + Once + Continue with last value

Select Next Value

Update Value On

When Out Of Values

Sequential

Once

Continue with last value

取值结果:无论多少次迭代,无论多少个虚拟用户进行并发,只取一个值。

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\QH6S~9Y%3O8N2D9QY0H919O.png

规则组合4Random + Each iteration + Continue with last value

Select Next Value

Update Value On

When Out Of Values

Random

Each iteration

Continue with last value

取值结果:在一次迭代中,无论参数出现几次都根据随机取同一个值;不同迭代中则取新的随机值。

规则组合5Random + Each occurence + Continue with last value

Select Next Value

Update Value On

When Out Of Values

Random

Each occurence

Continue with last value

取值结果:在Actions中每次出现参数时都会随机取值。

规则组合6Random + Once+ Continue with last value

Select Next Value

Update Value On

When Out Of Values

Random

Once

Continue with last value

取值结果:在Actions中只会随机取一个值。

规则组合7Unique + 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

取值结果:每次迭代会取唯一的一个参数值;当参数不足时循环参数列表。

规则组合9Unique + Each iteration + abort Vuser

Select Next Value

Update Value On

When Out Of Values

Unique

Each iteration

abort Vuser

取值结果:每次迭代会取唯一的一个参数值;当参数不足时中止Vuser执行。

规则组合10Unique + Each occurence + Continue with last value

Select Next Value

Update Value On

When Out Of Values

Unique

Each occurence

Continue with last value

取值结果:每次参数出现会取唯一的一个参数值;当参数不足时日志中报错。

规则组合11Unique + 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

取值结果:每次参数出现会取唯一的一个参数值;当参数不足时循环参数列表。

规则组合12Unique + Each occurence + abort Vuser

Select Next Value

Update Value On

When Out Of Values

Unique

Each occurence

abort Vuser

取值结果:每次参数出现会取唯一的一个参数值;当参数不足时中止Vuser执行。

规则组合13Random + Once + Continue with last value

Select Next Value

Update Value On

When Out Of Values

Random

Once

Continue with last value

取值结果:每个Vuser只会取唯一的一个参数值。

万法归一:可以通过simulate parameters来查看取参的结果。

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\Z[L]JLMY[IP45)D7N4JOM9J.png
4.4.8:关联的使用场景

关联是指获取服务器端返回的动态数据,将客户端的数据与服务器端的数据建立联系。

什么时候使用关联:

当我们需要使用从服务器端返回的动态数据时,我们需要使用web_reg_save_param这个注册函数将服务器端返回的数据保存到参数中。

下表列出web_reg_save_param可用的属性。

代码语言:javascript
复制
NotFound
找不到边界并且生成了空字符串时的处理方法。默认值“ERROR”表示找不到边界时 LoadRunner 应发出错误消息。如果设置为“EMPTY”,则不会发出错误消息,并且脚本的执行将继续进行。注意,如果为脚本启用了“出现错误时仍继续”,则即使将 NOTFOUND 设置为“ERROR”,在找不到边界时脚本将仍然会继续执行,但会将错误消息写入扩展日志文件中
LB
 参数或动态数据的左边界。此参数必须为非空的、以 null结尾的字符串。边界参数区分大小写;要忽略大小写,请在边界之后添加“/IC”。如果在边界之后指定“/BIN”,则指定为二进制数据。
RB
 参数或动态数据的右边界。此参数必须为非空的、以 null结尾的字符串。其他规则呃LB一样。
RelFrameID
 与请求的 URL 相关的 HTML 页的层次结构级别。可能的值为 ALL 或数字。
Search
 搜索的范围 - 搜索已分隔的数据的位置。可能的值为Headers (仅搜索页眉)、Body (仅搜索正文数据,而不包括页眉)或 ALL (搜索正文和页眉)。默认值为 ALL
ORD
 此可选参数表示匹配的序号或出现的次数。默认序号为 1。
 如果指定“All”,则会将参数值保存在数组中,是将找到的动态变量保存到数组中。默认是ord=1.如果搜索到的字符是多个,并想将他保存在数组里,则ord=all;他们分别保存到pr_1 pr_2 .....。其中pr_count为内部函数,统计数组的个数。
SaveOffset
 找到的值的子字符串偏移量,将保存到参数。默认值为 0。
 偏移量值必须为非负数,偏移量。从搜索到的字符串中,取子串。默认saveoffset=0.
Savelen
 找到的值的子字符串的长度(在指定的偏移量中),将保存到参数。默认值为 &ntilde;1,表示直到字符串的末尾。
Convert
 要应用于数据的转换方法:HTML_TO_URL:将 HTML 编码数据转换为 URL 编码数据格式
 HTML_TO_TEXT:将 HTML 编码数据转换为纯文本格式。
      函数的一些使用技巧:
         1、web_reg_save_param必须在获取返回值的操作前面注册,在获取返回值的操作后面使用。
         2、保存参数最大不能超过256字节,如果超过256字节请使用int web_set_max_html_param_len (const char *length )函数扩大参数保存范围
         例如:web_set_max_html_param_len ("1024"); //扩大参数最大保存范围为1024字节
         3、LB和RB后面跟着"/ic",则边界大小写都匹配(不加,也就是默认是大小写敏感的)
         例如:web_reg_save_param("IsRight","LB/ic=cache-control: private\r\n\r\n","RB/ic=|",LAST);

五:测试执行

5.1搭建禅道环境

搭建windows下的禅道环境很简单,官网下载压缩包,解压后启动

即可。

(ps:禅道集成环境试用mysql数据库,并集成了phpmyadmin来通过浏览器对数据库进行方便的管理)

5.2 设置LR场景

5.2.1 手工场景
C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\CU3IH(6@J]5NGF4YAVTMFP8.png

手动场景设计可以通过建立组并指定脚本、负载生成器和每个组中包括的Vuser数来建立手动场景;

还可以通过百分比模式建立手动场景,使用此方法建立场景可以指定场景中将使用的Vuser的总数,并为每个脚本分配负载生成器和占总数一定百分比的Vuser。

C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\7C6STEQLY%JDJF71366}HYI.png

脚本执行模式:

代码语言:javascript
复制
Real-world schedule:按照场景设计进行加压,可实现阶梯加压和峰值压测。
(PS:在real-world模式中,Duration的优先级高于run-time setting的设置)
Basic schedule:按照脚本设计的迭代方式执行,只能进行峰值压测。
Schedule by:
Scenario(场景):多个脚本组合场景的时候根据设计的场景运行方式,所有脚本按统一方式执行。
Group(组):多个脚本组合场景的时候,单独设计每个脚本的执行方式。

(PS:我们常用的场景设置对应我们的性能测试需求时如下:

1) 基准测试: 

采用一个虚拟用户迭代执行N次,取测试结果的平均值作为基准参考值。 

2) 单交易负载 

采用多个用户并发的方式对单交易进行负载测试,分析性能指标是否满足需求。 使用峰值加压,可以测试特定峰值下系统性能情况。 使用梯队加压,可以测试系统的最大并发用户数和最佳并发用户数。 

建议:在单交易负载测试时,采用峰值加压和梯度加压相结合的方式进行测试。 

3) 综合场景负载 

模拟真实交易场景,分析正常业务交易量,选取日交易量最多的几个交易,合理分配并发用户数,持续运行一段时间,分析测试结果是否满足需求。

 4) 稳定性测试 

根据需求选取几个常用交易,按照峰值压力的百分比进行加压,稳定运行一段时间,查看系统运行是否正常,系统资源利用率是否在最佳状态。

)

5.2.2 面向目标的场景
C:\Users\IP-206\AppData\Roaming\Tencent\Users\834321678\QQ\WinTemp\RichOle\LH[NCVB~%SCM[()I`[YH(AY.png

在面向目标的场景中,可以定义要实现的测试目标,LoadRunner会根据这些目标自动构建场景。

可以在一个面向目标的场景中定义希望场景达到的下列5种类型的目标:虚拟用户数、每秒单击次数(仅Web Vuser)、每秒事务数、每分钟页面数(仅Web Vuser)或事务响应时间。可以通过单击【Edit Scenario Goal...】设置,如目标类型、最小用户数、最大用户数、运行时间等。

(PS:当我们的性能测试需求非常明确的时候,我们可以采用面向目标的模式来验证系统服务的级别;也可来验证我们的优化结果是否满足要求)

5.3 多PC联机负载

(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选择压力机。

5.4 服务器监控

5.4.1:监控数据库

(PS:极力推荐spotlight系列的工具,高端专业监控Oracle/MySQL数据库。

当然我们也可以自己写shell或者python脚本来监控数据库)。

我们一起来看下Oracle数据库的常用性能监控指标:

--监控事例的等待--

代码语言:javascript
复制
select event,
sum(decode(wait_Time, 0, 0, 1)) "Prev",
sum(decode(wait_Time, 0, 1, 0)) "Curr",
count(*) "Tot"
from v$session_Wait
group by event
order by 4;

--回滚段的争用情况--

代码语言:javascript
复制
select name, waits, gets, waits / gets "Ratio"
from v$rollstat a, v$rollname b
where a.usn = b.usn;

--监控表空间的 I/O 比例--

代码语言:javascript
复制
select df.tablespace_name name,
df.file_name "file",
f.phyrds pyr,
f.phyblkrd pbr,
f.phywrts pyw,
f.phyblkwrt pbw
from v$filestat f, dba_data_files df
where f.file# = df.file_id
order by df.tablespace_name;

--监控文件系统的 I/O 比例--

代码语言:javascript
复制
select substr(a.file#, 1, 2) "#",
substr(a.name, 1, 30) "Name",
a.status,
a.bytes,
b.phyrds,
b.phywrts
from v$datafile a, v$filestat b
where a.file# = b.file#;

--在某个用户下找所有的索引--

代码语言:javascript
复制
select user_indexes.table_name,
user_indexes.index_name,
uniqueness,
column_name
from user_ind_columns, user_indexes
where user_ind_columns.index_name = user_indexes.index_name
and user_ind_columns.table_name = user_indexes.table_name
order by user_indexes.table_type,
user_indexes.table_name,
user_indexes.index_name,
column_position;

--监控 SGA 的命中率--

代码语言:javascript
复制
select a.value + b.value "logical_reads",
c.value "phys_reads",
round(100 * ((a.value + b.value) - c.value) / (a.value + b.value)) "BUFFER HIT RATIO"
from v$sysstat a, v$sysstat b, v$sysstat c
where a.statistic# = 38
and b.statistic# = 39
and c.statistic# = 40;

--监控 SGA 中字典缓冲区的命中率--

代码语言:javascript
复制
select parameter,
gets,
Getmisses,
getmisses / (gets + getmisses) * 100 "miss ratio",
(1 - (sum(getmisses) / (sum(gets) + sum(getmisses)))) * 100 "Hit ratio"
from v$rowcache
where gets + getmisses <> 0
group by parameter, gets, getmisses;

--监控 SGA 中共享缓存区的命中率,应该小于1%--

代码语言:javascript
复制
select sum(pins) "Total Pins",
sum(reloads) "Total Reloads",
sum(reloads) / sum(pins) * 100 libcache
from v$librarycache;
select sum(pinhits - reloads) / sum(pins) "hit radio",
sum(reloads) / sum(pins) "reload percent"
from v$librarycache;

--监控SGA中重做日志缓存区的命中率,应该小于1%--

代码语言:javascript
复制
SELECT name,
gets,
misses,
immediate_gets,
immediate_misses,
Decode(gets, 0, 0, misses / gets * 100) ratio1,
Decode(immediate_gets + immediate_misses,
0,
0,
immediate_misses / (immediate_gets + immediate_misses) * 100) ratio2
FROM v$latch
WHERE name IN ('redo allocation', 'redo copy');

--监控内存和硬盘的排序比率 增加 sort_area_size--

代码语言:javascript
复制
SELECT name, value
FROM v$sysstat
WHERE name IN ('sorts (memory)', 'sorts (disk)');

--监控当前数据库谁在运行什么SQL语句--

代码语言:javascript
复制
SELECT osuser, username, sql_text
from v$session a, v$sqltext b
where a.sql_address = b.address
order by address, piece;

--监控字典缓冲区--

代码语言:javascript
复制
SELECT (SUM(PINS - RELOADS)) / SUM(PINS) "LIB CACHE" FROM V$LIBRARYCACHE;
SELECT (SUM(GETS - GETMISSES - USAGE - FIXED)) / SUM(GETS) "ROW CACHE" FROM V$ROWCACHE;
SELECT SUM(PINS) "EXECUTIONS", SUM(RELOADS) "CACHE MISSES WHILE EXECUTING" FROM V$LIBRARYCACHE;

----此值大于0.5时,参数需加大--

代码语言:javascript
复制
select sum(wait)/sum(totalq) "dispatcher waits" from v$queue where type='dispatcher';
select count(*) from v$dispatcher;
select servers_highwater from v$mts;

servers_highwater接近mts_max_servers时,参数需加大

---碎片程度--

代码语言:javascript
复制
select tablespace_name, count(tablespace_name)
from dba_free_space
group by tablespace_name
having count(tablespace_name) > 10;
alter tablespace name coalesce;
alter table name deallocate unused;
create or replace view ts_blocks_v as
select tablespace_name,block_id,bytes,blocks,'free space' segment_name from dba_free_space
union all
select tablespace_name,block_id,bytes,blocks,segment_name from dba_extents;
select * from ts_blocks_v;
select tablespace_name, sum(bytes), max(bytes), count(block_id)
from dba_free_space
group by tablespace_name;

--查看碎片程度高的表--

代码语言:javascript
复制
SELECT segment_name table_name, COUNT(*) extents
FROM dba_segments
WHERE owner NOT IN ('SYS', 'SYSTEM')
GROUP BY segment_name
HAVING COUNT(*) = (SELECT MAX(COUNT(*))
FROM dba_segments
GROUP BY segment_name);

---表、索引的存储情况检查--

代码语言:javascript
复制
select segment_name, sum(bytes) space, count(*) ext_quan
from dba_extents
where tablespace_name = '&tablespace_name'
and segment_type = 'TABLE'
group by tablespace_name, segment_name;
select segment_name, count(*)
from dba_extents
where segment_type = 'INDEX'
and owner = '&owner'
group by segment_name;

--检测数据库中的事件和等待--

代码语言:javascript
复制
SELECT event, total_waits, total_timeouts,time_waited, average_wait FROM v$system_event

--查询会话中的事件和等待时间--

代码语言:javascript
复制
select sid, event, total_waits,average_wait from v$session_event where sid=10;

--查询等待进程--

代码语言:javascript
复制
SELECT sid, seq#, event, wait_time, state FROM v$session_wait;

---当前 sql 语句--

代码语言:javascript
复制
select sql_text, users_executing, executions, loads from v$sqlarea;

--查询高速缓存中的命中率--

代码语言:javascript
复制
select sum(pins) "Executions",
sum(reloads) "Cache Misses",
sum(reloads) / sum(pins)
from v$librarycache;
-- cpu used by this session --
select a.sid,
spid,
status,
substr(a.program, 1, 40) prog,
a.terminal,
oSUSEr,
value / 60 / 100 value
from v$session a, v$process b, v$sesstat c
where c.statistic# = 12
and c.sid = a.sid
and a.paddr = b.addr
order by value desc;

--查看表锁--

代码语言:javascript
复制
select * from sys.v_$sqlarea where disk_reads>100

--监控事例的等待--

代码语言:javascript
复制
select event,
sum(decode(wait_Time, 0, 0, 1)) "Prev",
sum(decode(wait_Time, 0, 1, 0)) "Curr",
count(*) "Tot"
from v$session_Wait
group by event
order by 4

--查看前台正在发出的SQL语句--

代码语言:javascript
复制
select user_name, sql_text   
from v$open_cursor   
where sid in (select sid
from (select sid, serial#, username, program   
from v$session   
where status = 'ACTIVE'))

--数据表占用空间大小情况--

代码语言:javascript
复制
select segment_name, tablespace_name, bytes, blocks
from user_segments
where segment_type = 'TABLE'
ORDER BY bytes DESC, blocks DESC

--查看表空间碎片大小--

代码语言:javascript
复制
select tablespace_name,
round(sqrt(max(blocks) / sum(blocks)) *
(100 / sqrt(sqrt(count(blocks)))),
2) FSFI
from dba_free_space
group by tablespace_name
order by 1

--查看表空间占用磁盘情况--

代码语言:javascript
复制
select b.file_id 文件ID号,
b.tablespace_name 表空间名,
b.bytes 字节数,
(b.bytes - sum(nvl(a.bytes, 0))) 已使用,
sum(nvl(a.bytes, 0)) 剩余空间,
sum(nvl(a.bytes, 0)) / (b.bytes) * 100 剩余百分比
from dba_free_space a, dba_data_files b
where a.file_id = b.file_id
group by b.tablespace_name, b.file_id, b.bytes
order by b.file_id

--查看session使用回滚段--

代码语言:javascript
复制
SELECT r.name 回滚段名,
s.sid,
s.serial#,
s.username 用户名,
t.status,
t.cr_get,
t.phy_io,
t.used_ublk,
t.noundo,
substr(s.program, 1, 78) 操作程序
FROM sys.v_$session s,sys.v_$transaction t,sys.v_$rollname r
WHERE t.addr = s.taddr and t.xidusn = r.usn
ORDER BY t.cr_get,t.phy_io

--查看SGA区剩余可用内存--

代码语言:javascript
复制
select name,
      sgasize/1024/1024        "Allocated(M)",
      bytes/1024            "**空间(K)",
      round(bytes/sgasize*100, 2)   "**空间百分比(%)"
   from (select sum(bytes) sgasize from sys.v_$sgastat) s, sys.v_$sgastat f
   where f.name = 'free memory'

---非系统用户建在SYSTEM表空间中的表--

代码语言:javascript
复制
SELECT owner,table_name
FROM DBA_TABLES
WHERE tablespace_name in('SYSTEM','USER_DATA') AND
owner NOT IN('SYSTEM','SYS','OUTLN', 'ORDSYS','MDSYS','SCOTT', 'HOSTEAC')

---性能最差的SQL--

代码语言:javascript
复制
SELECT * FROM ( SELECT PARSING_USER_ID EXECUTIONS,SORTS,COMMAND_TYPE,DISK_READS,sql_text
FROM v$sqlarea
ORDER BY disk_reads DESC)
WHERE ROWNUM<100

---读磁盘数超100次的sql--

代码语言:javascript
复制
select * from sys.v_$sqlarea where disk_reads>100

---最频繁执行的sql--

代码语言:javascript
复制
select * from sys.v_$sqlarea where executions>100

---查询使用CPU多的用户session--

代码语言:javascript
复制
select a.sid,spid,status,substr(a.program,1,40) prog,a.terminal,osuser,value/60/100 value
from v$session a,v$process b,v$sesstat c
where c.statistic#=12 and
c.sid=a.sid and
a.paddr=b.addr
order by value desc

5.4.2:监控OS

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

专为此进程分配,无法与其他进程共享的当前字节数

5.4.3:监控中间件
5.4.3.1:监控tomcat

使用LoadRunner自带的lr_user_data_point监控tomcat

1、  配置Tomcat登录用户,找到tomcat安装目录下的/conf/ tomcat-users.xml,添加配置如下:

代码语言:javascript
复制
<tomcat-users>
<role rolename="manager-gui"/>
<user username="用户名" password="密码" roles="manager-gui"/>
</tomcat-users>

(配置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脚本代码如下:

代码语言:javascript
复制
lr_start_transaction("");
lr_end_transaction();
 web_reg_save_param("JVMFreeMemory",
              "LB=Free memory: ",
              "RB= MB",
              "Ord=1",
              LAST);
       web_reg_save_param("JVMTotalMemory",
              "LB=Total memory: ",
              "RB= MB",
              "Ord=1",
              LAST);
       web_reg_save_param("JVMMaxMemory",
              "LB=Max memory: ",
              "RB= MB",
              "Ord=1",
              LAST);
web_url("status",
              "URL=http://{ServerName}/manager/status",
              "Resource=0",
              "RecContentType=text/html",
              "Referer=",
              "Snapshot=t1.inf",
              "Mode=HTTP",
              LAST);
lr_user_data_point("Tomcat JVM Free memory", atof(lr_eval_string("{JVMFreeMemory}")));
       lr_user_data_point("Tomcat JVM Total memory", atof(lr_eval_string("{JVMTotalMemory}")));
       lr_user_data_point("Tomcat JVM Max memory", atof(lr_eval_string("{JVMMaxMemory}")));

(PS: 需要注意的是,我们有两种方法来使用lr_user_data_point,1:将自定义时间和我们的业务请求放到一个Action中;2:将自定义时间单独放在一个Action中,并将这个action放到业务处理的Action之后)

5.4.3.2:监控Nginx

使用 ngxtop 监控 Nginx

ngxtop 默认会从其配置文件 (/etc/nginx/nginx.conf) 中查找 Nginx 日志的地址。所以,监控 Nginx ,运行以下命令即可:

1

$ ngxtop

这将会列出10个 Nginx 服务,按请求数量排序。

显示前20个最频繁的请求:

1

$ ngxtop -n 20

http://img.linux.net.cn/data/attachment/album/201406/16/133347yossqqn79yj4xdzr.jpg

获取Nginx基本信息:

1

$ ngxtop info

http://img.linux.net.cn/data/attachment/album/201406/16/133349me6x7pa71tg7chy3.jpg

你可以自定义显示的变量,简单列出需要显示的变量。使用 “print” 命令显示自定义请求。

1

$ ngxtop print request http_user_agent remote_addr

http://img.linux.net.cn/data/attachment/album/201406/16/133352ht00nx7fnz07fvq0.jpg

显示请求最多的客户端IP地址

1

$ ngxtop top remote_addr

http://img.linux.net.cn/data/attachment/album/201406/16/133354fypepynimipnnyyv.jpg

显示状态码是404的请求

1

$ ngxtop -i 'status == 404' print request status

http://img.linux.net.cn/data/attachment/album/201406/16/133357orc6q1voa44v65vg.jpg

除了Nginx,ngtop 还可以处理其他的日志文件,比如 Apache 的访问文件。使用以下命令监控 Apache 服务器

1

$ tail -f /var/log/apache2/access.log | ngxtop -f common

5.4.4 监控网络

个人推荐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的使用啥的 这儿不多说了 谁用谁知道~~

我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表