性能测试使用静态参数文件往往无法模拟真实情形的动态性,尤其是当测试数据需要和当前数据库状态保持实时一致时。LoadRunner通过强大的数据库查询功能,支持在脚本运行时直接从数据库实时获取数据,实现真正动态的参数化。
根据LoadRunner 12.55及以上版本使用 C语言(HTTP协议) 脚本进行数据库实时参数化。
1. 原理和配置
此方法的重要是利用LoadRunner的 lr_db_connect、lr_db_executeSQLStatement 等数据库函数,在 vuser_init 部分建立和数据库的持久连接,在 Action 部分执行SQL语句并将结果提取到参数中。
前置配置:
数据库驱动:保证LoadRunner的bin目录下有所需数据库的ODBC驱动或原生驱动(如Oracle的oci.dll)。一般需要安装对应的数据库客户端。
数据源(DSN)配置(可选但推荐):在系统的ODBC数据源管理器中,创建一个“系统DSN”。这能简化连接字符串,并集中管理连接配置。如,创建一个名为 PerfDB 的DSN,指向你的测试数据库。
包含头文件:在脚本开头,包含数据库操作的头文件:#include "lrd.h"。
2. 实现步骤
步骤一:在vuser_init中建立数据库连接
连接应在初始化时建立一次,以供整个脚本运行期间复用,避免每次查询都建立连接的开销。
c
vuser_init()
{
lr_db_connect("StepName=DBConnect",
"ConnectionString=Provider=MSDASQL;DSN=PerfDB;UID=testuser;PWD=testpass;", // 使用DSN
// 或使用连接字符串: "ConnectionString=Driver={MySQL ODBC 8.0 Unicode Driver};Server=10.0.1.100;Database=test_db;UID=uid;PWD=pwd;",
"ConnectionName=MyCon", // 为连接指定一个别名,后续操作使用
"ConnectionType=ODBC", // 连接类型,可以是ODBC、OLEDB、Oracle等
LrLastArg);
lr_log_message("数据库连接成功建立。");
return 0;
}
步骤二:在Action中执行查询并参数化
需要执行SQL语句,并将结果集的一列或多列值存储到LoadRunner参数中。
c
Action()
{
// 步骤1: 声明结果集和行句柄
LRD_DB_INFO db_info;
LRD_RESULT_INFO res_info;
LRD_CURSOR *cursor = NULL;
LRD_VALUE value;
// 步骤2: 执行SQL查询语句。此处查询一个需要使用的动态ID和名称。
lr_db_executeSQLStatement("StepName=GetDynamicData",
"ConnectionName=MyCon",
"SQLStatement=SELECT user_id, account_name FROM t_users WHERE status = 'ACTIVE' AND create_date > SYSDATE - 1 ORDER BY user_id",
"DatasetName=MyDataset", // 为结果集命名
LrLastArg);
// 步骤3: 获取结果集的第一行数据,并将其存储到参数中。
// 获取指定列的值(按列索引,从1开始)
lr_db_getValue("StepName=GetValue_UserId",
"ConnectionName=MyCon",
"DatasetName=MyDataset",
"ColumnIndex=1", // 对应`user_id`列
"OutParam=Param_UserId", // 将值存入参数 `Param_UserId`
LrLastArg);
lr_db_getValue("StepName=GetValue_AccName",
"ConnectionName=MyCon",
"DatasetName=MyDataset",
"ColumnIndex=2", // 对应`account_name`列
"OutParam=Param_AccountName", // 将值存入参数 `Param_AccountName`
LrLastArg);
// 步骤4: 在日志中输出获取的参数,用于调试
lr_log_message("获取到的用户ID: %s, 账户名: %s", lr_eval_string("{Param_UserId}"), lr_eval_string("{Param_AccountName}"));
// 步骤5: 在HTTP请求中使用这些动态参数(示例)
web_reg_find("Text={Param_AccountName}", LAST);
lr_start_transaction("api_get_user_info");
web_custom_request("get_user_info",
"URL=http://api.example.com/user/{Param_UserId}",
"Method=GET",
...
LAST);
lr_end_transaction("api_get_user_info", LR_AUTO);
return 0;
}
步骤三:处理多行结果集和游标遍历
如果需要使用查询结果的多行数据(如模拟不同用户并发操作),则需要使用游标进行遍历。
c
// 在 vuser_init 中连接数据库(同上)
// 在 Action 中
Action()
{
LRD_CURSOR *cursor = NULL;
int row_count = 0;
// 执行查询,获取所有活跃用户
lr_db_executeSQLStatement("StepName=GetAllUsers",
"ConnectionName=MyCon",
"SQLStatement=SELECT user_id FROM t_users WHERE status = 'ACTIVE'",
"CursorName=MyCursor", // 指定游标名
LrLastArg);
// 获取游标,用于遍历
lr_db_fetch("StepName=FetchNext",
"ConnectionName=MyCon",
"CursorName=MyCursor",
LrLastArg);
// 循环遍历游标中的每一行
while (lr_db_next_row("ConnectionName=MyCon", "CursorName=MyCursor") == LR_OK) {
row_count++;
lr_db_getValue("StepName=GetCurrentUserId",
"ConnectionName=MyCon",
"CursorName=MyCursor",
"ColumnIndex=1",
"OutParam=CurrentUserId", // 每行的值存入同一个参数,但值会更新
LrLastArg);
lr_log_message("正在处理第%d行,用户ID: %s", row_count, lr_eval_string("{CurrentUserId}"));
// 使用 CurrentUserId 发起业务请求...
web_custom_request("process_user",
"URL=http://api.example.com/process?uid={CurrentUserId}",
"Method=POST",
LAST);
}
lr_log_message("总共处理了 %d 行数据。", row_count);
// 关闭游标,释放资源(重要!)
lr_db_close_cursor("ConnectionName=MyCon", "CursorName=MyCursor");
return 0;
}
步骤四:在vuser_end中关闭数据库连接
c
vuser_end()
{
lr_db_disconnect("StepName=DBDisconnect",
"ConnectionName=MyCon",
LrLastArg);
lr_log_message("数据库连接已关闭。");
return 0;
}
3. 注意事项
连接池和性能:在vuser_init中建立的连接是该虚拟用户的专有连接。在情形中,大量虚拟用户同时建立连接会对数据库造成压力,需保证数据库能承受此连接数。
错误处理:必须检查每个数据库函数的返回值。使用lr_db_last_error函数在失败时获取详细错误信息,并记录到日志。
SQL优化:保证执行的SQL语句本身是高效的,避免在性能测试脚本中引入全表扫描等慢查询。
参数唯一性:当使用游标遍历时,如果需要在同一迭代中多次使用同一行的不同列,需及时将值保存到不同的参数中,因为lr_db_next_row会使游标前进。
事务划分:将数据库查询操作本身包含在一个独立的事务中(如lr_start_transaction("sql_query")),有助于在分析结果时精确了解数据准备阶段所消耗的时间。
通过以上高级参数化技术,可以创建出能够紧密反映生产环境数据状态变化的性能测试脚本,使测试结果更加有可信度。