3.7 关联 - AJava

来源:百度文库 编辑:神马文学网 时间:2024/04/29 08:32:30
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
打开源代码,可以发现置顶帖的代码如下所示(代码格式考虑美观略微调整):




 


闭合帖子列表置顶帖1

决定帖子ID 407的数据是上面代码中带下画线的内容,t_top后的数字并不包含在其中,因为不同的置顶等级决定了该数据的变化。所以可以通过设置左边界为"LB="RB=.aspx\" target=\"_blank\">"Ord=1",
"Search=NoResource",
LAST);
web_url("Topic",
"URL=http://127.0.0.1/showforum-1.aspx",
"Resource=0",
"Referer=",
LAST);
设置日志显示,运行后即可获得置顶帖对应的ID。
2.关联非置顶帖ID
检查源代码,可以得到非置顶帖ID的代码规则(代码格式考虑美观略微调整):


topicicon


 


闭合帖子列表非置顶帖1

相对置顶帖,这个ID的关联要简单很多,设置左边界为web_reg_save_param("topicid",
"LB=
"RB=.aspx\" target=\"_blank\">folder_",
"Ord=ALL",
"Search=NoResource",
LAST);
web_url("page","URL=http://127.0.0.1/showforum-1.aspx",LAST);
通常情况下关联的对象一般都选择链接地址而不是链接名,这样可以确保在以后的使用中比较简便。
3.7.4  一边录制一边关联
接着来看最后一种关联方式,上面的关联都需要进行一定的操作,那么为什么录制某些系统,会得到一些自动的关联函数呢?这就是系统默认提供的自动关联设置,打开录制选项,如图3.148所示。

图3.148  录制选项中的关联设置
在录制选项中有一个Correlation选项,在介绍录制选项时建议大家在初期都将Enable选项去掉,禁止这个功能的使用。
VuGen提供了一些常见应用需要做的关联规则,这些规则有效解决了录制脚本后回放失败的问题,但同时也带来了一些意外。例如:如果需要录制的项目使用了和规则十分相似的数据返回格式,而开发又在其中写了一些自定义的不规范内容,就会导致VuGen错误关联数据,使本来能够回放成功的脚本由于添加了错误的自动关联而无法正常回放。
接着我们来看看,如何设置一个自定义的新规则来实现一次开发终身受益。
单击New Application按钮,新建一个叫做lrwebtours的应用,如图3.149所示。

图3.149  添加关联规则的应用名
然后在这个规则上单击New Rule按钮新建一个规则,名为usersessionid,如图3.150所示。

图3.150  添加关联规则的名称
为该规则填写左右规则,左边界为userSession Value=,右边界为>,确定后保存,如图3.151所示。

图3.151  添加关联规则的左右边界
现在新建一个脚本,重新录制一次Web Tours登录的操作,录制结束查看一下脚本,可以发现系统已经自动按照规则生成了关联函数,运行脚本直接通过。
3.7.5  关联函数web_reg_save_param详解
上面说了常见的3种关联应用的方式,可以看到所谓关联都是使用web_reg_save_ param()函数将服务器返回的内容进行收集过滤的过程,接着我们来仔细研究一下关联函数提供的选项。
首先介绍一个函数web_set_max_html_param_len(),当关联出错的时候VuGen都会提示以下内容:
Action.c(20): Error -26377: No match found for the requested parameter "WCSParam2". Check whether the requested boundaries exist in the response data. Also, if the data you want to save exceeds 1024 bytes, use web_set_max_html_param_len to increase the parameter size [MsgId: MERR-26377]
很多朋友看到这个错误就会头皮发麻,完全不知所措。这种错误99%都是由于关联的边界设置不合理导致没有关联到所需要的内容。系统提示使用web_set_max_html_ param_len()函数的目的是提醒如果被关联内容超出了默认的1024字节就会导致存放数据溢出,就会产生参数值为空、关联失败的情况(做附件下载的脚本就可能会遇到这个问题),但通常都不会关联到如此巨大的内容。
web_set_max_html_param_len()函数可以自定义关联返回值存放的参数的最大长度。
打开Insert/Add Step窗口,找到对应的web_set_max_html_param_len函数,如图3.152所示。

图3.152  添加web_set_max_html_param_len函数
设置最大长度为9999999,如图3.153所示。

