43
测试实践丛书 LoadRunner 虚拟用户开发指南 陈绍英 金成姬 冯艳硕 Publishing House of Electronics Industry

测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

  • Upload
    others

  • View
    11

  • Download
    0

Embed Size (px)

Citation preview

Page 1: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

测试实践丛书

LoadRunner 虚拟用户开发指南

陈绍英 金成姬 冯艳硕 著

Publishing House of Electronics Industry

Page 2: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

《LoadRunner 虚拟用户开发指南》样章目录:

1.2.6 文件基础操作 P47

2.5.5 应用实例 P101

3.4.3 多机代理方式录制脚本 P225

3.4.4 Socket 脚本的参数化 P229

4.4.2 模块定义文件 P263

4.5.1 导出与封装 C++类 P274

说明:

本书赠送陈绍英、金成姬两位老师共同开发的视频课程《LoadRunner 性能测试快速入

门》,您可以在阅读本书之前,先行学习此视频。

下载地址: http://www.itcast.net/course/detail/507

本书互动网订购地址:http://www.china-pub.com/195440 博文视点读者信箱:[email protected]

Page 3: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

47

1.2.6 文件基础操作

由于 VuGen 已经将多数文件操作和脚本功能封装在一起,因此,在实际的 Vuser 开发

中涉及的文件操作并不多,仅在一些特定情况下须要操作文件。另外,考虑到并发运行

Vuser 时访问文件将可能导致在脚本中“要么进行并发控制”、“要么为每个 Vuser 创建

文件”,而这两种情况都将降低脚本执行效率,进而影响到测试结果的精度,所以实际脚

本开发中不提倡进行文件操作。

不过作为一个性能测试脚本开发人员,仍须要掌握必要的文件编程知识。下面介绍一

些基本的 C 语言文件操作知识。

1. 文件简介

文件是计算机内存中以二进制表示的数据在外部存储介质上的另一种存放形式。从编

码方式来看,文件主要分为文本文件与二进制文件两类。不过这两种类型文件本质上没有

区别,因为它们在外部存储器上都以二进制形式存在。

文本文件也称为 ASCII 码文件,其特点是每一个字符在磁盘上存储时均对应一个字

节,每一个字节存放的是相应字符的 ASCII 码。例如整型十进制数 12345,按 ASCII 文件

存放则要占用 5 个字节,分别存放‘1’、‘2’、‘3’、‘4’、‘5’对应的 ASCII 码。

因此文本文件通常占用空间较大,读写效率相对较低。

二进制文件的特征是,对于不同的数据类型将按照其实际占用的内存字节数来存放。

即,按照数据在内存中的实际存储形式来将其保存到外部存储器上。对于整型十进制数

12345,以二进制文件存放时须要 4 个字节:00000000 00000000 00110000 00111001。因此

二进制文件占用的空间相对较小,读写效率相对较高。二进制文件一般是可执行程序、图

形、图像、声音等文件。

以文本文件工具打开文本文件时,会自动将 ASCII 代码转换为相应的字符来显示,因

此可以直接阅读其内容;而二进制文件由于存放的是文件信息的二进制编码,如果以文本

文件方式来打开并转为 ASCII 码显示后,看到的内容通常是乱码。

下面我们来更深入地了解文件的存储过程。仍然以数字 12345 为例。如果以文本形式

保存到硬盘上,则硬盘上保存的数据应该为‘1’、‘2’、‘3’、‘4’、‘5’对应 ASCII码的二进制数据“00110001 00110010 00110011 00110100 00110101”;如果将“12345”以整型数据直接保存到磁盘上,则是将“12345”对应的二进制格式的数据“00000000 00000000 00110000 00111001”保存到磁盘上。因此,前者用记事本打开,将会看到字符

“12345”;而后者用记事本打开则会看到“90”(Little Endian 字节存储机制)或“09”(Big Endian 字节存储机制)。

LoadRunner 虚拟用户开发指南

Page 4: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

48

提示:Big Endian 与 Little Endian 是两种不同的字节存储方式。Big Endian 方式是“高字节

数据存放在内存低地址处,低字节数据存放在内存高地址处”;Little Endian 方式则

是“低字节数据存放在内存低地址处,高字节数据存放在内存高地址处”。C/C++语

言编写的程序中,数据存储顺序取决于编译平台的 CPU;而 Java 编写的程序则采用

Big Endian 方式来存储数据。

以数字 0x12345678 为例,Big Endian 从低地址到高地址存储的字节内容依次是 0x12、

0x34、0x56 和 0x78;而 Little Endian 从低地址到高地址存储的字节内容依次是 0x78、

0x56、0x34、0x12。

各种网络协议通常采用 Big Endian 的方式来传输数据,因此也可以把 Big Endian 方式

称为网络字节序。当两台采用不同字节序的主机通信时,在传输数据之前双方都必须

对其进行字节序转换,转为网络字节序后才能进行传输。

对于上例,如果是 C 语言保存的整型数据 12345,Windows 环境下用记事本打开通常

会看到“90”(参见下面的内容),为什么看到的不是乱码呢?实际上,“12345”保存

后,以文本方式显示出来的“90”或“09”也是乱码。如果将“12345”换成“67890”,

在 Windows 环境下用 C 语言保存后,通过记事本打开将会看到乱码“ ”。

2. 文件的打开/关闭、读/写与定位

文件常见的操作是打开/关闭、读/写,以及内容定位等,有相应的函数来支持这些操

作。下面将逐一介绍 Vuser 开发过程中常用的文件操作函数。

文件进行读写访问操作之前要先打开,使用完成之后要关闭。文件打开函数 fopen 的

语法如下:

FILE *fopen ( const char *filename, const char *access_mode );

Filename 是要打开的目标文件的路径。

access_mode 是文件访问模式。

