3.7 关联
3.7.1 关联原理
先来模拟一个场景:我们去坐飞机,登机前需要在检票点出示机票,检票登机。那么检票人员会检查哪些东西呢?机票是否真实、航班是否正确等信息,验证通过即可登机。
过了几天我们又去坐飞机,还是拿同样的一张票去登机,检票人员再一次核对信息,发现机票已经过期了,自然就不能成功登机了,那怎么才能顺利登机呢?可以通过伪造机票信息的方式来登机,首先去询问其他乘客的机票信息,然后将自己的机票涂改为他们的机票,最后使用涂改后的机票登机。
在这里我们通过收集别人返回的信息,将提交给检票人员的数据修改为这些收集到的信息,从而满足登机的验证策略。在脚本中也存在大量类似的情况,录制的时候,服务器会给一个唯一的认证码来进行操作,当再次回放脚本的时候服务器会给一个全新的认证码,而脚本录制是死的,还是拿老的认证码提交,从而导致脚本执行失败。
例如:常见系统中的登录功能,在登录后服务器会返回SessionID,登录后的操作都需要提交该SessionID确认身份。使用VuGen录制时,将会记录服务器返回的SessionID并且原封不动地在下一个请求中发给服务器,如图3.127所示。
图3.127 录制得到SessionlD
待到回放的时候,服务器会在接收到用户名和密码后返回新的SessionID,而脚本仍然发送旧的SessionID给服务器,最终因SessionID错误,导致脚本回放失败,如图3.128所示。
图3.128 回放时SessionID 错误
为了确保脚本回放的成功,我们需要获得服务器每次返回的动态SessionID,再将这个动态数据发回给服务器。而关联能够帮助我们将服务器返回的数据进行处理并保存为参数。
如何获得服务器的返回数据呢?我们先来回顾一下VuGen是如何发出请求并如何接受返回数据的。录制一个Mspetshop访问首页的代码,看一下发出的请求和服务器的返回是什么。根据HTTP规范,应该是给服务器发了一个Get请求,服务器返回HTML页面。录制完成后把脚本切换到Tree模式,如图3.129所示。
在Tree模式下切换到web_url()函数,如图3.130所示。
图3.129 切换视图为Tree模式 图3.130 Tree视图下选择:localhost:8081函数
在右侧的Snapshot中可以看到该请求对应的Page View,如图3.131所示。
图3.131 Tree视图下Url:localhost:8081函数Page View
在Page View的右侧就是客户端发给服务器的数据包Client Request,如图3.132所示。
图3.132 Tree视图下函数Client Request
服务器返回的Server Response数据内容也会被显示出来,如图3.133所示。
图3.133 Tree视图下函数Server Response
关联的作用是能够把服务器返回的Server Response内容保存为参数。关联是通过关联web_reg_ save_param()函数来实现的,这个函数可以帮助我们完成对服务器返回的保存操作。
现在将关联函数添加在脚本中。
Action()
{
//在请求前添加关联函数
web_url("localhost:8081", "URL=http://localhost:8081/", LAST);
return 0;
}
关联函数是一个注册型函数,需要告诉VuGen下一个请求返回是需要被处理的,所以该函数必须要写在请求前,否则就会提示无法获得关联结果的错误。这是绝大多数使用关联的新手最容易犯的错误。
将鼠标移动到添加关联函数的地方,然后选择Insert菜单下的New Step选项,如图3.134所示。
系统弹出Add Step窗口,在Find Function中输入web_reg_save_param找到该函数,如图3.135所示。
图3.134 在代码中添加一个新步骤 图3.135 在Add Step中找到关联函数
确定后弹出web_reg_save_param关联函数设置窗口,如图3.136所示。
我们先不解释各个选项的作用,按照以下规则填写,如图3.137所示。
图3.136 关联函数设置窗口 图3.137 设置关联服务器返回的所有内容
设置Parameter Name为temp,Instance为1,Search in为All,单击OK按钮,脚本变为下面的内容。
Action()
{
web_reg_save_param("temp",
"LB=",
"RB=",
"Ord=1",
"Search=All",
LAST);
//在请求前添加关联函数
web_url("localhost:8081", "URL=http://localhost:8081/", LAST);
return 0;
}
关联函数的作用是通过一种规则将服务器的返回保存到一个参数中,所以为了看到参数的内容,应打开参数取值的日志选项。运行脚本查看日志,会看到大量的蓝色参数值罗列出来,如图3.138所示。为了方便浏览,也可以打开该日志文件output.txt查看明细。
图3.138 关联后服务器返回的参数内容
每一句“Notify:Saving Parameter temp=”后面都是被关联到的服务器返回,我们会发现返回不止一条,一共有10次关联值被保存到了temp这个参数中去。那么访问一个页面怎么会有那么多个关联值呢?
根据对日志的具体分析会发现,被关联到的内容有以下几种:
1.HTML
2.CSS
3.JavaScript
4.JPEG、PNG、GIF
回想和对比一下前面介绍的HTTP基础,能够发现访问一个页面,服务器会先返回页面HTML,再下载页面中调用的CSS、JavaScript和图片,而关联函数把这些内容都抓了下来,所以会得到如此多的关联内容,也就是说通过这个关联函数获得了服务器所有的返回内容。
对于图片和CSS等内容其实一般都不是我们关心的,系统的动态数据需要关联的服务器返回信息一般都保存在HTML正文中。所以接着修改一下关联函数,将Search In选项从All修改为Noresource,也就是只需要关联HTML、XML等资源而不关联附属的信息。关联函数变为如下形式:
web_reg_save_param("temp",
"LB=",
"RB=",
"Ord=1",
"Search=Noresource",
LAST);
再运行一次可以看到被关联的内容只有一个了,而且就是服务器返回的HTTP包中的正文内容,和通过VuGen看到的服务器返回内容完全相同,现在这个返回是保存在一个叫做temp的参数中。
整个HTTP请求分为两段,一段是开头的HTTP头数据包,叫做header;另外一段就是HTML页面,叫做body。在“Search”选项中可以修改为Headers或者Body来更加精确地划分关联范围。
通常我们还是使用Noresource来处理关联范围,因为这样可以得到最常用的返回内容。在得到了服务器返回的内容后,接着就可以做任何想做的事情,比如获得页面中的任意一个对象,例如如何将MS Petshop 4.0首页上Powered by的字样抓出来作为参数呢?
关联函数提供了一个叫做左边界、右边界的策略,只需要填写这个规则,它会在整个被关联范围内查找符合该规则的内容。先查看需要关联的内容的对应代码。
Version 4.0 - Powered by .NET 2.0
在服务器返回的内容中,可以找到Powered by这个词,那么什么样的边界条件能够让我们获取这个词呢?可以通过设置左边界为 Version 4.0 -,右边界为.NET 2.0的方式来检索这个词,然后修改关联函数。
修改关联函数有以下两种方法:
1.直接在代码上修改。
2.切换到Tree模式下,双击关联函数,在Left Boundary/Right Boundary中输入条件,修改关联函数,如图3.139所示。
图3.139 修改关联函数的左右边界
确认后得到新的关联函数:
web_reg_save_param("temp",
"LB=Version 4.0 -",
"RB=.NET 2.0",
"Ord=1",
"Search=NoResource",
LAST);
现在再运行一下整个脚本,在日志中可以看到想要的这个词被成功地关联出来,并且保存到了参数temp中。
Action.c(20): Notify: Saving Parameter "temp = Powered by "
可以通过lr_eval_string()将temp值提取出来并输出,或者提供给后面需要使用该值的函数。关联函数的结果需要请求结束后才能获得,所以提取关联结果参数的值必须在请求后,而关联函数必须在请求前。
由于我们在访问请求前设置规则,服务器返回的动态内容都会被关联函数捕捉并且保存到参数中,这样就实现了对动态数据的捕获,通过后期处理过程,即可完成对于动态对象的操作功能。
通常情况下我们使用关联的步骤流程如图3.140所示。
图3.140 关联步骤流程
简单地说,关联就是对服务器的返回做处理的过程,刚才我们使用的是手动关联,而关联其实有3种方式:
1.自动关联
2.手动关联
3.一边录制一边关联
3.7.2 自动关联
首先来看最简单的自动关联。
自动关联是VuGen提供的自动扫描关联处理策略,它的原理是对同一个脚本运行和录制时的服务器返回进行比较,来自动查找变化的部分,并且提示是否生成关联。
打开LoadRunner自带的Web Tours网站,录制一个登录的过程然后再回放,通过Test Results界面可以看到回放虽然没有提示错误,但是并没有正确地登录到系统,如图3.141所示。
图3.141 Web Tours登录脚本回放失败
问题就出在脚本中web_submit_data()函数的userSession(这里如果使用web_submit_ form()函数就不会出现错误,需要修改录制选项中的Recording等级和录制方式)。
web_submit_data("login.pl",
"Action=http://127.0.0.1:1080/WebTours/login.pl",
"Method=POST",
"TargetFrame=body",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/nav.pl?in=home",
"Snapshot=t2.inf",
"Mode=HTML",
ITEMDATA,
"Name=userSession", "Value=98852.6810044552fAitHDtpHHQVzzzHDAfAipt
AizHf", ENDITEM,
"Name=username", "Value=admin", ENDITEM,
"Name=password", "Value=admin", ENDITEM,
"Name=JSFormSubmit", "Value=off", ENDITEM,
"Name=login.x", "Value=0", ENDITEM,
"Name=login.y", "Value=0", ENDITEM,
LAST);
这里的 userSession是用户在每次访问该网站时系统提供的随机字符串,用来区分不同的用户,在回放时由于发送了过期的userSession导致脚本回放失败。几乎绝大多数脚本回放失败都是因为关联的问题。现在单击Vuser菜单中的Scan Script for Correlations选项,如图3.142所示。
使用自动关联前,脚本必须要先运行一次。
运行后会在窗口中看到以下内容,VuGen已经识别出脚本中的动态内容,如图3.143所示。
图3.143 VuGen识别到可以自动关联动态数据
如果支持VuGen的选择,那么可以单击右下角的Correlate按钮将这个数据生成关联,也可以单击Create Rule按钮将其转化为一个规则(该功能在第3.7.4节再详细介绍),如图3.144所示。
图3.144 单击Correlate按钮确认自动关联的对象
单击Correlate按钮后,便生成了一个关联,关联的前后内容和所属Action也被列了出来,自动关联结束,如图3.145所示。
图3.145 确认后的自动关联对象
切回到Script模式看看脚本发生了什么变化,首先脚本中增加了以下内容:
// [WCSPARAM WCSParam_Diff1 44 98852.6810044552fAitHDtpHHQVzzzHDAfAiptAizHf] Parameter {WCSParam_Diff1} created by Correlation Studio
web_reg_save_param("WCSParam_Diff1",
"LB=userSession value=",
"RB=>",
"Ord=1",
"RelFrameId=1.2.1",
"Search=Body",
"IgnoreRedirections=Yes",
LAST);
这里多了一个关联函数,而关联出来的内容被保存到一个叫做WCSParam_Diff1的参数中去。再看web_submit_data()函数:
web_submit_data("login.pl",
"Action=http://127.0.0.1:1080/WebTours/login.pl",
"Method=POST",
"TargetFrame=body",
"RecContentType=text/html",
"Referer=http://127.0.0.1:1080/WebTours/nav.pl?in=home",
"Snapshot=t2.inf",
"Mode=HTML",
ITEMDATA,
"Name=userSession", "Value={WCSParam_Diff1}", ENDITEM,
"Name=username", "Value=admin", ENDITEM,
"Name=password", "Value=admin", ENDITEM,
"Name=JSFormSubmit", "Value=off", ENDITEM,
"Name=login.x", "Value=0", ENDITEM,
"Name=login.y", "Value=0", ENDITEM,
LAST);
提交给服务器的userSession值,已经变成了前面关联获取的{WCSParam_Diff1}参数。再次回放脚本,一切正确,进入系统。
自动关联是通过录制和回放时的服务器返回值比较来确定需要关联的内容,然后帮助生成对应的关联函数,常用在非常标准的动态数据处理中,例如sessionid。在大多数情况下脚本无法正常回放都可以通过自动扫描的方法来生成关联,解决动态数据的问题。
当希望将多次回放脚本的结果作为自动关联的比较参考时,可以切换脚本到Tree下的比较模式,如图3.146所示。
然后单击View菜单中Snapshot下的Select Iteration选项,弹出如图3.147所示的窗口。
图3.146 打开view both recording and replay snapshots模式 图3.147 设置选择测试结果的目录
将多次脚本回放的Result日志添加进来(这个需要通过General Options中的设置每次运行Result重命名才能实现),这样做自动扫描关联就不仅比较录制和最后一次回放的服务器返回了。
但是自动关联有很强的局限性,无法实现特殊的动态数据捕获。例如帖子的ID、作者名、某些表格单元值等,这个时候就需要使用手动关联来解决它了。
3.7.3 手动关联
手动关联是关联应用的最有效手段,通过手动关联函数web_reg_save_param()将想要的字符串保存到一个参数中。通过关联可以捕获服务器返回的标题或正文的文本内容,也可以用来捕获服务器返回的超链接,比如需要获得Discuz!NT2.5论坛版面下的置顶和非置顶帖中顶端帖子的ID。
打开论坛版面,可以看到该板块的所有帖子列表。为了获得需要关联的帖子ID,首先需要分析一下置顶帖的左右边界。
1.关联置顶帖ID
打开源代码,可以发现置顶帖的代码如下所示(代码格式考虑美观略微调整):
决定帖子ID 407的数据是上面代码中带下画线的内容,t_top后的数字并不包含在其中,因为不同的置顶等级决定了该数据的变化。所以可以通过设置左边界为
设置关联的内容偏移量,从第几位开始进行关联操作。继续上面的例子,如果需要获得123456这个字符串,则需要设置Save Offset为10,同时设置Save Length为6即可。
通过Save Length和Save Offset的设置,我们就可以方便地抓取服务器返回内容的任意一个部分了。
关联可以调整偏移量和长度,那么参数能做到吗?当然可以,如果需要对一个参数值进行偏移和长度设置,则需要使用lr_save_var()函数,例如下面的代码:
lr_save_string("I come from shanghai","city");
lr_save_var(lr_eval_string("{city}"),6,0,"result");
//从city这个参数中取6位长度的内容保存到result参数中
lr_save_var(lr_eval_string("{city}")+7,4,0,"result");
//从city这个参数的第7位开始取4个长度的内容保存到result参数中
Action.c(3): Notify: Saving Parameter "city = I come from shanghai"
Action.c(4): Notify: Saving Parameter "result = I come"
Action.c(5): Notify: Saving Parameter "result = from"
extern char * strtok(char * string, const char * delimiters ); //这个函数是扩展的要声明
strcpy(city,"this is shanghai!");
token = (char *)strtok(city," ");
token = (char *)strtok(NULL," ");
token = (char *)strtok(NULL," ");
extern char * strtok(char * string, const char * delimiters );
lr_save_string("sessionid=54321123&action=work","param");
strcpy(temp,lr_eval_string("{param}"));//取出参数值,并且赋值给变量temp
token = (char *)strtok(temp,"&");//使用&符号作为分隔符
上面详细介绍了关联的作用和关联函数的详细选项,那么在工作中除了要使用关联函数获得服务器返回以外,还能做什么呢?
例如论坛一个版面中有20个帖子,如何实现随机单击其中某一个帖子的操作呢?回想一下关联选项Ord=All的时候关联出来的结果是不是一个参数数组?既然是参数数组,怎么从参数数组中取出一个随机的值呢?
在不同的LoadRunner版本中处理这个问题使用不同解决方法,现在来分别了解一下具体的方案(这里关联后的参数名为link,设置Ord为All)。
在LoadRunner 9中做这个操作非常简洁,因为有了参数数组函数,所以只需要这样写就可以了:
siteval =lr_paramarr_random(link)
Lr_paramarr_random()函数的随机范围其实是根据lr_paramarr_len()决定的。比如数组长度是20,随机值介于1~20之间,现在手动将这个参数数组设置得小一些,问题就解决了:
lr_save_string("10","link_count");
siteval =lr_paramarr_random(link);
思考:如果需要随机获取关联结果中的第5至第10个对象,该如何处理呢?
在这种情况下,需要引入随机数,生成随机值为5~10的正整数,再调用lr_paramarr_idx()函数进行处理即可。
如果使用的是LoadRunner9以前的版本,没有这个参数数组函数怎么办?
假设存在关联后的参数数组为{link},数组记录总个数为20,需要取得其中的一个随机关联值可以这样写:
randnum=rand()%atoi(lr_eval_string("{link_count}"))+1;//获得关联参数数目内的随机数字
//lr_error_message("%s",linkname);
//lr_error_message"%s",linkname);
lr_save_string(lr_eval_string(linkname),"temp");
atoi()类型强制转换函数的作用是将字符串型的内容转化为整数型。
由于使用求余操作是对一个数字进行操作,所以需要使用atoi将参数转化为正整数。
使用Rand()%atoi(lr_eval_string("{link_count}")可以得到0~19的随机正整数。
strcpy()字符复制函数就是将一个字符串复制到一个变量中去。
所以strcpy(linkname,"{link_"}的作用是将"{link_"这个内容保存到变量linkname中。
itoa()也是一个强制类型转换函数,和atoi()相反,它是将整数型内容转化为字符串型。
strcat()是一个字符添加函数,它将一个字符串附加在一个变量后。
如果需要得到1~10的随机记录呢?这个时候只需要在随机数生成的时候做点手脚就行了。
rndnum=rand()%atoi(lr_eval_string("{link_count}"))+1;//获得关联参数数目内的随机数字
rndnum=rand()%10+1;//得到1~10的随机数字
sprintf(temp,"welcome %dtesting",51);
可以看到结果是"welcome 51testing",通过这个函数将数字51拼接到了这个字符串中。
还是前面的脚本,看看在LoadRunner 8.x中怎么写,现在可以将脚本改为以下形式:
randnum=rand()%atoi(lr_eval_string("{link_count}"))+1;//获得关联参数数目内的随机数字
sprintf(linkname,"{link_%d}",rndnum);
lr_save_string(lr_eval_string(linkname),"temp");
这样直接就把随机的rndnum变量放在了linkname变量中,并且生成linkname= "{link_2}"这样的变量。后面的按照LoadRunner 7系列的做法就行了,是不是方便了 很多呢?
通过上面的方法就能处理常见的一些在关联后需要进行随机处理的情况。例如:现在需要这样的脚本,访问论坛首页,登录后检查所有在线用户,然后随机给所有的在线普通会员发一条广告短信息,那么这个脚本怎么去做呢?
普通用户使用的图片是member.gif,而管理员使用的是admin.gif,所以可以通过这个信息来做一个关联操作,得到所有用户信息前是member.gif的用户id。
web_reg_save_param("member",
web_url("fristpage","URL=http://192.168.0.200",LAST);
按照代码填写关联的边界,运行后会发现关联失败,原因来自于member.gif" />和
","RB=
","Ord=1",
"Search=NoResource",
LAST);
web_url("userinfo","URL=http://192.168.0.200/userinfo-{userid}.aspx",LAST);
web_submit_data("usercppostpm.aspx",
"Action=http://192.168.0.200/usercppostpm.aspx?msgtoid={userid}",
"Method=POST",
"TargetFrame=",
"RecContentType=text/html",
"Referer=http://192.168.0.200/usercppostpm.aspx?msgtoid=1038",
"Snapshot=t14.inf",
"Mode=HTML",
ITEMDATA,
"Name=msgto", "Value={username}", ENDITEM,
"Name=subject", "Value=广告测试", ENDITEM,
"Name=message", "Value=广告测试", ENDITEM,
"Name=sendmsg", "Value=立即发送", ENDITEM,
LAST);
}
Return 0;
}
补充:
表3.14 关联中常用的转义内容
转义内容
说明
\b
Backspace键
\f
换页
\n
换行
\r
回车
\t
水平制表符
\v
垂直制表符
\'
单引号标记
\"
双引号标记
\\
反斜杠
\?
文本问号
关联的使用要点:
1.什么内容需要关联
当脚本中的数据每次回放都发生变化时,并且这个动态数据在后面的请求中需要发送给服务器,那么这个内容就需要通过关联来询问服务器,获得该数据的变化结果。当确认关联的内容后,要确认所需要的数据是在哪个请求中返回的,并且定位该数据的位置和特征,以及左右边界。
2.关联的边界如何设置
左右边界是定位动态数据的关键,但是想要精确定位数据的左右边界并不是一件容易的事,可以通过先松后紧的方式来达到这个目标。首先在做关联的时候设置一个比较明确固定的值,确保能够将所需要的内容整体保存为参数,其次再根据返回的内容进一步细化边界值,一步步达到准确关联结果的目的。