图3.153  设置web_set_max_html_param_len函数长度
得到以下脚本:
web_set_max_html_param_len("9999999");
通过这个函数可以确保不会出现参数内容过长而无法存放的错误,不过这是以开销系统资源为代价的。
接着来看看web_reg_save_param()函数的选项,由于关联出来的内容存放在参数中,所以还是建议打开日志中的Parameter substitution选项,以方便调试跟踪。
打开Add Step添加步骤,选择web_reg_save_param函数,打开关联函数设置窗口,如图3.154所示。

图3.154  web_reg_save_param函数设置窗口
Parameter Name
此处设置存放参数的名称,关联出来的内容将会存放在该参数中。这里受到Instance选项的影响。
例如:
设置Parameter Name为temp,当对应的Instance选项是任意一个数字的时候,只会关联一个匹配的记录,关联值将会存放在temp这个参数中。当 Instance是All的时候,关联成功后的值将会依次存放在“temp_数字”这样的参数数组中,并且还会添加一个temp_count的参数存放关联出来的记录条数。
Left Boundary
此处设置左边界,这里是用来填写关联对于数据处理的左匹配内容规则。
在左边界中存放的是一个字符串,例如填写的内容为“左边界”会被转换成以下形式:
web_reg_save_param("Param",
"LB=左边界",
"RB=",
LAST);
如果输入的内容里面有双引号,那么需要通过转义符来进行处理,转义符为\。例如:
web_reg_save_param("Param",
"LB=\"左边界",
"RB=",
LAST);
Match case
默认情况下边界是Match case的,也就是检查大小写的,可以取消下面的选项来忽略大小写检查,会看到函数变为以下形式:
web_reg_save_param("Param",
"LB/IC=左边界",
"RB=",
LAST);
Binary data
如果需要关联的内容是非ASCII字符的,那么需要使用该选项。选中该选项后可以看到函数变为以下形式:
web_reg_save_param("p", "LB/BIN=\\x3F\\xDD", "RB/BIN=\\xCCb", LAST);
Use # for any digit
有些时候需要关联的边界中有些变动的数字,并且由于这个数字导致关联非常难于设置边界,可以使用该选项,例如需要关联处理的内容是:
 backup