函数返回类型为 FILE 类型的指针变量,但要用 long 类型的指针变量来保存返回的文

件指针,因为 Vuser 脚本中不支持直接使用 FILE 类型;打开文件出错时,fopen 将返回一

个空指针值 NULL,可以利用 NULL 来判别是否成功打开文件。

访问模式(access_mode)共有 12 种,如表 1-3 所示。

表 1-3

文件打开方式 意义

“rt” 只读打开一个文本文件,只允许读数据

“wt” 建立并打开一个文本文件,只允许写数据

“at” 打开或建立一个文本文件,在末尾写入

LoadRunner 虚拟用户开发指南

Page 5: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

49

“rb” 打开一个已经存在的二进制文件,只允许读数据

“wb” 建立并打开一个二进制文件,只允许写数据

“ab” 打开或建立一个二进制文件,并在文件末尾写数据

“rt+” 打开一个已经存在的文本文件,允许读和写

“wt+” 打开或建立一个文本文件,允许读写

“at+” 打开或建立一个文本文件,允许读或在文件末写入数据

“rb+” 打开一个已经存在的二进制文件,允许读和写

“wb+” 打开或建立一个二进制文件,允许读和写

“ab+” 打开或建立一个文本文件,允许读或在文件末写入数据

提示:提示:由于文件读写时的默认格式是文本文件,所以上面文件打开方式中的 t 均可以

省略。例如,“rt+”可以省略为“r+”。

各个字符代表含义如下:

r(read): 读 w(write): 写 a(append): 追加 t(text): 文本文件 b(banary): 二进制文件 +: 读和写

打开文件时应该注意下面几点。

以“r”打开文件时,文件应该存在,且只能进行读操作。

以“w”打开文件时仅能进行写操作。文件如果不存在,则会创建一个,如果存在将

会删除当前文件,然后重新创建一个。

追加信息应该用“a”方式打开,同时文件应该存在,否则将会发生错误。

文件关闭函数 fclose 的语法结构如下:

int fclose ( FILE *file_pointer );

参数 file_pointer 是 fopen 函数返回的文件指针。成功关闭文件后,fclose 函数的返回

值为 0,如果有错误发生,则会返回非零值。

文件打开完成后,就可以进行读写操作了。C 语言提供了丰富的读写函数来操作文件,

这里仅介绍 Vuser 开发过程中使用较多的数据块读写函数 fread 与 fwrite。两个函数的语法

结构分别如下:

size_t fread ( void *buffer, size_t size, size_t count, FILE *file_pointer );

LoadRunner 虚拟用户开发指南

Page 6: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

50

size_t fwrite ( const void *buffer, size_t size, size_t count, FILE *file_pointer );

buffer 是一个指向数据缓冲区的指针。在 fread 函数中,buffer 指向的数据缓冲区用来

保存读取到的内容;在 fwrite 函数中,buffer 指向的数据缓冲区的数据将作为文件的输入

数据来源。

size 是 buffer 指向缓冲区数据类型在内存中占有的字节数,可以用 sizeof 函数来获取。

count 是读或写的数据块的块数。

file_pointer 是 fopen 函数返回的文件指针。

fread 与 fwrite 执行后将会返回实际读或写的数据块块数。返回结果通常是指定的读写

数据块数 count。但是当有读写错误发生时,返回的实际读写块数将会小于 count。

文件定位函数 fseek 的语法结构如下:

int fseek ( FILE *file_pointer, long offset, int origin );

file_pointer 是 fopen 函数返回的文件指针。

offset 是相对起始位置的偏移量。

origin 是起始位置,共有三种取值:

SEEK_SET (0) ——文件的开始位置。 SEEK_CUR (1)——文件指针的当前位置。

SEEK_END (2)——文件的末尾。

函数执行成功将会返回 0,否则将会返回非零值。

除了上面的函数外,常用的文件函数还有读写函数 fgetc/fputc、fgets/fputs、getw/putw、

fprintf/fsanf,文件定位函数 rewind、ftell,文件状态函数 feof、ferror、clearerr 等。读者可

以通过 VuGen 的函数帮助手册来掌握它们的用法。

接下来看各个函数的使用方法。

代码清单 1-42 的程序演示了如何直接将变量以二进制形式保存到文件中。在程序中,

首先以二进制读写方式打开文件,然后将 i 和字符数组 test1 在内存中的原始形式直接写到

文件中。写文件完成后,接下来的功能是从文件中读取数据:由于文件位置指针已经到了

文件末尾,因此读文件前要调用 fseek 函数将其移到文件头;由于以二进制形式保存,因

此可以按照写入数据时的方式进行读取,读取数据完成后,调用消息函数输出结果。

