应对复杂的业务场景LoadRunner 仅靠录制回放远不够。开发自定义函数并灵活适配业务流程是创建高可用、可维护压测脚本的能力。
一、脚本架构和语言基础
LoadRunner的VuGen脚本主要使用C 语言,支持ANSI C标准,可以:
定义函数、结构体、全局/静态变量
包含自定义头文件(.h)和源文件(.c)
调用标准库函数(如 string.h、stdio.h、stdlib.h 等)
调用第三方动态库(DLL/SO)
推荐的脚本组织方式:
vuser_init.c:只放一次性的初始化操作,如登录、建立数据库连接。
Action.c:业务思路,循环执行的部分。复杂思路应拆分为多个自定义函数,让Action主流程清晰可读。
vuser_end.c:清理工作,如注销、断开连接。
公共函数/常量:放入独立的 .h 和 .c 文件,通过 VuGen 的解决方案资源管理器添加,然后用 #include "my_func.h" 引入。
二、自定义函数开发实战
1. 创建和添加
在VuGen左侧的解决方案资源管理器中,右键单击脚本名或 Action 文件夹,选择添加文件 - 新建 .c 或 .h 文件。如创建 common.h 和 common.c。
2. 函数编写重点
参数和返回值:和标准C语言一致,可以使用指针、结构体。
内存管理:函数内如果动态分配内存(malloc),一定要在合适时机 free。对 LoadRunner 内部返回的字符串(如 lr_eval_string),不要手动释放,它会自动管理。
线程安全:多虚拟用户并发时,全局变量是非线程安全的,除非确定需要跨 Vuser 共享(极少见)。应使用局部变量或参数传递。
避免高耗时操作:自定义函数应保持轻量,复杂的加解密、图像处理等建议放在独立服务中或使用高效 C 库。
3. 示例生成随机手机号并保存到参数
c
// 在 common.c 中
#include "lrun.h"
#include <stdlib.h>
#include <time.h>
void GenerateRandomPhone(char* paramName) {
char phone[12];
// 生成以 138 开头的随机号
sprintf(phone, "138%08d", rand() % 100000000);
lr_save_string(phone, paramName);
}
在 Action.c 中调用:
c
srand(time(NULL) + lr_get_vuser_id()); // 每个 Vuser 不同种子
GenerateRandomPhone("pMobile");
web_submit_data("login",
"Name=mobile", "Value={pMobile}", ENDITEM,
LAST);
4. 函数库和宏的复用
将常用的判断、格式转换等封装为宏或内联函数,放在头文件中:
c
// common.h
#define FAIL_IF_NOT_FOUND(text, step) \
if (strstr(lr_eval_string("{Response}"), text) == NULL) { \
lr_error_message("Step %s failed, text '%s' not found", step, text); \
lr_exit(LR_EXIT_ITERATION, LR_FAIL); \
}
这样业务脚本就能像写伪代码一样简洁。
三、调用外部 DLL/ SO库
当需要复杂算法(如 AES 加密、Protobuf 序列化)时,直接写 C 代码效率低,更好的方式是调用现成的 C/C++ 动态库。
1. Windows 平台 (DLL)
c
// 在 vuser_init 中加载一次
lr_load_dll("mycrypto.dll");
然后直接声明外部函数并调用:
c
extern char* aes_encrypt(const char* plain, const char* key);
// 在 Action 中使用
char* cipher = aes_encrypt("hello", "1234567890123456");
lr_save_string(cipher, "EncryptedData");
注意:DLL 必须和 VuGen 编译目的一致(32 位/64 位),且依赖的 VC++ 运行时需提前安装。
2. Linux 负载生成器 (SO)
如果负载生成器是 Linux 系统,需要使用 .so 文件:
c
lr_load_dll("libmycrypto.so");
编译 SO 时加上 -fPIC -shared,并保证 LoadRunner 的 LD_LIBRARY_PATH 能找到它。
3. 封装成简单函数
为外部函数做一层轻封装,统一错误处理,避免直接暴露给业务脚本:
c
int SafeAesEncrypt(const char* input, const char* key, char* outBuf, int bufSize) {
// 调用 DLL 函数并进行长度、空指针检查
// 返回 0 成功,-1 失败
}
四、复杂业务场景适配方法
1. 多步骤关联和动态 Token
多数现代应用都有 Token、SessionID 等动态值。可以用 web_reg_save_param 配合高级边界,但遇到复杂格式(如 JSON 里的特定字段,或需要解密后才能取的值)时,需自定义处理:
先用 web_reg_save_param 将整个响应体保存下来。
在自定义函数中用 C 的字符串函数或 JSON 分析库(如 cJSON)提取目的字段。
必要时进行 Base64 解码、解密运算。
lr_save_string存为新参数。
示例:从 JSON 响应中提取嵌套的 Token
c
#include "cJSON.h" // 将 cJSON.c 加入脚本
void ExtractToken(const char* jsonStr) {
cJSON *root = cJSON_Parse(jsonStr);
if (root) {
cJSON *tokenItem = cJSON_GetObjectItem(root, "data.token");
if (tokenItem && tokenItem->valuestring) {
lr_save_string(tokenItem->valuestring, "AuthToken");
}
cJSON_Delete(root);
}
}
2. 异步轮询和等待控制
对于异步任务(如提交订单后轮询结果),需要编写循环体,并使用 web_reg_save_param 捕获状态。
重点:
必须设定最大等待时间和轮询间隔,防止死循环。
在轮询中可插入 lr_think_time(1) 或使用 sleep 控制频率。
根据状态变化用 lr_exit 提前结束迭代或标记事务成功。
c
int timeout = 0;
while (timeout < 30) {
web_reg_save_param("taskStatus", "LB=\"status\":\"", "RB=\"", LAST);
web_custom_request("check_task", ...);
if (strcmp(lr_eval_string("{taskStatus}"), "SUCCESS") == 0) {
lr_end_transaction("Task", LR_PASS);
break;
}
sleep(2);
timeout += 2;
}
3. 多协议混用(Web加WebService加JDBC)
同一脚本中可混合多种协议,只需在录制或开发时添加对应的服务函数。注意:
协议加载顺序:在 Run-time Settings 中确定多协议已启用。
数据库操作:在 vuser_init 中用 lr_db_connect 连接,Action 中查询,End 中断开。为避免连接池耗尽,需控制并发连接数,或给每个 Vuser 独立连接。
WebService:使用 web_service_call 或 soap_request。可将 SOAP 包体参数化,并用自定义函数动态组装 XML。
4. 业务分支和数据驱动
复杂业务往往需要根据参数数据决定执行途径。
将一个参数同时用作输入数据和业务流程控制符。如 CSV 中增加一列 scenario,值为 purchase 或 browse。
在 Action 中通过 strcmp(lr_eval_string("{scenario}"), "purchase") == 0 来进入不同的处理分支,每个分支有独立的事务名。
使用 lr_eval_int 或字符串转换来控制循环次数、金额范围等。
5. 错误处理和容错
自定义错误处理函数能极大降低脚本崩溃概率,并输出可读性强的日志。
c
void ValidateResponse(const char* stepDesc) {
if (strstr(lr_eval_string("{Response}"), "\"code\":0") == NULL) {
lr_error_message("Step [%s] failed, unexpected response", stepDesc);
lr_end_transaction("MainTransaction", LR_FAIL);
lr_exit(LR_EXIT_ITERATION_AND_CONTINUE, LR_FAIL);
}
}
在每个重点请求后调用 ValidateResponse,可避免后续依赖错误的参数导致一连串无意义的报错。
五、调试建议
调试阶段:充分使用 lr_output_message、lr_log_message 打印中间变量,确定函数输出正确。压测时必须注释或删除,否则 I/O 会成短板。
参数化文件:如果自定义函数需要加载大量数据,应在 vuser_init 中一次性读入内存,避免在 Action 中反复读磁盘。
性能影响:自定义函数带来的 CPU 开销会影响压测结果(如过量的加密运算会让 Vuser 自身成为短板)。可用 lr_start_timer 和 lr_end_timer 测量函数耗时,决定是不是要移到辅助服务。
可移植性:如果脚本需要在 Windows 和 Linux LG 上都能跑,注意途径分隔符、DLL/SO 名称、API 差别(如 Windows 的 Sleep vs Linux 的 sleep)。
LoadRunner自定义函数和复杂业务适是用C语言的灵活性弥补录制功能的不足。