LoadRunner
LoadRunner contains the following components
- The Virtual User Generator or VuGen records end-user business processes and creates an automated performance tseting script,known as a Vuser script.虚拟用户生成器或VuGen记录最终用户的业务流程并创建一个自动性能测试脚本,称为Vuser脚本
- The Controller organizes,driveres,manages,and monitors the load test.controller(控制器)组织、驱动、管理和监视负载测试
- Analysis helps you view,dissect,and compare the results of the load tests.Analysis(分析器)可以帮助您查看、分析和比较负载测试的结果
- Load Generators,computers that run Vusers to generate a load on the system.loadGenerators(负载生成器)运行vuser向服务器发起负载测试的计算机
特定
-
广分支持业界标准协议
-
支持多种平台开发脚本
-
创建真实的系统负载
VuGen创建脚本时变量参数化,实现并发用户不同行为,然后再在controller中借助集合点实现真正意义上的并发测试
-
强大的实时监控与数据采集功能
实时监控场景状态,掌握测试进度,及时发现问题
-
精确分析结果,辅助定位问题
-
采用虚拟用户生成器进行脚本录制、脚本编写、脚本调试,完成脚本二的准备工作
-
铜鼓共脚本组合设计压测场景
-
调度压力机进行场景分布式压测
-
性能结果分析、图表分析
linux装压力机
不是全国职业院校职业大赛的考题范围,没学
脚本录制
能够将可视化操作转换为脚本代码,并且能够进行重复性的操作
简化操作,大大降低工作量和难度
-
直接录制
-
本地代理录制
-
离线流量录制
-
手动代理录制
-
第三方代理
fiddler进行辅助生成脚本
录制方式
直接录制
本地代理录制
Recording Options-Advanced
打开Use the LoadRunner Proxy to record a local application
离线流量录制
浏览器F12 以HAR格式保存所有内容
远程代理录制
第三方录制
同离线流量,只不过改成用fiddler录制了
录制优化
脚本结构认识
Actions:脚本区
Extra Files:头文件区
Runtime Setting:运行时设置
Parametes:参数设计
Recording Report:录制报告
Replay Summary:回放总结
录制优化
-
基于html和基于url脚本的区别
html录制的脚本比较简单(资源型请求放在开头,压测去掉就行)
url将各种请求拆开了
-
http头过滤,内容类型过滤,资源/非资源类型的定义
回放优化
log:日志(Extended log-Data returned by server)更详细的日志
think time:思考时间
lr_think_time(10); //启动回放时间时,在回放时在代码处会暂停10s
preferences-General
小结
View-Layouts-Reset to defaults 恢复默认布局
-
录制前设置好编码,以防中文乱码
- Recording Options—HTTP Properties—Advanced
- Runtime Settings—Preference—General
-
脚本录制
脚本录制有多种方法,看个人喜好,倾向于本地代理和流线流量录制。录制过程中要对脚本进行基本的过来和处理,提出一些无用静态请求和业务上认为无关的请求,让脚本最终处于最简洁的状态,降低后续的难度
-
结构优化
正对录制好的脚本,我们要进行结构整理,设计好初始脚本,迭代脚本,退出脚本三个最基本的环节,针对复杂的脚本,我们要将迭代部分进行逻辑分拆,让其尽可能简化
-
脚本回放
录制好的脚本要记得进行回放,并学会回放日志分析业务,进一步确认我们的脚本逻辑
脚本编写
脚本参数化
在各种业务场景中,我将多个常量用统一变量进行替代的过程称之为脚本参数化
- 脚本参数化加强了代码的可读性,非常方便看出关联
- 脚本参数化降低了测试代码的维护难度,非常方便我们快速修改,减少低级错的发生
- 比如主机,端口,查询条件,以及其它的业务数据的可根据需要进行参数化
内置参数
在LoadRunner中,内置了多种不同类型的参数,合理的采用会降低脚本的难度和提升脚本设计效率
-
LoadGN
全称为LoadGeneratorName,该参数是以压力机名称为基础,可按不同格式生成
//定义一个LoadGeneratorName参数 char* NewParam=lr_eval_string("{NewParam}");//把参数转换为字符串并赋值给NewParam变量 lr_log_message("参数1,LoadGeneratorName:%s",NewParam);
[
-
UserID
该参数是以虚拟用户的序号为基础,可按不同格式生成
//定义一个Vuser ID参数 lr_log_message("参数,Vuser ID:%s",lr_eval_string("{NewParm}"));
-
table参数
以白哦个中的行数据为基础生成的参数,支持记事本编辑以及从csv等文案金中导入,每一行各列之间的数据可以设置分隔符
表格取数的6种组合规则
- 顺序:按照参数化的顺序一个一个的来取(Sequential)
- 随机:参数化中的数据,每次随机的从中选取数据(Random)
- 唯一:为每个虚拟用户分配一个唯一的一条数据(Unique)
更新值的规则
- 每一次迭代时更新(Each iteration)
- 运行场景中只更新一次(once)
情况组合 顺序-迭代更新 a1-a2-a3-a1-a2 顺序-仅更新一次 a1-a1-a1-a1-a1 随机-迭代更新 每次都随机选择 随机-仅跟新一次 仅随机选一次(值相同) 唯一-迭代更新 首行 唯一-仅跟新一次 随机选择一次 //定义一个表参数 lr_Log_message("参数3,表参数:%s",lr_eval_string("NewParam"));
[
-
file参数
文件与表格的区别在于
表格可以取整行数据,文件一般只取一行中的一列
file参数可以有多列,每次取一列
取下一行数据方法有三种
- 顺序:按照参数化的顺序一个一个的来取
- 随机:参数化中的数据,每次随机的从中选取数据
- 唯一:为每个虚拟用户分配一个唯一的一条数据
更新值得规则
- 每一次迭代时更新
- 每一次出现时跟新
- 运行场景中更新一次
//定义一个file参数 lr_log_message("参数6,file参数:%s",lr_eval_string("{param6}"));
-
迭代编号
该参数是以运行逻辑得替代次数为基础,可按不同格式生成
//定义一个迭代编号参数 char* NewParam=lr_eval_string("{NewParam}");
%07d就是编号前面补些0直至凑齐7位数字
-
日期时间
能按照时间生成不同格式字符串
更新值得规则
- 每一次迭代时更新
- 每一次出现时更新
- 运行场景中更新一次
//定义一个日期时间参数 lr_log_message("%s",lr_eval_string("{NewParam}"));
-
随机数字
-
唯一编号
能在范围内随机生成唯一数字
更新值得规则
- 每一次迭代时更新
- 每一次出现时更新
- 运行场景中更新一次
//定义一个唯一编号参数 lr_log_message("%s",lr_eval_string("{NewParam}"));
-
xml参数
以xml结构为基础的参数
取下一行数据方法有三种
- 顺序:按照参数化的顺序一个一个的来取
- 随机:参数化中的数据,每次随机的从中选取数据
- 唯一:为每个虚拟用户分配一个唯一的一条数据
更新值得规则:
- 每一次迭代时更新
- 每一次出现时更新
- 运行场景中更新一次
//定义一个xml参数 lr_log_message("%s",lr_eval_string("{param}"))
-
自定义参数
规则关联
一般在业务系统中,我们再发起请求时,有可能用到前面步骤的返回结果:lr关联时指把服务器返回数据(部分数据)以参数来表示,同时为后续请求的一个变量这样一个过程,规则关联分为手动关联和自动关联
手动关联
- 找到前置请求中返回的业务数据
- 将上述数据设置为参数
- 将后续条件采用参数化替代
//从服务端返回中取认证数据
web_reg_save_param("devToken","LB=\"devt_token\":\","RB=\"",LAST);
lr_log_message("取得结果为:%s",lr_eval_string("{devToken}"));
//将认证数据放置头部信息中
web_add_auto_header("Authorization","Bearer{devToken}");
参数名称devToken
左边界:”devToken”:“
有边界:“
没找到时:报错(ERROR)
在body里找
web_reg_save_param("devToken",
"LB=\"devToken\":\"",
"RB=\"",
"NotFound=ERROR",
"Search=Body",
LAST);
//请求函数
自动关联
由loadrunner内置规则在录制时会自动生成关联,比如JSESSION,COOKIE自动关联
另外所有的自动关联均可以用手动关联替代,因此手动关联我们的学习重点
事务定义
事务(Transaction)用于模拟用户的一个相对完整的有意义的业务操作过程,例如人登录、查询、转账,这些都可以作为事务,而一般不会把每次HTTP请求作为一个事务
//事务开始
lr_start_transaction("devt-query");
//事务结束
lr_end_transaction("devt-query",LR_AUTO);
//事务成功结束
lr_end_transaction("devt-query",LR_PASS);
//事务失败结束
lr_end_transaction("devt-query",LR_FAIL);
//实例
//不检查结果,自动结束
lr_end_transaction("devt-query",LR_AUTO);
//判断是否成功
if(atoi(lr_eval_string("{paraml}"))>0){
lr_end_transaction("devt-query",LR_PASS);
}else{
lr_end_transaction("devt-query",LR_FAIL);
}
web_reg_save_param("rspCode",
"LB=\"rspCode\":",
"RB=,",
"Search=Body",
LAST
);
web_reg_save_param("treeName","LB=\"treeName\":\"","RB=\"","Search=Body",LAST);
//此处省去请求
//请求的response带有"rspCode":1,"rspDesc":"success"
if(atoi(lr_eval_string("{rspCode}"))>0){
//atoi(),字符串转整数
lr_end_transaction("longge_query",LR_PASS);
}else{
lr_end_transaction("longge_query",LR_FAIL);
}
if(strcmp(lr_eval_string("{treeName}"),"{treeName}")==0){
//strcmp(),比较参数的值,一样的话{treeName}就不为空
lr_end_transaction("longge-query",LR_FAIL);
}else{
lr_end_transaction("longger-query",LR_PASS);
}
结果检查
利用结果检查函数,我们可以判断业务是否正确,类似于断言
//找一个字段welcome
web_reg_find("Text=Welcome",
LAST);
web_reg_find("Fail=NotFound",
"Search=Body",
"SaveCount=Token_Count",
"TextPfx=\"devt_token\":\"",
"TextSfx=\"",
LAST);
小结
- 脚本参数化,规则关联,事务,结果检查时脚本二编写的几个核心场景
- 文件类、日期类、唯一编号、自定义参数在脚本参数化中使用非常多,比如
- 在订单场景入库的时候,我们可以采用唯一编号,生成一些数据序列
- 主机,端口等,我们可以采用自定义参数管理
- 通常我们的脚本在录完之后,第一次有可能报错,这是由于一些关联没有做好,我们可以打开日志
- 找到请求中返回的业务数据
- 然后再利用脚本参数化进行处理
- 对于初学者,我们一定要多谢,多练,针对一些常用函数一定要熟悉
常用函数
注册函数和检查函数需要放于动作函数前面
调用结果需要在动作类后面
参数类函数
lr_eval_string
//将参数转化为字符串
lr_save_int(0,"searchCount");
lr_save_string("字符","str1");
if(atoi(lr_eavl_string("{searchCount}"))==0){
lr_output_message("Is zero,%s",lr_eval_string("{searchCount}"));
}
lr_save_int(47,"searchCount");
if(atoi(lr_eval_string("{searchCount}"))!=0){
lr_output_message("Not zero,%s",lr_eval_string("{searchCount}"));
}
lr_eval_json
//将字符串或文件转换为Json对象
char* json_input="{""\"firstName\":\"John\",""\"lastName\":\"smith\"""}";
lr_save_string(json_input,"JSON_Input_Param");
//创建json对象
lr_eval_json("Buffer={JSON_Input_Param}","JsonObjecct=json_obj_1",LAST);
//修改json对象值
lr_json_set_values("JsonObject=json_obj_1","Value=test1111","QueryString=$.firestName","SelectAll=Yes",LAST);
//取得json对象值
lr_json_get_values("JsonObject=json_obj_1","ValueParam=test1_result","QueryString=$.firstName",LAST);
//json传字符串
lr_json_stringify("JsonObject=json_obj_1","Format=compact","OutputPatam=Rsult",LAST);
//打印结果
lr_log_message("%s--%s",lr_eval_string("{test1_result}"),lr_eval_string("{Result}"));
return 0;
lr_json_set_values
//修改Json对象中的值
lr_json_get_values
//获取对象中的值
lr_json_stringify
//Json对象转字符串
lr_save_string
//将变量以字符串类型存入参数
char strl[100] = "我是测试字符串";
lr_save_string(strl,"Paraml");
lr_log_message("%s",lr_eval_string("{Paraml}"));
lr_save_int
//将变量以数字类型存入参数
int n=1314;
lr_save_int(n,"paraml");
lr_log_message("%s",lr_eval_string("{paraml}"));
lr_save_datetime
//时间格式化,精确到秒,存入参数
lr_save_datetime("%Y-%m-%d %H:%M:%S",TIME_NOW,"currDateTime");
lr_output_message(lr_eval_string("{currDateTime}"));
lr_save_timestamp
//将时间戳存入到参数,可精确到秒,毫秒,微秒
//以秒为单位
lr_save_timestamp("param","DIGITS=10",LAST);
lr_output_message(lr_eval_string("{param}"));
//以毫秒为单位
lr_save_timestamp("param",LAST);
lr_output_message(lr_eval_string("{param}"));
//以微秒为单位
lr_save_timestamp("param","DIGITS=16",LAST);
lr_output_message(lr_eval_string("{param}"));
web_save_timesamp
//将时间戳存入到参数,固定为毫秒
web_save_timestatmp_param("tStamp",LAST);//默认以毫秒为单位,将时间戳存入参数
lr_output_message(lr_eval_string("Timestamp:{tStamp}"));
lr_param_sprintf
//格式化字符串,类似于sprintf
int index = 56;
char * suffix = "txt";
//底层采用C的sprintf函数
lr_param_sprintf("LOG_NAME_PARAM","log_%d.%s",index,suffix,100);
lr_output_messsage("The new file name is %s",lr_eval_string("{LOG_NAME_PARAM}"));
lr_convert_string_encoding
//编码转换,可防止中文乱码
lr_convert_string_encoding("中文",LR_ENC_SYSTEM_LOCALE,LR_ENC_UTF8,"RelyMessage");
lr_log_message("%s","{ReplyMessage}");
检查类函数
web_reg_find
//搜索指定字符串
web_reg_find("Search=Body","Text=devt_token",LAST);
//搜索指定字符串(开始+结束)
web_reg_find("Search=Body","TextPfx\"devt_token\":\"","TextSfx=\"");
//统配搜索
web_reg_find("Text/ALNUMLC=\"devt_token\"",LAST);
web_reg_save_param
//将动态数据注册到参数
//从服务端返回中取得数据,支持双右边界
web_reg_save_param("devtToken","LB=\"devt_token\":\"","RB=\"",LAST);
web_reg_save_param_regexp
//将动态数据注册到参数,比web_reg_save_param功能强大
//从服务端返回中取得数据
web_reg_save_param_regexp("ParamName=devtToken","RegExp=(\"RegExp=(\"devt_token\":\".*?\")","Group=0",SEARCH_FILTERS,"Scope=BODY",LAST);
web_reg_save_param_json
//以json方式解析动态1数据并注册到参数(要求数据为json)
//从服务端返回中取得数据
web_reg_save_param_json("ParamName=devtToken","QueryString=$..devt_token","SEARCH_FILTERS","Scope=BODY",LAST);
web_reg_save_param_xpath
web_reg_save_param_xpath(
"ParamName=CorrelationParameter",
"QueryString=/LR_EXTENSION[1]/object[1]/object[1]/array[1]","DFEs=JsonXml","ReturnXML=Yes",SEARCH_FILTERS,"SCope=Body",LAST
);
web_reg_save_param_attib
//以html文档方式解析数据并注册到参数
//<INPUT TYPE="HIDDEN" NAME="fieldl" VAULE="测试数据">
web_reg_save_param_attrib("ParamName=paraml","TagName=input","Extract=value","Name=fieldl","Type=*",SEARCH_FILTERS,"IgnoreRedirection=No",LAST);
//把input标签中name=fieldl的value值赋给paraml
日志类函数
lr_output_message
//向日志文件、输出窗口和其他测试报告摘要发送消息
lr_output_message("取得token为:%s",lr_eval_string("{devtToken}"));
lr_log_message
//向日志文件发送消息
lr_output_message("取得token为:%s",lr_eval_string("{devtToken}"));
lr_debug_message
//向日志文件发送调试消息(需要设置日志级别)
lr_debug_message(LR_MSG_CLASS_RESULT_DATA|LR_MSG_CLASS_PARAMETERS,lr_eval_string("1111{devtToken}"));
lr_error_message
//向日志文件发送错误消息
lr_error_message("取得token为:%s",lr_eval_string("{devtToken}"));
常用C函数
sprintf
//格式化字符串
//类似于lr_param_sprintf
char* param1="I am param1!";
char* param2="I am Param2!";
int num1=123;
char dest[100];
sprintf(dest,"%s %s %d",param1,param2,num1);
lr_log_message(dest);
strcat
//连接两个字符串
char fullpath[1024];
char * filename = "logfile.txt";
strcat(fullpath,"c:\\tmp");
strcat(fullpath,"\\");
strcat(fullpath,"filename");
lr_output_message("当前文件路径为%s",fullpath);
strchr
//查找第一次出现的字符
char* string = "His Excellency the Duke of Exeter";
char* first_x,* last_x;
first_x = (char *)strchr(string,'x');
lr_output_message("第一次出现的字符x:%s",first_x);
last_x = (char *)strrchr(string,'x');
lr_output_message("最后一次出现的字符x:%s",last_x);
strcmp
//比较两字符串的字母顺序,可用于判断是否相等
int result;
char tmp[20];
char string1[] = "The quick brown dog jumps over the lazy fox";
char string2[] = "The quick brown dog jumps over the lazy fox";
result = strcmp(string1,string2);
if(result > 0)
strcpy(tmp,"greater than");
else if(result < 0)
strcpy(tmp,"less than");
else
strcpy(tmp,"equal to");
lr_output_message("strcmp:String 1 is %s string2",tmp);
strcpy
//将一个字符串复制到另一个字符串
char fullpath[1024],* filename = "logfile.txt";
strcpy(fullpath,"c:\\tmp");
lr_output_message("fullpath after strcpy:%s",fullpath);
stricmp
//忽略大小写,比较两字符串的字母顺序,可用于判断是否相等
int result;
char tmp[20];
char string1[] = "The quick brown dog jumps over the lazy fox";
char string2[] = "The QUICK brown dog jumps over the lazy fox";
result = stricmp(string1,string2);
if(result > 0)
strcpy(tmp,"greater than");
else if(result < 0)
strcpy(tmp,"less than");
else
strcpy(tmp,"equal to");
lr_output_message("strcmp:String 1 is %s string2",tmp);
strlen
//返回字符串的长度
int is_digit,i=0;
char * str = "213431241";
unsigned int len = strlen(str);
do{
if(i == len){
lr_output_message("No letters found in string");
return -1;
}
if_digit = isdigit(str[i++]);
}
while(is_digit);
lr_output_message("The first letter appears in character %d of string",i);
strncat
//把src所指向的字符串追加到dest所指向的字符串的结尾,直到n字符长度为止
char src[50], dest[50];
strcpy(src, "This is source");
strcpy(src, "This is destination");
strncat(dest,src,5);
lr_log_message("最终的目标字符串:%s",dest);
strnicmp
//对n个字符串执行不区分大小写的比较
int result;
char tmp[20];
char string1[] = "The quick brown dog jumps over the lazy fox";
char string2[] = "The QUICK brown dog jumps over the lazy fox";
result = strnicmp(string1,string2);
if(result > 0)
strcpy(tmp,"greater than");
else if(result < 0)
strcpy(tmp,"less than");
else
strcpy(tmp,"equal to");
lr_output_message("strcmp:String 1 is %s string2",tmp);
行动类函数
//一般录制所得,能看懂就行
lr语言编程
变量使用
LoadRunner中的变量其实就是C语言的变量,不过有以下约束
- 变量可以定义在.h文件中
- 变量可以定义在Action函数外部
- 变量可以定义在Action开始部分
- Action体内不可用定义变量
int a1 = 0;
int a2 = 0;
char* p1;
char str2[] = "string1123412";
Action()
{
lr_log_message("%s",&str2[5]);
p1 = lr_eval_string("{param1}");
lr_log_message("%s",p1);
//int a3=0;//此处编译会报错
return 0;
}
参数操作
参数是一个loadrunner中的概念
参数可以认为是C语言中的一个全局变量,我们书写脚本过程中,要学会用参数化的思想取解决问题
-
字符串数类的操作
char str1[] = "str11"; //参数的取值 lr_log_message("%s",lr_eval_string("{param1}")); lr_log_message("%s",lr_eval_string("{param2}")); //参数的赋值 lr_save_string(str1,"param1"); lr_save_string(str2,"param2"); //参数的取值 lr_log_message("%s",lr_eval_string("{param1}")); lr_log_message("%s",lr_eval_string("{param2}"));
-
int类型的操作
//参数的取值 lr_log_message("%s",lr_eval_string("{param1}")); lr
没
学
完
,
有
时
间
再
学
场景压测
LoadRunner中常见(Scenario)是一种用来模拟大量用户操作的技术手段,通过配置和执行场景向服务器产生负载,验证系统中各项性能指标是否达到用户要求:场景通过一系列的脚本组合,并通过1到多个压力机来产负载
- 了解一下Controller界面,大体熟悉一下各项菜单
- 场景分两种,一种是计划场景,一种是目标场景
- 场景先进行设计,然后再运行,运行时可查看各项性能指标,运行完成后可分析性能结果
基本操作
计划场景的设计演示
User Generator中Tools-Create Controller Scenario…
结果分析
- TPS越高,响应时间越小,性能越好,反之性能越差
- 同一场景中,可以采用前后对比测试,用来去欸但那个前后性能的好坏(一般伴随着程序有优化处理)
- 同一场景中,可以采用横对比,来确定不同事务中性能好坏
报告输出
Controller中Results-Analyze Results