LoadRunner VuGen C 语言脚本开发技巧大全
LoadRunner的VuGen使用标准C语言(或类似 C 的语法)作为脚本语言,并提供了大量 lr_、web_ 等开头的API。掌握C语言基础和 LoadRunner 特有函数的结合技巧是写出稳定、高效、易维护脚本的重点。
一、脚本结构和设计原则
1.三段式结构
vuser_init:只执行一次,用于登录、初始化连接等。
Action:反复执行(根据迭代次数),存放重要业务思路。
vuser_end:只执行一次,用于退出、清理资源。
把初始化思路放在vuser_init,避免每次迭代重复登录;事务尽量放在Action中。
2.用函数封装复用思路
c
// 封装一个登录函数,返回事务是不是成功
int login(const char* user, const char* pwd) {
int rc = 0;
lr_start_transaction("login");
// 提交登录请求...
if (/* 成功 */) {
lr_end_transaction("login", LR_PASS);
rc = 1;
} else {
lr_end_transaction("login", LR_FAIL);
}
return rc;
}
这样可在 vuser_init 和后续需要重新登录的地方直接调用。
3 使用LR_PARAM宏代替硬编码
c
#define URL "http://www.zmtests.com"
改为:
c
lr_save_string("http://www.zmtests.com", "pUrl");
web_url("step", "URL={pUrl}", LAST);
便于统一管理和参数化。
二、参数化技巧
1.基础参数化
选中值 - 右键 - Replace with a Parameter - 创建参数并指定数据文件。
常见用法:
c
web_submit_data("login",
"Action=http://www.zmtests.com/login",
"Method=POST",
"TargetFrame=",
"Referer=",
ITEMDATA,
"Name=username", "Value={Username}", ENDITEM,
"Name=password", "Value={Password}", ENDITEM,
LAST);
2. 动态获取参数值
lr_eval_string("{参数名}") 返回字符串,可用于C变量比较或处理。
c
char *user = lr_eval_string("{Username}");
if (strcmp(user, "admin") == 0) { ... }
注意:lr_eval_string 返回的指针可能在下一次调用后失效,需要及时复制。可用 strcpy 或 lr_save_string 保存。
3.参数更新方法选择
Select Next Row:Sequential / Random / Unique
Update Value On:Each iteration / Each occurrence / Once
场景:模拟不同用户并发时,使用 Unique + Each iteration,并启用When out of values: Abort Vuser可保证数据不重复。如果数据有限,应设置合适的中止方法或循环使用。
4.参数化非字符串类型
整数参数可直接用 {intParam},VuGen 会自动转换为字符串,使用时再转回:
c
int num = atoi(lr_eval_string("{countParam}"));
5.参数文件使用技巧
文件首行作为列名,参数名和列名一致。
支持多个参数共享同一文件多列。
使用 lr_advance_param 可手动推进参数行,实现精细控制:
c
lr_advance_param("MyParam");
三、关联技巧
1.手动关联四步法
录制两份相同业务脚本,对比差别,找出动态值(如 Session ID)。
找到动态值出现的服务器响应,确定左右边界。
在请求之前添加关联函数 web_reg_save_param:
c
web_reg_save_param("sessionID",
"LB=Set-Cookie: sessionid=",
"RB=;",
"Search=Headers",
LAST);
用 {sessionID} 替换脚本中硬编码的动态值。
2.使用正则表达式关联
边界不规则或需一致复杂方式时,改用 web_reg_save_param_regexp:
c
web_reg_save_param_regexp(
"ParamName=token",
"RegExp=token[\"']?\\s*:\\s*[\"']([^\"']+)",
"Ordinal=1",
SEARCH_FILTERS,
"Scope=Body",
LAST);
可通过 RegExp 设置捕获组。
3.关联多个一致项Array
当响应中有多个相同边界的值时,使用 Ord=All:
c
web_reg_save_param("items",
"LB=<li class=\"item\">",
"RB=</li>",
"Ord=All",
LAST);
然后用数组接口获取个数和值:
c
int count = atoi(lr_eval_string("{items_count}"));
char itemName[256];
sprintf(itemName, "{items_%d}", i+1); // 索引从1开始
lr_output_message("Item: %s", lr_eval_string(itemName));
或使用 lr_paramarr_idx、lr_paramarr_len 函数。
4.关联边界转义
特殊字符需转义:如 "LB=name=\"value\""。
如果边界包含换行,可尝试 \r\n,或使用 Search=Body 时开启 Extended 选项。
5.自动关联的使用
录制后VuGen会自动扫描关联建议,可快速建立规则,但必须检查生成的边界是不是鲁棒,避免只对单一响应有效。
四、事务和检查点
1.事必须须成对出现
c
lr_start_transaction("T01_SubmitOrder");
// ... 业务请求 ...
lr_end_transaction("T01_SubmitOrder", LR_AUTO);
严禁事务名称不一致、忘记结束事务或在子函数里开始却不结束。
2.使用检查点判断事务成功
c
web_reg_find("Text=Payment Successful",
"SaveCount=success_count",
LAST);
web_submit_data("pay", ...);
if (atoi(lr_eval_string("{success_count}")) > 0)
lr_end_transaction("T01_Pay", LR_PASS);
else
lr_end_transaction("T01_Pay", LR_FAIL);
SaveCount 记录了出现次数,即使一次也可用于判断。
3. 事务内不应包含思考时间
如果业务需要,应把 lr_think_time 放到事务外面,或在 Controller 中设置事务不计思考时间。
在VuGen中,可在 Run-time Settings - General - Think Time 设置忽略或按比例回放。
4.图像检查点
c
web_image_check("src=images/logo.gif",
"Alt=Company Logo",
LAST);
证实页面完整性。
五、日志和调试
1. lr_output_message 和 lr_log_message
lr_output_message 始终输出到日志和控制台,适合调试。
lr_log_message 发送到 LoadRunner 内部日志,仅在对应日志级别启用时才可见。
c
lr_output_message("当前用户:%s,订单号:%s", lr_eval_string("{user}"), orderNo);
2.设置日志级别
在脚本中动态调整:
c
lr_set_debug_message(LR_MSG_CLASS_EXTENDED_LOG | LR_MSG_CLASS_RESULT_DATA, LR_MSG_ON);
// 调试后关闭
lr_set_debug_message(LR_MSG_CLASS_EXTENDED_LOG, LR_MSG_OFF);
或在 Run-time Settings 的 Log 选项中勾选 Extended log。
3.使用断点和Watch
VuGen 支持设置断点,右键菜单可添加变量到 Watch 窗口,观察参数值变化。
回放时可按 F5 逐步执行。
4.输出服务器返回内容
c
web_reg_save_param("responseBody", "LB=", "RB=", "Search=Body", LAST);
// 请求...
lr_output_message("%s", lr_eval_string("{responseBody}"));
对于调试关联非常有用。
六、C语言常见问题
1.字符串操作安全
永不要使用未初始化的 char 数组作为输出。
使用 snprintf 或保证缓冲区足够大的 sprintf。
c
char temp[512];
snprintf(temp, sizeof(temp), "Order_%s", lr_eval_string("{orderID}"));
LoadRunner 的sprintf在某些版本可能导致栈溢出,安全做法是用 lr_save_string 组合或 snprintf。
2.动态内存管理
VuGen 允许 malloc/free,但必须及时释放,避免在长期运行时内存泄漏。
也可以利用 lr_eval_string 的自动管理,但注意其返回指针的生命周期。
如果使用大量字符串,优先用 LoadRunner 参数系统存储。
3.类型转换
字符串 - 整数:atoi(lr_eval_string("{count}"))
整数 - 字符串:lr_save_int(myNum, "myParam") 或 sprintf
浮点数:可使用 atof,保存浮点参数需用 lr_save_string。
4.处理空参数
c
char *val = lr_eval_string("{optionalParam}");
if (val == NULL || strcmp(val, "") == 0) {
// 空值处理
}
5.避免全局变量冲突
Action 文件中的全局变量在迭代间会保留,注意重置或用vuser_init初始化。
使用static限制作用域。
6.文件名和途径
文件操作时,相对途径指向脚本目录,可用 lr_get_attrib_string 获取绝对途径。
c
char scriptDir[1024];
lr_get_attrib_string("ScriptDir", scriptDir, sizeof(scriptDir));
七、错误处理和恢复
1.遇到错误继续执行
c
lr_continue_on_error(1); // 后续步骤即便出错也继续
web_url("step1", ...);
lr_continue_on_error(0); // 恢复默认
或通过 Run-time Settings - Miscellaneous - Continue on error 全局设置。
2.获取HTTP返回码
c
web_url("step", ...);
int httpCode = web_get_int_property(HTTP_INFO_RETURN_CODE);
if (httpCode != 200) {
lr_end_transaction("T1", LR_FAIL);
}
3.手动结束脚本
c
lr_exit(LR_EXIT_ABORT, LR_FAIL);
7.4 事务状态赋值技巧
LR_AUTO:根据动作函数的返回状态自动判断。
LR_PASS / LR_FAIL:显式设定。
LR_STOP:非正常停止(如条件不满足时中止事务但不标记失败)。
八、性能优化和运行时设置
1.减少日志输出
在 Controller 运行时,必须关闭 Extended log,只保留标准日志,可大幅降低 I/O 开销。
2.设置合理思考时间
在 Run-time Settings 中可限制为录制值的 50%~150% 随机,或固定值。
不要在事务中包含过大或随机的思考时间,以免影响响应时间统计。
3.关联缓冲区大小
如果返回内容特别大,关联可能截断,需设置:
c
web_set_max_html_param_len("100000");
或使用 web_reg_save_param 的 MaxLength 属性。
4.少用 lr_output_message 在并发场景
高并发时大量打印会拖慢 Vuser,尽量用条件编译或仅在调试时启用。
5.DNS 缓存和连接复用
勾选 Simulate browser cache 和 Use persistent connections 可贴近真实浏览器行为,但需根据测试目的调整。
在 Run-time Settings → Internet Protocol 中设置。
九、高级脚本技术
1.文件读写自定义数据
c
FILE *f = fopen("output.txt", "a");
if (f) {
fprintf(f, "User %s, Time: %ld\n", lr_eval_string("{user}"), time(NULL));
fclose(f);
}
注意:多 Vuser 并发写同一文件需自行处理锁(如使用文件队列或 VTS)。
2.调用外部 DLL
c
lr_load_dll("myutils.dll");
// 声明函数
int __declspec(dllimport) Add(int a, int b);
// 调用
int result = Add(3,4);
3.使用Virtual Table Server
可在多 Vuser 间共享数据、同步唯一值,比文件操作更可靠。
函数:vtc_connect、vtc_send_row、vtc_retrieve_row 等。
4.数据库证实
c
lr_db_connect("StepName=DB1",
"ConnectionString=...",
"ConnectionName=conn1",
LAST);
lr_db_executeSQLStatement("StepName=verify",
"ConnectionName=conn1",
"SQLStatement=SELECT count(*) FROM orders WHERE ...",
"DatasetName=ds",
LAST);
5.加密和编码
Base64 编解码:lr_base64_encode / lr_base64_decode
MD5/SHA:可加载 crypto DLL 或使用 lr_decrypt(用于负载脚本)。
对参数进行简单加密,提升数据安全性。
十、录制和回放设置
1.选择正确的脚本方式
HTML-based script:直观,适合页面元素确定的场景。
URL-based script:录制每个请求,更底层,适合非 HTML 应用或需精确控制资源下载的测试。
2.自动生成脚本后的清理
删除不必要的请求(如第三方统计、广告),保证压测结果准确。
合并无意义的额外 web_url,减少脚本大小。
3.端口映射和代理设置
使用 web_set_proxy 可动态设置代理。
web_set_sockets_option 设置超时等。
4.处理自定义Header
c
web_add_header("Authorization", "Bearer {token}");
十一、集合点和 IP 欺骗
1.集合点使用
c
lr_rendezvous("before_pay");
lr_start_transaction("pay");
// ...
在 Controller 的 Scenario - Rendezvous 中设置激活方法。
注意集合点应放在事务开始之前,否则会影响事务时间统计。
2.IP 欺骗
在 Controller 的 Scenario 中启用 IP Spoofer,并为每个 Vuser 分配不同 IP。
脚本无需改动,但需保证负载生成器支持 IP 别名。