代码清单 1-42 Action() { char *filename ="d:\\temp.txt"; long file=NULL; int i=12345,j=0;

LoadRunner 虚拟用户开发指南

Page 7: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

51

char test1[6]="abcde",test2[6]; memset(test2,0,6);//将数组各个元素置零,可避免一些意外错误 file=fopen(filename,"wb+"); fwrite(&i,sizeof(i),1,file);//i以二进制方式写到文件中; fwrite(&test1,sizeof(test1),1,file);//保存字符数组 test2; fseek(file,0,0);//将指针移到文件头 fread(&j,sizeof(j),1,file); //将整型数据读取到 j中 lr_output_message("%d",j); fread(&test2,sizeof(test2),1,file); //将字符读取到 test2中 lr_output_message("%s",test2); fclose(file); return 0; }

代码清单 1-42 的部分执行结果如下:

Starting action Action. Action.c(13): 12345 Action.c(16): abcde Ending action Action.

用记事本打开代码清单 1-42 程序执行后生成的文件 temp.txt,可以看到 12345 显示

成了乱码“90”,如图 1-16 所示。这是因为“12345”对应二进制格式数据“00000000 00000000 00110000 00111001”,对于当前的 Windows 编译环境,存储时将会按照 Little Endian 字节存储机制来进行存储。因此,“12345”将会存储为二进制的“00111001 00110000 00000000 00000000”。而打开文本文件 temp.txt 时,二进制的“00111001 00110000 00000000 00000000”将会被当成 ASCII 码来处理,即十六进制的 ASCII 码“39 30 00 00”,39 与 30 分别对应于字符 9 与 0,而“00”对应于 null,因此看到两个空格。

而字符“abcde”尽管以二进制形式存储,但保存的仍然是各个字符对应的 ASCII 码,因

此字符显示是正常的。

图 1-16 记事本中打开二进制方式保存的文件

再分析一下文件的大小:变量 i 即“12345”是个整型数据,占 4 个字节,以二进制形

LoadRunner 虚拟用户开发指南

Page 8: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

52

式写入文件中仍然为 4 个字节;而存储“abcde”的字符数组 test1 长度为 6( 后一个元

素用来保存字符串结束标识“\0”),因此 后实际写入的大小为 10 个字节。

可以通过文件 temp.txt 的属性来查看其大小,可以看到与前面分析的结果一致。如图

1-17 所示。

可能读者还有一些疑惑,图 1-16 所示的文件并不是乱码,因为可以看懂。可以做个

小实验来看看乱码的效果:将代码清单 1-42 程序中的“12345”换成“67890”,其他部

分不做任何修改,然后执行脚本。再次打开 temp.txt,将会看到图 1-18 所示的乱码文件。

图 1-17 二进制方式保存的文件 temp.txt 的属性

图 1-18 在记事本中显示成乱码的二进制数据

LoadRunner 虚拟用户开发指南

Page 9: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

53

另外,记事本显示内容时还会受字符集的影响,不同的字符集显示的内容可能不同。

点击记事本的菜单[格式]→[字体],可以查看当前的字符集设置(如图 1-19 所示)。对于

当前的字符集,图 1-18 中仅能识别出字符“e”。

图 1-19 查看当前文件的字体设置

尽管显示成乱码,但是读取文件的内容输出结果仍然是正常的。将代码清单 1-42 程

序中的“12345”换成“67890”后,可以看到下面的执行结果:

Starting action Action. Action.c(15): 67890 Action.c(17): abcde Ending action Action.

由于 C 语言把文件看成是字节流或二进制流,因此即使以文本文件方式来读写

temp.txt,代码清单 1-42 程序执行结果也不会发生变化。读者可以将程序中的打开文件一

句替换成:

file=fopen(filename,"wt+");

然后运行程序并检查执行情况。

代码清单 1-43 程序演示了如何将变量转为字符型数据进行保存与读取,这也是经常

使用的一种数据处理方法。在程序中,将整型数据 i 通过函数 itoa 转换为字符串并保存到

字符数组 itostr 中,然后将 itostr 写入文件中。读取文件时,再将字符数据“12345”读到

字符数组 strtoi 中,然后用函数 atoi 将 strtoi 转为整型数据并保存到整型变量 j 中进行输出。

代码清单 1-43 Action() { char *filename ="d:\\temp.txt"; long file=NULL;

LoadRunner 虚拟用户开发指南

Page 10: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

54

int i=12345,j; char itostr[6],strtoi[6]; char test1[6]="abcde",test2[6]; memset(itostr,0,6);//将数组各个元素置零,可避免一些意外错误; memset(strtoi,0,6);//同上; memset(test2,0,6);//同上; file=fopen(filename,"wt+"); itoa(i,itostr,10);//i转为字符串并保存到 itostr中 fwrite(&itostr,sizeof(itostr)-1,1,file); //以字符形式写入整型数据, //-1表示去掉字符串结尾标识; fwrite(&test1,sizeof(test1),1,file); fseek(file,0,0); fread(&strtoi,sizeof(strtoi)-1,1,file); j=atoi(strtoi);//将读出的字符转为整形并保存到 j中; lr_output_message("%d",j); fread(&test2,sizeof(test2),1,file);//将字符读取到 test2中; lr_output_message("%s",test2); fclose(file); return 0; }

注意在代码清单 1-43 中,将 itostr 写入到文件中时去掉了字符串的结尾标识“\0”。

代码清单 1-43 的部分执行结果如下:

Starting action Action. Action.c(17): 12345 Action.c(20): abcde Ending action Action.

用记事本打开代码清单 1-43 程序执行后生成的文件 temp.txt,可以看到这种方式保存

的整型数据没有显示为乱码,如图 1-20 所示。

图 1-20 记事本中打开字符方式保存的文件

LoadRunner 虚拟用户开发指南

Page 11: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

55

查看图 1-20 所示文件 temp.txt 的属性,可以看到文件大小为 11 个字节,如图 1-21 所示。

图 1-21 二进制方式保存的文件 temp.txt 的属性

通过前面的示例可以看出,文本文件与二进制文件在外部存储器上都以二进制形式存

在,主要差别在读写控制上。因此,使用文件时应该注意下面的事项。

不管以文本方式还是以二进制方式打开文件,都可以以二进制方式来写入数据,内存

中的数据将原样输出到文件中。

对于 char 类型的数据,不管是以二进制方式输出到文件中,还是以文本方式输出到文

件中,都要将存储字符转换为对应的 ASCII 码的二进制形式。

写入与读取文件的方式应该保持一致,如采用文本方式写入,就应该采用文本方式读

取;反之,如果以二进制方式写入,读取时也应该采用二进制方式。

不管是文本文件还是二进制文件,如果统一采用二进制方式进行读取和写入,肯定不

会出错。

不管是文本文件还是二进制文件,都可以采用二进制方式或文本方式打开,然后进行

写入或读取。但二进制文件用文本方式进行读取时,可能会出现一些问题。

后还有一个须注意的细节:当按照文本方式向文件中写入数据时,一旦遇到换行字

符(ASCII 为 10),则会转换为“回车—换行”(ASCII 为 13、10)两个字节;在读取文

件时,一旦遇到“回车—换行”的组合(即连续的 ASCII 13、10),则会转换为换行字符

(ASCII 为 10)。而按照二进制方式写入换行字符时则不存在转换问题。

LoadRunner 虚拟用户开发指南

Page 12: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

56

提示:对于 Vuser 开发而言,如果想通过记事本来阅读文件内容,则应该将内存中的数据转

换为字符即 ASCII 码的形式写入文件中;反之则没有必要这样操作。

应用举例

在 VuGen 创建虚拟用户脚本后,通常会进行参数化。例如:很多 Vuser 脚本通常会参

数化用户名与密码以模拟不同的用户并发登录系统,这时就须要创建一个如图 1-22 所示

的参数文件来作为数据源。

图 1-22 参数文件示例

参数文件的生成过程实际就是循环写入数据到文件中的过程,相应的程序如代码清单

1-44 所示。在实际工作中,可以根据须要修改代码清单 1-44 的程序使之生成不同内容的

参数文件。

代码清单 1-44 #define USERCOUNT 100//定义参数数量 Action() { char *namecommon="people";//用户名通用部分,根据须要修改 char *passwordcommon="people";//密码通用部分,根据须要修改 char *filename = "c:\\marks.txt";//参数文件保存路径,根据须要修改 char username[20];//保存用户名的字符数组 char password[20];//保存密码的字符数组 int i=0; long file;//指向文件的指针

LoadRunner 虚拟用户开发指南

Page 13: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

57

//用于打开文件 if ((file = fopen(filename, "w+" )) == NULL) { lr_output_message("Unable to create %s", filename); return -1; } //创建用户名与密码,然后写入文件中 for(i=0;i< USERCOUNT;i++) { sprintf(username,"%s%d",namecommon,i);//创建用户名信息 sprintf(password,"%s%d",passwordcommon,i); //创建密码信息 strcat(username," ");//用空格分隔用户名与密码 strcat(username,password);//连接用户名与密码 strcat(username,"\r\n");//添加回车换行标识 fwrite(username, 1,strlen(username), file) ;//把一组用户信息写入参数文件 } fclose(file);//关闭文件 return 0; }

LoadRunner 虚拟用户开发指南

Page 14: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225
Page 15: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

101

2.5.5 应用实例

在实际项目中,关联应用往往更加灵活,本小节将结合具体的实例来讲解关联在实际

中的应用。在这个例子中 Vuser 脚本实现的功能如下:首先打开百度新闻主页,然后查找

页面上的新闻热搜词,接着以第一个热搜词作为关键字来搜索对应的新闻。 这类应用是

实际工作中 常遇到的情况:先通过关联来保存服务器返回的数据,然后在后面的事务函

数中使用前面保存的数据。

本例中,录制的 Vuser 将完成下面的工作:

打开百度首页 www.baidu.cn;

点击新闻;

以新闻网页上的一个“新闻热搜词”作为关键字搜索新闻。

新闻页面以及关键词页面如图 2-64 所示。

图 2-64 百度新闻主页面

接下来按照下面的操作流程录制脚本。

LoadRunner 虚拟用户开发指南

Page 16: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

102

选择 init 部分,进入百度首页。

选择 Action 部分,录制两个事务:

(1)“新闻”,即点击“新闻”进入到新闻页面;

(2)“热点搜索”,即在搜索文本框中输入任意关键字(本示例为 HTTP),点击“百

度一下”进行搜索。

结束录制。

提示:最后搜索 HTTP 的目的是通过录制生成脚本。

录制完成后将脚本切换到树形目录下,找到“新闻”首页对应项,然后在右键菜单中

点击“Insert Before”,如图 2-65 所示。

图 2-65 预览新闻页面

在打开的函数对话框中输入 web_reg_save_param,找到关联函数,如图 2-66 所示。

LoadRunner 虚拟用户开发指南

Page 17: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

103

图 2-66 查找关联函数

在图 2-66 中,选中 web_reg_save_param,然后点击“OK”按钮,将会打开关联函数

配置对话框,如图 2-67 所示。

图 2-67 关联函数对话框

进入到脚本根目录下 data 文件夹,找到录制日志 RecordingLog.txt 并打开。找到新闻

热搜词所在位置,如图 2-68 所示。

我们分析一下各个新闻热搜词的左右边界特征,可以看到左边界均以

或 开头,右边界则以

结束。因此可以借助通配符来配置图 2-67 所示的关联函数对话框,配置结果如图 2-69 所

示,其中#为数字通配符。

LoadRunner 虚拟用户开发指南

Page 18: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

104

图 2-68 录制日志

图 2-69 关联函数配置结果

LoadRunner 虚拟用户开发指南

Page 19: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

105

接下来进入到脚本的 Action 部分,找到搜索 HTTP 对应的代码,如图 2-70 所示。

图 2-70 搜索热点事务对应代码

然后将图 2-70 所示脚本中的关键字“HTTP”替换为前面创建的关联参数

“{KeyWord}”,替换完成后如图 2-71 所示。

图 2-71 脚本修改结果

终脚本的 Action 部分如代码清单 2-7 所示。

代码清单 2-7 Action() { …… lr_start_transaction("新闻"); …… web_reg_save_param("KeyWord", "LB/DIG= class=\"face#\" rel=\"&ct=1&a=30\">", "RB=</a>", "Ord=1", "Search=Body", LAST); web_url("news.baidu.com", "URL=http://news.baidu.com/", "Resource=0", "RecContentType=text/html", "Referer=http://www.baidu.cn/", "Snapshot=t3.inf", "Mode=HTML", EXTRARES,

LoadRunner 虚拟用户开发指南

Page 20: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

106

"URL=/img/icon.gif?v=1.1", ENDITEM, "URL=/img/line.gif", ENDITEM, "URL=http://eiv.baidu.com/other/ff.js", ENDITEM, …… LAST); lr_end_transaction("新闻", LR_AUTO); lr_start_transaction("搜索热点"); web_submit_form("ns", "Snapshot=t4.inf", ITEMDATA, "Name=word", "Value={KeyWord}", ENDITEM, "Name=tn", "Value=news", ENDITEM, EXTRARES, "URL=http://eiv.baidu.com/maimg/20081230/1385182947.swf", "Referer=http://news.baidu.com/ns?word={KeyWord}&tn=news&from=news&cl=2&rn=20&ct=1",ENDITEM, "URL=http://eiv.baidu.com/maimg/20090112/1385223212.swf", "Referer=http://news.baidu.com/ns?word={KeyWord}&tn=news&from=news&cl=2&rn=20&ct=1", ENDITEM, LAST); lr_end_transaction("搜索热点", LR_AUTO); return 0; }

运行脚本,则可以通过 VuGen 的内置浏览器看到图 2-72 所示的执行结果。

图 2-72 预览脚本运行结果

LoadRunner 虚拟用户开发指南

Page 21: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

107

接下来再查看关联结果的准确性。先打开脚本的 Run-time Settings 对话框,切换到 Log设置,开启扩展 Log 中的“Parameter substitution”选项,如图 2-73 所示,然后保存。

图 2-73 设置脚本的运行参数

接下来将代码清单 2-7 所示脚本中的“Ord=1”修改为“Ord=All”,然后再次执行脚

本,则可以看到图 2-74、图 2-75 所示的执行结果。

图 2-74 关联的参数取值信息

图 2-75 关联的参数个数信息

LoadRunner 虚拟用户开发指南

Page 22: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

108

提示:图 2-74 中的第 10 个关键字在百度的新闻页面上进行了隐藏处理,所以在浏览器查看

页面时看不到,但是实际浏览器已经收到了对应的内容。

LoadRunner 虚拟用户开发指南

Page 23: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

225

3.4.3 多机代理方式录制脚本

当客户端运行在非 Windows 平台(例如 Linux 或 Unix)上时,可以采用多机代理方

式来录制脚本。接下来仍以 WinSocketServer 与 WinSocketAgent 作为示例程序,演示一下

如何使用这种录制方法。

提示:在多机代理录制方法中,VuGen 并不关注 WinSocketServer 与 WinSocketAgent 运行在

何种平台上,仅仅通过分析客户端与服务器的通信内容来生成对应协议的脚本。不过

由于录制过程略显复杂,因此通常作为开发 Vuser 脚本的备选方案。

表 3-2 列出了录制脚本时用到的三台主机及其上面运行的程序。

表 3-2 主机名称 主机 IP 运行程序

bj2-ws-chenshy 10.20.12.37 VuGen

Test-WINXP_2 10.20.5.101 WinSocketServer.exe

Test-WIN2003_5 10.20.5.102 WinSocketAgent.exe

下面将以 10.20.12.37 作为操作主机来描述录制过程。

首先通过开始菜单的“运行”程序,依次登录到两台主机的 D 盘,将示例程序

WinSocketSample(即 bin 文件夹)分别复制到两台主机的 D 盘上,如图 3-33 所示。

图 3-33 部署 WinSocketSample

提示:从开始菜单打开运行对话框后,按“\\IP\D$”格式输入数据后将会弹出对应主机的

登录对话框,输入管理员的用户名与密码后即可登录到相应的磁盘。

LoadRunner 虚拟用户开发指南

Page 24: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

226

接下来修改 10.20.5.101 的配置文件 AgentsList.xml(见图 3-34)。由于 WinSocketServer与 VuGen 不运行在同一主机上,连接端口可以改回默认的 9001。

图 3-34 10.20.5.101 上的 AgentsList.xml 配置信息

在主机 10.20.12.37 上,启动 VuGen,新建一个多协议的虚拟用户脚本,协议类型选

择 Windows Sockets,可以参考前面第 3.4.2 节中的操作过程。接下来修改“Recording Options”中Network项的 PortMapping设置,参考第 3.3.2节中的相关内容新增加一个 Server Entry 项,具体配置如图 3-35 所示。

添加这个端口映射后,VuGen 录制时将会把本机 9001 端口收到的所有数据转发到

10.20.5.102 的 9001 端口。而录制时数据完整发送过程如下: 10.20.5.101 上的

WinSocketServer 先把数据发到运行 VuGen 的 10.20.12.37 的 9001 端口,然后由其转发到

10.20.5.102 上的 WinSocketAgent 的 9001 端口;WinSocketAgent 返回的数据同样由

10.20.12.37 上的 VuGen 转发给 10.20.5.101 上的 WinSocketServer。

图 3-35 新建 Server Entry

LoadRunner 虚拟用户开发指南

Page 25: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

227

在图 3-35 中点击“Update”按钮后保存更新,检查本机(10.20.12.37)WinSocketAgent是否已经关闭,确认已经关闭后就可以开始脚本录制工作;否则 WinSocketAgent 由于已

经占用了 9001 端口,因此将会使映射失效,而 10.20.5.101 上的 WinSocketServer 也会直

接连接本机的 WinSocketAgent, 后导致录制不到任何内容。点击 ,在弹

出的开始录制对话框中将录制对象选择为 wplus_init_wsock.exe,具体操作可以参考第 3.3.2节中的相关内容(见图 3-36)。

图 3-36 10.20.12.37 上的录制界面

接下来通过 Windows 的远程桌面功能登录 10.20.5.102,启动 WinSocketAgent 进行监

听,然后登录 10.20.5.101 启动 WinSocketServer。

WinSocketServer 启动后,进行发送消息的操作,可以看到 10.20.5.101 与 10.20.5.102能够进行正常的 Socket 通信。运行场景如图 3-37 与图 3-38 所示,从运行界面可以看出

WinSocketServer 与 WinSocketAgent 显示的信息是自己正与主机 10.20.12.37 进行通信,这

恰恰说明了代理服务器 wplus_init_wsock.exe 在其中起的作用。

图 3-37 10.20.5.101 运行界面

LoadRunner 虚拟用户开发指南

Page 26: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

228

图 3-38 10.20.5.101 运行界面

在主机 10.20.12.37 的录制状态条上同时可以看到 VuGen 准确地记录了

WinSocketServer 与 WinSocketAgent 的通信过程,如图 3-39 所示。

图 3-39 录制状态条

在 10.20.5.101 上点击“Exit”按钮退出 WinSocketServer,然后在 10.20.12.37 上点击 按钮结束录制,将会生成如代码清单 3-26 所示的 Vuser 脚本。

代码清单 3-26

通过分析上面的脚本,可以看出 lrs_create_socket 创建 Socket 连接指向的远程主机是

LoadRunner 虚拟用户开发指南

Page 27: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

229

Test-WIN2003_5(10.20.5.102),而不是 bj2-ws-chenshy(10.20.12.37),这说明 VuGen在生成脚本时自动进行了处理。不过仍然须要把脚本中的一句:

修改为:

才可以运行。因为 10.20.12.37 与 10.20.5.102 不在同一个网段,回放脚本时无法根据

主机名称来建立连接。

细心的读者可能会发现,代码清单 3-26 的脚本仍然与第 3.4.2 节的案例一样,没有录

制到关闭 Socket 的操作,实际上问题的产生原因与第 3.4.2 节案例的原因是一样的。

按照前面的部署重新启动 VuGen 进行录制,然后启动 WinSocketServer 发送消息。接

着启动 cmd 命令窗口,通过 netstat 命令查看一下网络连接建立情况,可以看到与图 3-40相类似的两对连接。

图 3-40 录制时相关的网络连接

点击“Exit”按钮退出 WinSocketServer,注意不要结束录制。再次用 netstat 查看网络

连接,可以看到图 3-40 中 10.20.12.37(bj2-ws-chenshy)与 10.20.5.101 的连接发生了变化,

与 10.20.5.102 的连接则保持不变,如图 3-41 所示。

图 3-41 WinSocketServer 退出后的网络连接

由于所有连接仍然存在,因而导致 WinSocketServer 退出时录制不到关闭“socket0”的操作。解决这个问题的方法仍然是自己手工在脚本中“return 0”一行前面添加

“lrs_close_socket("socket0");”一句,以关闭“socket0”。

3.4.4 Socket 脚本的参数化

Socket 脚本录制完成后,除了用第 3.2 节相关函数增强功能外,还会用到参数化功能

来处理脚本,以实现用户行为的差异化。Socket 虚拟用户脚本主要有两种参数化方式:直

接创建参数与通过函数来创建参数。

在 data.ws 中创建参数

代码清单 3-27 是创建了参数的 data.ws 文件,创建过程与在 Web(HTTP/HTML)等

类型协议的脚本中创建过程基本一致,通过鼠标右键按照提示即可完成,这里不再赘述。

LoadRunner 虚拟用户开发指南

Page 28: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

230

代码清单 3-27

在脚本中通过函数创建

代码清单 3-28 脚本中,通过 lrs_save_param_ex 创建了一个参数“Param_User”,之

后就可以在 data.ws 中来进行使用。

代码清单 3-28

代码清单 3-29 为与代码清单 3-28 脚本相对应的 data.ws 文件,“<Param_User>”即为

前面脚本创建的参数。

代码清单 3-29

LoadRunner 虚拟用户开发指南

Page 29: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

231

注意:lrs_save_param、lrs_save_param_ex、lrs_save_searched_string 保存的参数可以在 data.ws

中直接使用,但是当 data.ws 中的 buffer 包含参数时,调用这三个函数却无法读取参数

的内容,只是把参数名称当成字符串来处理。

这两种方式创建的参数经常会结合起来进行使用。

LoadRunner 虚拟用户开发指南

Page 30: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225
Page 31: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

263

4.4.2 模块定义文件

模块定义 (.def) 文件可以为链接器提供有关被链接程序的导出、属性及其他方面的信

息,借助模块定义文件可以更有效地解决函数名称改编问题。

下面结合实例来讲解如何借助.def 文件导出外部函数。首先打开前面的工作空间

Win32Dll,然后在里面创建一个空的动态链接库工程 DefFileDll。具体操作为,点击 File

→New,在弹出的新建对话框中切换到 Project 标签,如图 4-34 所示。

点击“OK”按钮确认后,在接下来的 DLL 类型对话框中选择“An empty DLL project”,

点击 Finish 按钮。然后在弹出的确认对话框中选中“OK”按钮,即可完成向导工作。可

以参考第 4.2.1 节中 Win32Dll 工程的创建过程。

工程创建完成后,首先添加一个 C++源文件 DefFileDll.cpp,然后将 Win32Dll 工程下

Win32Dll.cpp 的代码复制过来并逐行修改, 终的代码如代码清单 4-10 所示。

图 4-34 DefFileDll 工程信息设置

LoadRunner 虚拟用户开发指南

Page 32: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

264

代码清单 4-10

本例中仅须要创建 C++源文件,不须要创建.h 头文件。而且,在 C++源文件中只须要

定义函数返回类型即可,不须要使用导出标志。函数导出声明将会在模块定义文件.def 中完成。

接下来通过文件浏览器进入到 DefFileDll 的工程目录下(本例中为 E:\VC Project\ Win32Dll\DefFileDll)新建一个记事本文件,并将其文件名称修改成 DefFileDll.def。

提示:修改文本文件的扩展名时,文件浏览器当前设置可能会不显示扩展名,遇到这个问题

可以通过文件浏览器“工具”菜单下的“文件夹选项”来修改显示属性,使之显示文

件扩展名,如图 4-35 所示。

LoadRunner 虚拟用户开发指南

Page 33: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

265

图 4-35 文件夹属性设置

返回到 Visual C++的工作空间中,切换到文件视图。在 DefFileDll 的工程上点击右键,

如图 4-36 所示。

图 4-36 添加文件到当前项目的菜单

在图 4-36 中的右键菜单中,点击“Add Files to Project”,将会打开图 4-37 所示的添

加文件对话框。选择文件类型为 .def,然后选择 DefFileDll.def 将其添加到当前工程

DefFileDll 中。

LoadRunner 虚拟用户开发指南

Page 34: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

266

图 4-37 选择 DefFileDll.def 文件

添加完成后,通过工作空间的 FileView 打开 DefFileDll.def 进行编辑,在其中添加导

出函数的相关代码,如代码清单 4-11 所示。

代码清单 4-11

代码清单 4-11 的代码比较简单:

“LIBRARY DefFileDll”声明导出库名称,与工程名称应该一致;

“EXPORTS”导出标志;

“MyAdd=Add”导出函数 Add,其导出名称为 MyAdd;

“MySetDate=SetDate”导出函数 SetDate,其导出名称为 MySetDate。

接下来编译工程 DefFileDll,debug 目录下将会生成如图 4-38 所示的文件。

图 4-38 工程 DefFileDll 的编译结果

启动命令行窗口,用 dumpbin 命令查看 DefFileDll.dll 的导出情况,如图 4-39 所示。

LoadRunner 虚拟用户开发指南

Page 35: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

267

可以看到导出了两个函数,MyAdd 与 MySetDate。这说明 Visual C++按照模块定义文件的

说明来导出了 DLL 中的函数。

图 4-39 DefFileDll.dll 导出函数信息

把新生成的 DefFileDll.dll 复制到 Win32DllApp 工程目录(Debug 目录下也可以)下。

然后修改 Win32DllAppDlg.cpp 中的源程序。

在“加法”按钮中,把 GetProcAddress 函数获取函数地址一句修改成:

ADDPROC Add=(ADDPROC)GetProcAddress(hmodule,"MyAdd");

在“日期”按钮中,把 GetProcAddress 函数获取函数地址一句修改成:

SETDATEPROC SetDate=(SETDATEPROC)GetProcAddress(hmodule,"MySetDate");

然后把两个按钮代码中加载 DLL 的一句均修改为:

hmodule=LoadLibrary("DefFileDll.dll");

代码修改完成后,把 Win32DllApp 设置为活动工程,再次编译并运行工程

Win32DllApp。测试按钮的功能,可以看到将会调用 DefFileDll.dll 中的两个导出函数。

提示:隐式调用中仍然应该用 MyAdd 与 MySetDate 来进行测试,读者可以自己来进行隐式

加载 DefFileDll.dll 的练习。

下面在 LoadRunner 中来调用 DefFileDll.dll 中的函数。打开上节的虚拟用户脚本

DllTest,然后将 DefFileDll.dll 复制到脚本目录下。

修改 Action 部分的脚本,使其如代码清单 4-12 所示。

LoadRunner 虚拟用户开发指南

Page 36: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

268

代码清单 4-12

然后运行虚拟用户脚本,可以在 VuGen 中看到如图 4-40 所示的部分执行日志。

图 4-40 DllTest 脚本部分执行日志

上面的例子为了区别导出函数与原函数的名称,仍然改变了函数名称,而较合理的方

法是不改变 DLL 源程序中的导出函数名称。返回到 DefFileDll 工程中,修改 DefFileDll.def的代码如代码清单 4-13 所示,然后重新编译。

代码清单 4-13

编译完成后,启动命令行窗口,用 dumpbin 命令查看导出 DefFileDll.dll 新的导出情况,

如图 4- 41 所示。可以看到,导出的函数与源文件中声明的函数名称一致,并未发生改变。

图 4-41 DefFileDll.dll 函数导出信息

LoadRunner 虚拟用户开发指南

Page 37: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

269

读者可以自己在 Win32DllApp 工程或者 LoadRunner 的虚拟用户脚本中来调用新生成

的 DefFileDll.dll。

LoadRunner 虚拟用户开发指南

Page 38: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

274

4.5.1 导出与封装 C++类

使用动态链接库不但能导出全局函数,还能导出 C++类以及类中的函数。但是导出的

C++类及其中的函数仅适用于隐式加载 DLL 的调用方式,而不能直接用于 LoadRunner 的虚拟用户脚本中。想要用于显式加载或者虚拟用户脚本中,则须要进行一定的封装处理。

下面将介绍如何导出 C++类、类中的方法以及如何把类或者类中的方法改造成

LoadRunner 可以调用的外部函数。

参照前面第 4.2.1 节的内容,新建一个空的“Win32 Dynamic-Link Library”项目,仍

然把项目添加到当前工作空间中。项目创建完成后添加两个文件:头文件 CppDll.h 和程序

文件 CppDll.cpp。头文件 CppDll.h 中的代码如代码清单 4-20 所示,程序文件 CppDll.cpp的内容如代码清单 4-21 所示。

接下来对程序内容进行分析。在头文件中,共声明了两个类 SimpleTest 与 DrawTest。SimpleTest 类包含两个方法:Add 与 SetDate;DrawTest 类包含一个方法 DrawCircle。

编译的时候,由于 CppDll_API 在源文件中已经定义成_declspec(dllexport),因此编译

后 DrawTest 将被编译成一个导出类,SimpleTest 将仅仅导出类中的一个方法 Add。对于导

出类 DrawTest 而言,它的 public 类型的方法将会全部被导出。

代码清单 4-20

LoadRunner 虚拟用户开发指南

Page 39: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

275

代码清单 4-21

编译工程 CppDll,然后用 Dumpbin 命令查看 CppDll.dll 的导出情况。可以看到,导出

了类“??4DrawTest@@QAEAAV0@ABV0@@Z”、方法“?Add@SimpleTest@@QAEHHH @Z”和类方法“?DrawCircle@DrawTest@@QAEXHHH@Z”,如图 4-45 所示。

图 4-45 CppDll.dll 的导出信息

返回到前面的测试工程 Win32DllApp,切换到 ResourceView 视图。打开对话框设计

LoadRunner 虚拟用户开发指南

Page 40: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

276

界面,添加一个 ID 为 IDC_BTN_DRAWCIRCLE 的新按钮,并将其显示标题设定为“画

圆”。接下来通过类向导来为按钮添加代码。在“画圆”按钮上右键点击 按钮打开类视图,切换到 Message Maps 标签,Object IDs 选择 IDC_BTN_DRAWCIRCLE,Message 选择 BN_CLICKED,然后点击右边的 Add Function 按钮。弹出添加函数对话框,

如图 4-46 所示,单击“OK”按钮确认即可。

图 4-46 通过类向导为“画圆”添加消息函数

按钮消息函数添加完成后,接下来连同加法按钮一同修改代码。“加法”按钮和“画

圆”按钮对应的代码如代码清单 4-22 所示。

代码清单 4-22

编译 Win32DllApp 工程前,应该先把 CppDll.dll 与 CppDll.lib 拷贝到其对应的工程目

录下;然后设置其工程属性,在 Link 标签下添加对 CppDll.lib 的引用。可以参照第 4.3.1

LoadRunner 虚拟用户开发指南

Page 41: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

277

节图 4-26 中的设置方法。

后编译并运行 Win32DllApp 工程。在对话框窗口中点击“画圆”按钮,可以看到如

图 4-47 所示的效果。

图 4-47 “画圆”按钮执行效果

接下来探讨如何在 LoadRunner 脚本中使用 CppDll.dll。显然,CppDll.dll 无法直接供

LoadRunner 虚拟用户脚本使用,而这类问题在实际的虚拟用户开发中也非常常见。例如在

实际工作中,开发人员经常会提供一些不太适合直接使用的动态链接库。解决这类问题的

方式很简单,将这样的 DLL 在 Visual C++重新封装即可。下面以 CppDll.dll 为例来讲解如

何封装导出内容以及在虚拟用户脚本中如何使用类中的内容。

首先修改一下 CppDll.h 中的内容,如代码清单 4-23 所示。修改后 CppDll.dll 将会导出

两个类中的全部函数。

代码清单 4-23

LoadRunner 虚拟用户开发指南

Page 42: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

278

然后编译工程 CppDll。编译完成后在当前工作空间新建一个空的动态链接库工程

CppLRDll,将工程 CppDll 编译后生成的 CppDll.dll 与 CppDll.lib 拷贝到 CppLRDll 工程目

录下。

接下来为工程 CppLRDll 添加一个 C++源程序文件 CppLRDll.cpp,打开后添加如代码

清单 4-24 所示的代码。

代码清单 4-24

设置工程 CppLRDll 的属性,在 Link 标签下添加对 CppDll.lib 的引用,可以参照第 4.3.1节图 4-26 中的设置方法。设置完成后编译工程,编译成功后将会生成 CppLRDll.dll。

打开前面创建的虚拟用户脚本 DllTest,然后将新生成的 CppLRDll.dll 复制到脚本目录

下。修改脚本 Action 部分内容如代码清单 4-25 所示。

代码清单 4-25

如果这个时候编译脚本,将会看到下面的编译错误。这是因为 CppLRDll.dll 并不包含

Add 与 SetDate 的实现,因为它们的实现封装在 CppDll.dll 中。 Starting iteration 1. Starting action Action. Action.c(5): Error: C interpreter run time error: Action.c (5): Error -- File error : LoadLibrary(CppLRDll.dll) failed : 找不到指定的模块。 Action.c(5): Notify: CCI trace: Action.c(5): ci_load_dll(0x00b20584,

LoadRunner 虚拟用户开发指南

Page 43: 测试实践丛书 - Baiduimages.china-pub.com/ebook195001-200000/195440/ch01.pdf · 1.2.6 文件基础操作 P47 2.5.5 应用实例 P101 3.4.3 多机代理方式录制脚本 P225

279

0x00ef015e "CppLRDll.dll"). Action.c(5): Notify: CCI trace: Compiled_code(0): Action(). Action.c(6): Error: C interpreter run time error: Action.c (6): Error -- Unresolved symbol : Add. Action.c(6): Notify: CCI trace: Compiled_code(0): Action().

将工程 CppDll 编译生成的 CppDll.dll 拷贝到 DllTest 脚本目录下。重新编译运行脚本,

将会看到下面的正确运行结果。 Running Vuser... Starting iteration 1. Starting action Action. Action.c(6): Action.c(9): Today is 2008-4-16 Ending action Action. Ending iteration 1.

通过这种方式,有效地解决了 LoadRunner 不能直接调用这类 DLL 的问题。读者可以

自己在 Win32DllApp 工程中尝试显式加载 CppLRDll.dll,以调用其中的导出方法。

提示:对于自己开发的 DLL,把 DLL 放到脚本的当前路径下之后,一定要通过 File 菜单下

的 把全部相关 DLL 文件添加到脚本中。这样在执行场景时 Controller

才会把 DLL 同步发送到远程主机上,否则远程主机上将会找不到动态链接库。

LoadRunner 虚拟用户开发指南