chen chen
Administrator Administrator
云层 云层
现在需要从这个服务器返回中获得href对应的地址,应该怎么写边界呢?可以跳过变动的数字直接设置href=为左边界好了,但这样做可能会出现一个问题,如果服务器返回中间有很多href=这样的超链接,那么关联出来的数据可能就无法满足我们的需要,所以这里可以使用#来解决这个问题。
在这里关联的左边界内容应该为:
title="[Alt+2]" accessKey="2" href=",然后将这个内容写入关联函数,添加转义符,函数结果应该为:
web_reg_save_param("Param",
"LB/DIG=title=\"[Alt+#]\" accessKey=\"#\" href=\"",
"RB=",
LAST);
使用这样的左边界可以得到准确范围内的关联值,如果在项目中遇到一些很难确定关联边界的情况时,可以请求程序员编写一些便于区分的标识来方便进行关联的边界设置。
Use ^ as a wildcard for xxx alphanumerical characters
对比上面的功能,这里可以使用^符号来代替任何常用的字符。该功能有3个选项:All、LowerCase、UpperCase,使用^支持任意的字符,可以通过该选项来支持大写字母、小写字母或者不区分大小写。
web_reg_save_param("time",
"LB/ALNUMIC=Server response ti^^:",
"RB=seconds",
"Ord=1",
"Search=Body",
LAST);
该选项在某些时候能够帮助跳过边界中变动的内容完成抓取操作,但是注意这里使用的^符号是不支持长度变化的,也就是说一个^代表了一个字符的位置。如果需要关联的边界本身就是变动的,就不能通过普通的方法来解决了。
Right Boundary
此处设置右边界,这里是用来填写关联时对于数据处理的右匹配内容规则,选项同左边界。
Instance
这个关键字在很多函数里面都有应用,在这里可以填写任意一个整数,也可以填All。如果填写数字,那么说明从返回的记录中取出对应顺序的值,而填写All的话将会返回所有的内容。
当使用Ord=All时,关联函数会把所有匹配过滤策略的记录都抓出来,由于参数只能存放一条记录,所以关联函数会生成一个参数数组。被关联的记录会以{关联参数名_关联id}的形式生成参数列表,并且在最后会有一个 {关联参数名_count}的参数来存放被关联到的记录条数。
例如:上面写过的一个关联函数如下所示:
web_reg_save_param("Param",
"LB/DIG=title=\"[Alt+#]\" accessKey=\"#\" href=\"",
"RB=\">",
“ORD=ALL”,
LAST);
当Instance设置为All时,关联将会返回所有匹配左右边界的内容,结果如下:
Param_1= index.php?module=ACLRoles&action=DetailView&
record=e2ff0695-ebfd-0b34-7947-48cf557dcc0d
Param_2= index.php?module=Users&action=DetailView&
record=59002589-79d0-7037-0ad5-48cf5cd80d2e
Param_3= index.php?module=Users&action=DetailView&
record=1
Param_4= index.php?module=Contacts&action=DetailView&
record=df820bd4-e8ee-6df5-9725-48cf52ebabff
Param_count=4
Relative Frame ID
这个选项是专门针对框架结构的网站设计的,有些时候需要关联的内容是在某个框架中的,这个时候就需要说明所关联的页面是框架中的哪一个了。比如自动关联Web Tour页面就必须使用这个属性:
web_reg_save_param("WCSParam_Diff1",
"LB=userSession value=",
"RB=>",
"Ord=1",
"RelFrameId=1.2.1",
"Search=Body",
"IgnoreRedirections=Yes",
LAST);
这里的1.2.1说明Web Tour网站是基于在第一个大的框架中的第二个框架,第二个框架中的第一个页面这样的两次嵌套框架,所以如果想读取左下侧的页面,这个框架所属的编号就为1.2.1。
框架上栏1.1
框架下栏1.2
下栏中的左栏1.2.1
下栏中的右栏1.2.2
Not Found
如果关联的对象不存在,又该如何进行处理呢?默认值为ERROR,默认情况下如果没有关联到任何内容则提示错误。
Action.c(20): Error -26377: No match found for the requested parameter "time". Check whether the requested boundaries exist in the response data. Also, if the data you want to save exceeds 256 bytes, use web_set_max_html_param_len to increase the parameter size  [MsgId: MERR-26377]
Action.c(20): Notify: Saving Parameter "time = "
Action.c(20): web_url("Mspetshop") highest severity level was "ERROR", 452988 body bytes, 11060 header bytes, 13 chunking overhead bytes  [MsgId: MMSG-26387]
而选择WARNING,则只会简单提示没有抓到内容,不会产生错误。
Action.c(20): Warning -26377: No match found for the requested parameter "time". Check whether the requested boundaries exist in the response data. Also, if the data you want to save exceeds 256 bytes, use web_set_max_html_param_len to increase the parameter size  [MsgId: MWAR-26377]
在很多时候关联不到内容也是正常的。例如需要关联一个板块中的帖子编号,就存在没有帖子的情况。为了确保关联的正确性,在某些情况下可以修改Not Found的提示方式为WARNING,并且为了确保没有帖子时不会对后面的操作产生影响,需要编写一个if语句进行判断,如下所示:
Action()
{
web_reg_save_param("topicid",
"LB="RB=.aspx\" target=\"_blank\">"Ord=1",
"NotFound=WARNING",
"Search=NoResource",
LAST);
web_url("Topic",
"URL=http://127.0.0.1/showforum-1.aspx",
"Resource=0",
"Referer=",
LAST);
if (strcmp(lr_eval_string("{topicid}"),"")==0) {
return 0;
}
else
{
web_url("topic",
"URL=http://127.0.0.1/showtopic-{topicid}.aspx",
"Resource=0",
"Referer=",
LAST);
}
return 0;
}
这样如果该板块没有帖子,就不会执行后面请求帖子页面的操作了。
如果关联选项Ord为All时,这里if语句检查的对象应该修改为topicid_ count参数。
Search in
该项设置关联查询的范围,这里VuGen提供了4个选项:Header、Body、Noresource、All。我们将这4个选项划分两个大类。
Ÿ Noresource
Noresource是从服务器返回的内容类别来考虑的,Noresource就是指只从资源文件中关联内容,也就是只从HTML文件格式中抓内容。
Ÿ Header/Body/All
这3个选项都是从请求返回的所有内容进行关联处理,包括图片、JavaScript脚本等。区别在于对返回信息的分隔方式。在前面介绍HTTP的时候介绍过HTTP返回的内容其实是由Header(HTTP信息头)和Body(HTTP内容)组成的。
Ø Header
指所关联的内容是所有服务器返回请求的HTTP头部分内容。可以通过查看服务器返回内容来了解,Body之前的内容都属于Header,例如:
Header:
------------------------------------------
HTTP/1.1 200 OK
Date: Thu, 25 Sep 2008 06:27:37 GMT
Server: Apache/2.2.8 (Win32) PHP/5.2.5
X-Powered-By: PHP/5.2.5
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
Ø Body
就是服务器返回在Body以后的内容,例如:
Body:
------------------------------------------









