当前位置: 首页 > 测试知识 > LoadRunner自定义函数开发与复杂业务脚本适配
LoadRunner自定义函数开发与复杂业务脚本适配
2026-07-03 作者cwb 浏览次数157

应对复杂的业务场景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语言的灵活性弥补录制功能的不足。


文章标签: 软件测试 测试工具
咨询软件测试