SugarCRM
Ø All
指服务器返回的所有内容。
关于Search in这个选项,一般使用得比较多的是Noresource,因为需要关联的内容一般都存放在HTML页面中,并且使用Noresource被关联到的内容又比较少(只返回一个HTML页面)比较适合常用处理,如果某些信息是放在HTTP头内的,那么只能用     Header了。
Save Length
关联出来的内容所需要保存的长度。
例如:通过左右边界关联出来的内容是“sessionid=123456&action=work”,那么如何获得需要的sessionid信息呢?可以使用Save Length来实现,注意这里必须确保被关联内容的长度恒定。将Save Length设置为16,关联出来的结果就变成“sessionid=123456”了,如果想得到后面的sessionid值,就要靠Save Offset选项了。
Save Offset
设置关联的内容偏移量,从第几位开始进行关联操作。继续上面的例子,如果需要获得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"
问题:
前面关联的左右边界都是静态的。如果左右边界是动态的,并且系统返回的id是不定长度的,那么如何使用关联函数将该id取出呢?这个问题在现实情况中会经常遇到,仅仅通过一个关联函数是无法处理的,这个时候还需要使用一个函数strtok()来进行字符内容切割(类似于正则表达式)。
strtok()函数的作用是通过某个分隔符来切分内容。
例如:
char city[1000];
char * token;
extern char * strtok(char * string, const char * delimiters ); //这个函数是扩展的要声明
strcpy(city,"this is shanghai!");
token = (char *)strtok(city," ");
lr_error_message(token);
token = (char *)strtok(NULL," ");
lr_error_message(token);
token = (char *)strtok(NULL," ");
lr_error_message(token);
通过这个函数可以得到三个字符:this、is、shanghai。通过空格来分隔字符串,可以得到第一个符合该条件的内容,如果需要继续分隔就使用strtok(NULL," ");语句。如果关联出来的内容sessionid是变动长度的,如"sessionid=54321123&action=work",则如何获得这个变动长度的sessionid呢?使用下面的代码即可解决:
char temp[100];
char * token;
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,"&");//使用&符号作为分隔符
这个时候token="sessionid=54321123",并且是根据&符号分隔的,所以id的长度可以任意变化,而token中的sessionid可以通过关联的时候Save Offset进行处理,或者使用strtok()函数对等号再次进行处理。
3.7.6  关联函数的高级使用
上面详细介绍了关联的作用和关联函数的详细选项,那么在工作中除了要使用关联函数获得服务器返回以外,还能做什么呢?
例如论坛一个版面中有20个帖子,如何实现随机单击其中某一个帖子的操作呢?回想一下关联选项Ord=All的时候关联出来的结果是不是一个参数数组?既然是参数数组,怎么从参数数组中取出一个随机的值呢?
在不同的LoadRunner版本中处理这个问题使用不同解决方法,现在来分别了解一下具体的方案(这里关联后的参数名为link,设置Ord为All)。
LoadRunner 9系列
在LoadRunner 9中做这个操作非常简洁,因为有了参数数组函数,所以只需要这样写就可以了:
char * siteval;
siteval =lr_paramarr_random(link)
即直接从参数数组link中取一个随机的值。
问题:如果需要随机选择前10个帖子怎么办?
Lr_paramarr_random()函数的随机范围其实是根据lr_paramarr_len()决定的。比如数组长度是20,随机值介于1~20之间,现在手动将这个参数数组设置得小一些,问题就解决了:
char * siteval;
lr_save_string("10","link_count");
siteval =lr_paramarr_random(link);
思考:如果需要随机获取关联结果中的第5至第10个对象,该如何处理呢?
在这种情况下,需要引入随机数,生成随机值为5~10的正整数,再调用lr_paramarr_idx()函数进行处理即可。
如果使用的是LoadRunner9以前的版本,没有这个参数数组函数怎么办?
LoadRunner 8以前的版本
假设存在关联后的参数数组为{link},数组记录总个数为20,需要取得其中的一个随机关联值可以这样写:
char linkname[100],num[100];
int randnum;
//关联和请求操作省略
randnum=rand()%atoi(lr_eval_string("{link_count}"))+1;//获得关联参数数目内的随机数字
strcpy(linkname,"{link_");
//lr_error_message("%s",linkname);
itoa(randnum,num,10);
strcat(linkname,num);
strcat(linkname,"}");
//lr_error_message"%s",linkname);
lr_save_string(lr_eval_string(linkname),"temp");
这段代码看起来是比较头疼的,因为涉及了很多新的函数。
atoi()类型强制转换函数的作用是将字符串型的内容转化为整数型。
atoi(字符串);
由于使用求余操作是对一个数字进行操作,所以需要使用atoi将参数转化为正整数。
使用Rand()%atoi(lr_eval_string("{link_count}")可以得到0~19的随机正整数。
strcpy()字符复制函数就是将一个字符串复制到一个变量中去。
strcpy(变量名,需要复制的字符串内容);
所以strcpy(linkname,"{link_"}的作用是将"{link_"这个内容保存到变量linkname中。
itoa()也是一个强制类型转换函数,和atoi()相反,它是将整数型内容转化为字符串型。
itoa(数字型,字符串,转化格式);
这里转化格式使用的是十进制。为了拼接参数数组,需要生成" {link_2}"这样的参数,通过前面的随机函数已经生成1~20的随机正整数,现在需要把这个数字拼接上去,这里使用strcat()来实现,由于 strcat()必须使用字符串,所以需要将随机整数randnum转化成字符串型的num。
strcat()是一个字符添加函数,它将一个字符串附加在一个变量后。
strcat(变量名,字符串);
接着将上面生成的字符串继续拼接到linkname变量中。
这样就拼接出了linkname="{link_2}",在讲述参数化的时候提到过参数和变量的调用,当变量这样写的时候就可以直接读取到参数名的值。所以使用lr_eval_string()可以将这个变量对应的参数值取出,再通过lr_save_string()将值存放到另一个参数temp中去,最后参数temp就存放了{link_2}参数所对应的值了。
如果需要得到1~10的随机记录呢?这个时候只需要在随机数生成的时候做点手脚就行了。
rndnum=rand()%atoi(lr_eval_string("{link_count}"))+1;//获得关联参数数目内的随机数字
把这段代码修改为:
rndnum=rand()%10+1;//得到1~10的随机数字
在这里最好先做一个判断,避免出现帖子少于10个的问题。
如果需要得到5~10的随机记录呢?这个时候还是随机数生成的操作,如何生成一个范围内的随机数呢?通过公式rand()%(max-min+1)+min能生成从最小值到最大值之间的随机数,所以只需要写为以下形式即可:
Rndnum=rand()%(10-5+1)+5;
LoadRunner 8系列
到了 LoadRunner 8.x系列,VuGen提供了一个新的函数来帮助我们快捷地处理类似的类型转化操作。这个函数就是sprintf(),sprintf()和C语言中的 printf()函数十分相似,使用它可以生成带格式的字符串,从而帮助我们快捷地完成一个特殊格式拼接过程。
sprintf(变量名,格式,值)
例如可以这样写:
char temp[100];
sprintf(temp,"welcome %dtesting",51);
lr_error_message(temp);
可以看到结果是"welcome 51testing",通过这个函数将数字51拼接到了这个字符串中。
还是前面的脚本,看看在LoadRunner 8.x中怎么写,现在可以将脚本改为以下形式:
char linkname[100];
int randnum;
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系列的做法就行了,是不是方便了      很多呢?
通过上面的方法就能处理常见的一些在关联后需要进行随机处理的情况。例如:现在需要这样的脚本,访问论坛首页,登录后检查所有在线用户,然后随机给所有的在线普通会员发一条广告短信息,那么这个脚本怎么去做呢?
分析一下业务:作为一个用户,首先要登录系统,然后查看到所有的在线用户,依次给在线的用户发送短信息。录制一个用户执行以上操作的脚本。如何获得在线的用户信息?在首页的最下面可以看到在线用户的信息,通过HTML代码可以发现普通用户和管理员的区别在于用户名前的图片名。

  • admin00011

  • 普通用户使用的图片是member.gif,而管理员使用的是admin.gif,所以可以通过这个信息来做一个关联操作,得到所有用户信息前是member.gif的用户id。
    web_reg_save_param("member",
    "LB=member.gif\" />"RB=.aspx",
    "Ord=ALL",
    "Search=NoResource",
    LAST);
    web_url("fristpage","URL=http://192.168.0.200",LAST);
    按照代码填写关联的边界,运行后会发现关联失败,原因来自于member.gif" />和
    web_reg_save_param("member",
    "LB=",
    "RB=.aspx",
    "Ord=ALL",
    "Search=NoResource",
    LAST);
    web_url("fristpage","URL=http://192.168.0.200",LAST);
    现在再运行一下这个脚本,可以得到对应的关联内容。
    Action.c(10): Notify: Saving Parameter "member_1 = \r\n\t\t\t\t\t\t\t
    通过这个关联结果,进一步尝试补全整个左边界。
    web_reg_save_param("member",
    "LB=member.gif\" />\r\n\t\t\t\t\t\t\t
    "RB=.aspx",
    "Ord=ALL",
    "Search=NoResource",
    LAST);
    web_url("fristpage","URL=http://192.168.0.200",LAST);
    通过这个代码,就可以将所有是在线的普通用户id通过关联函数保存到member这个参数数组中。
    解决了上面的问题后,还有一个问题就是怎么发送短信息,录制的脚本中可以看到下面的内容。
    web_submit_data("usercppostpm.aspx",
    "Action=http://192.168.0.200/usercppostpm.aspx?msgtoid=1",
    "Method=POST",
    "TargetFrame=",
    "RecContentType=text/html",
    "Referer=http://192.168.0.200/usercppostpm.aspx?msgtoid=1",
    "Snapshot=t14.inf",
    "Mode=HTML",
    ITEMDATA,
    "Name=msgto", "Value=admin", ENDITEM,
    "Name=subject", "Value=广告测试", ENDITEM,
    "Name=message", "Value=广告测试", ENDITEM,
    "Name=sendmsg", "Value=立即发送", ENDITEM,
    LAST);
    这个请求是发送短信的请求,在这里可以看出,给用户发送短消息的关键是在Action属性中的msgtoid=1和ITEMDATA属性中的"Name=msgto","value=admin"这两个值上面,通过测试可以发现系统是以msgto的值作为最终发送人的确认方式来实现发送消息的,那么前面通过关联得到用户id怎么才能获得用户名呢?
    1.用前面的strtok()进行关联拆分。
    2.另一种做法是,得到了用户id后再通过用户id去获得用户名。
    这里使用第2种做法,所以可以得到下面的最终代码。
    Action()
    {
    int i;
    web_reg_save_param("member",
    "LB=member.gif\" />\r\n\t\t\t\t\t\t\t
    "RB=.aspx",
    "Ord=ALL",
    "Search=NoResource",
    LAST);
    web_submit_data("login.aspx",
    "Action=http://192.168.0.200/login.aspx?loginsubmit=true",
    "Method=POST",
    "TargetFrame=",
    "RecContentType=text/html",
    "Referer=http://192.168.0.200/",
    "Snapshot=t11.inf",
    "Mode=HTML",
    ITEMDATA,
    "Name=referer", "Value=index.aspx", ENDITEM,
    "Name=username", "Value=admin", ENDITEM,
    "Name=password", "Value=51testing", ENDITEM,
    "Name=userlogin", "Value= 登录 ", ENDITEM,
    EXTRARES,
    LAST);
    for(i=1;i<=atoi(lr_eval_string("{member_count}"));i++)
    {
    lr_output_message("paramarr %d 's values %s",i,lr_ paramarr_idx("member", i));
    lr_save_string(lr_paramarr_idx("member",i),"userid");
    web_reg_save_param("username",
    "LB=",
    "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.关联的边界如何设置
    左右边界是定位动态数据的关键,但是想要精确定位数据的左右边界并不是一件容易的事,可以通过先松后紧的方式来达到这个目标。首先在做关联的时候设置一个比较明确固定的值,确保能够将所需要的内容整体保存为参数,其次再根据返回的内容进一步细化边界值,一步步达到准确关联结果的目的。