1. 项目概述:从录制到回放,构建可信的虚拟用户
性能测试的核心,是模拟真实用户对系统施加压力,从而评估其在高负载下的表现。而模拟的逼真度,直接决定了测试结果的可信度。LoadRunner作为一款经典的性能测试工具,其脚本开发环节——主要由Virtual User Generator(简称VuGen)完成——正是构建这些“虚拟用户”灵魂的关键。很多团队在性能测试中踩坑,根源往往不在于场景设计或结果分析,而在于脚本本身:一个录制完未经任何处理的脚本,就像一台只会复读的机器,它无法处理动态数据,无法模拟思考时间,更无法应对复杂的业务逻辑,用它跑出来的结果,其参考价值微乎其微。
因此,掌握VuGen脚本开发的全流程,远不止是学会点“录制”和“回放”按钮。它是一套从业务理解、协议选择开始,贯穿脚本录制、增强、调试,直至最终参数化和关联的完整工程。这个过程的目标,是让每一个虚拟用户(Vuser)都能像一个有血有肉的真实用户那样去操作,携带独立的身份,处理动态的响应,并在操作间有合理的停顿。今天,我就结合自己多年在金融、电商等多个高并发项目中的实战经验,为你拆解这个全流程中的每一个关键步骤、背后的原理,以及那些手册上不会写的“坑”与技巧。
2. 脚本开发全流程核心思路拆解
2.1 流程全景与核心理念
在动手之前,我们必须建立一个正确的认知:VuGen脚本开发不是一个线性的“录制-运行”过程,而是一个迭代的、以“真实性”和“可重用性”为目标的工程循环。一个完整的脚本开发流程,通常包含以下几个核心阶段,它们环环相扣:
- 前期准备与协议分析:这是最容易被忽视却至关重要的第一步。你需要明确测试的业务流程,并准确判断应用所使用的网络协议。
- 脚本录制与初步捕获:利用VuGen的录制功能,捕获客户端与服务器之间的网络交互数据,生成初始脚本。
- 脚本清理与增强:删除冗余请求,插入事务(Transaction)和集合点(Rendezvous),并加入思考时间(Think Time),使脚本逻辑清晰且符合真实用户行为模型。
- 动态数据处理:这是脚本的“灵魂”所在。通过关联(Correlation)处理服务器返回的动态值(如Session ID、订单号),通过参数化(Parameterization)为不同的虚拟用户提供不同的输入数据(如用户名、搜索关键词)。
- 脚本调试与验证:确保脚本能够单用户、无错误地回放,其业务结果符合预期(如成功登录、成功下单)。
- 脚本强化与容错:添加逻辑判断、错误处理机制,使脚本在压力测试中更健壮。
这个流程的核心驱动力,是让脚本从“记录一次操作”变成“模拟一类用户行为”。例如,一个登录脚本,不应该只是回放你录制时输入的那个“张三”的账号密码,而应该能让1000个虚拟用户分别使用1000个不同的账号密码成功登录。这就是参数化和关联要解决的问题。
2.2 协议选择的底层逻辑与常见误区
协议选择是VuGen脚本开发的基石,选错了协议,后续所有工作都是徒劳。VuGen支持上百种协议,但日常高频使用的集中在Web(HTTP/HTML)、Web Services、Socket等几类。
为什么协议如此重要?因为VuGen本质上是一个“协议客户端模拟器”。它需要知道如何“说”应用程序使用的“语言”(协议),才能正确地构建请求包、解析响应包。选择“单协议”还是“多协议”,取决于你的应用架构。
- Web(HTTP/HTML):这是最常用的协议,用于测试基于浏览器的B/S架构应用。它录制的是浏览器与Web服务器之间的HTTP请求和响应。这里有一个关键选择:
HTML-based script还是URL-based script。- HTML-based script:VuGen会尝试理解HTML页面结构,将页面内的资源(如图片、JS、CSS)请求自动关联到前一个页面请求中,脚本更贴近用户视角(一个动作对应一个函数,如
web_submit_form),易于理解和增强。这是绝大多数Web应用的推荐选择,尤其适合现代动态Web应用。 - URL-based script:录制所有独立的HTTP请求,包括每个资源的请求都会生成单独的
web_url函数。脚本冗长,但控制粒度最细。通常只在HTML-based模式无法正确录制或需要精细控制每个请求时使用。
- HTML-based script:VuGen会尝试理解HTML页面结构,将页面内的资源(如图片、JS、CSS)请求自动关联到前一个页面请求中,脚本更贴近用户视角(一个动作对应一个函数,如
- Web Services:用于测试SOAP或RESTful API。这是目前微服务架构下的测试重点。选择此协议,VuGen会直接录制对API端点的调用(如
web_custom_request),并可以方便地处理XML或JSON格式的请求与响应。 - Windows Sockets:用于测试纯TCP/UDP Socket通信的应用,如一些游戏服务器、自定义协议的中间件等。它录制的是底层的Socket数据流,脚本是二进制或文本格式的
lrs_send和lrs_receive函数,需要开发者对协议格式有深入了解。
实操心得:协议选择避坑指南
- 不确定时,先用“单协议”:对于大多数Web应用,直接选择“单协议 -> Web (HTTP/HTML)”开始录制。如果VuGen自动探测不到,再尝试“多协议”并勾选可能的协议组合。
- 观察录制结果:录制后,如果脚本中充满了
web_url请求图片、CSS等静态资源,而缺少核心的提交动作(如web_submit_form),很可能是因为应用采用了大量前端框架(如React, Vue.js)的异步加载,HTML-based模式解析失败。此时可以尝试切换到URL-based模式,或者使用VuGen的“录制选项”调整录制粒度。- 对于手机APP测试:不要试图直接录制手机协议。标准做法是,在PC上设置代理(如Fiddler、Charles),将手机的网络代理指向PC,然后在VuGen中选择“Web (HTTP/HTML)”协议,并设置代理录制。这样就能捕获到APP发出的所有HTTP/HTTPS请求。对于非HTTP协议(如自定义TCP),则需要使用Socket协议并配合抓包工具分析报文格式。
3. 脚本录制、清理与结构化增强
3.1 录制配置与实战技巧
确定了协议,就可以开始录制。以最常用的Web (HTTP/HTML)协议为例,启动VuGen创建新脚本,选择协议后进入录制对话框。关键配置在“录制选项”(Recording Options)中:
- 录制模式(Recording Mode):如前所述,优先选择
HTML-based script。 - 浏览器(Browser):建议使用VuGen兼容列表内的浏览器,如IE或Chrome的特定版本。使用不兼容的浏览器可能导致录制失败或脚本异常。
- URL地址(URL Address):填写你要测试的Web应用起始地址。
- 工作目录(Working directory):脚本的存储位置。
点击“开始录制”,VuGen会打开指定的浏览器并开始捕获流量。此时,你就像正常用户一样操作业务流程即可,例如:打开首页 -> 登录 -> 搜索商品 -> 加入购物车 -> 下单 -> 退出。
录制完成后,VuGen会自动生成脚本,并显示在脚本视图中。同时,“录制快照”会显示录制过程中捕获的页面截图,这对于后续的脚本增强和调试非常有帮助。
注意事项:录制环境与数据准备录制前,务必准备一个干净的测试环境。避免在已登录状态的浏览器中开始录制,这会导致登录步骤缺失。最好使用无痕模式或全新浏览器会话。同时,想清楚你要录制的“典型业务场景”是什么,使用一套独立的测试数据(如test_user01),避免与线上或其他测试数据冲突。
3.2 脚本清理:去芜存菁,提升效率
刚录制的脚本通常包含大量“噪音”,主要是对静态资源(如图片、样式表、JavaScript文件)的请求。这些请求在性能测试中通常不是压力瓶颈(它们可以被浏览器缓存或CDN加速),但会显著增加脚本的复杂度、回放时间,并消耗不必要的压力机资源。
清理策略:
- 自动过滤:在“录制选项” -> “高级” -> “脚本”中,可以设置自动过滤掉某些资源类型(如.jpg, .png, .css, .js)。但需谨慎,有些.js文件可能包含重要的业务逻辑请求。
- 手动删除:回放脚本,在“回放日志”中观察哪些请求是必须的(通常返回
200状态码且对业务有影响),哪些是可选的(如返回304 Not Modified的缓存请求或静态资源)。在脚本中注释或删除那些非必需的web_url调用。 - 一个简单的判断原则:如果删除某个请求后,单用户回放依然能成功完成业务流程,那么这个请求通常可以安全删除。重点关注那些提交数据的
web_submit_form、web_custom_request以及关键的页面跳转请求。
3.3 插入事务与集合点:定义度量与制造并发
脚本清理后,我们需要为其注入结构,以便在Controller中能清晰地度量性能,并制造真实的并发压力。
事务(Transaction):事务用来衡量一个或多个操作所消耗的时间。例如,“登录”事务应包含从点击登录按钮到成功跳转到首页的所有服务器请求。在VuGen中,使用
lr_start_transaction(“登录”)和lr_end_transaction(“登录”, LR_AUTO)将相关操作包裹起来。事务必须成对出现,且名称一致。在结果分析时,我们主要关注事务的响应时间、通过率等指标。- 实操技巧:事务的起点和终点要精准。起点通常放在触发业务操作的请求之前,终点放在该操作完成、页面稳定之后(例如,放在跳转后页面最后一个关键元素加载完成的请求之后)。避免将思考时间包含在事务内。
集合点(Rendezvous):集合点用于在场景中让虚拟用户同步,模拟瞬间的并发操作。例如,模拟“秒杀”场景,需要在点击“立即抢购”按钮前设置一个集合点,让所有虚拟用户准备好后同时发出请求。使用
lr_rendezvous(“秒杀提交”)插入集合点。- 重要警告:集合点必须与事务结合使用,且通常放在事务开始之后、关键请求之前。绝对不要将集合点放在
lr_think_time()函数内或事务外,这会导致虚拟用户无意义地等待或同步混乱。在Controller中,你需要手动启用集合点策略才会生效。
- 重要警告:集合点必须与事务结合使用,且通常放在事务开始之后、关键请求之前。绝对不要将集合点放在
思考时间(Think Time):思考时间模拟真实用户在操作间隔的等待(如阅读页面内容、填写表单)。录制时,VuGen会默认记录操作间隔作为
lr_think_time()。在压力测试时,我们可以在Controller中设置思考时间的处理方式:忽略、按录制时间回放、按比例随机化。为了产生持续的压力,在调试脚本时可以先忽略思考时间;但在真实负载场景中,建议使用按比例(如50%-150%)随机化,以更真实地模拟用户行为差异。
4. 脚本的灵魂:参数化与关联实战解析
如果说事务和集合点定义了脚本的骨架,那么参数化和关联就是赋予脚本灵魂和血液的关键。
4.1 参数化:让每个虚拟用户成为独立个体
参数化解决了“数据唯一性”和“数据驱动”的问题。你不能让1000个用户都用“张三”登录。
操作步骤:
- 在脚本中选中需要参数化的静态值(如用户名
username)。 - 右键选择“替换为参数”(Replace with a Parameter)。
- 为参数命名(如
P_UserName),并选择参数类型。最常用的是“文件”(File)类型。 - 在参数文件中(如
P_UserName.dat),按列准备好测试数据。每一行代表一个虚拟用户可能使用的值。 - 在参数属性中,设置“选择下一行”(Select next row)和“更新值的时间”(Update value on)策略。
- 选择下一行:
Sequential(顺序):每个Vuser按顺序取数据。Random(随机):每次随机取。Unique(唯一):每个Vuser分配唯一值,确保不重复。这是最常用且重要的策略,特别是对于登录名、订单号等唯一性约束字段。
- 更新值的时间:
Each iteration(每次迭代):每次脚本迭代更新参数值。Each occurrence(每次出现):每次遇到该参数都更新(很少用)。Once(一次):整个场景运行中只取一次值。
- 选择下一行:
一个电商登录下单的参数化示例:我们需要参数化:用户名、密码、搜索关键词、商品ID。
- 创建
P_User、P_Pwd、P_Keyword、P_ProductID四个文件参数。 P_User和P_Pwd采用Unique+Each iteration策略,并且两列数据行行对应,确保账号密码匹配。P_Keyword可以采用Random+Each iteration,模拟用户随机搜索。P_ProductID可以采用Sequential+Each iteration,假设我们有一批固定的测试商品。
踩坑实录:参数化文件与数据准备
- 数据量不足:如果使用
Unique策略,但参数文件中的数据行数少于虚拟用户数乘以迭代次数,VuGen会报错“参数P_XXX无更多唯一值”。务必确保数据充足。一个技巧是,在Controller中设置“当超出值时”(When out of values)为“中止虚拟用户”(Abort Vuser),而不是“循环”(Continue with last value),后者会导致数据重复,破坏测试真实性。- 文件格式与路径:参数文件默认保存在脚本目录的
dat文件夹下,是.dat格式的文本文件。如果数据中包含逗号,需要使用引号包裹,或者更改分隔符。在脚本迁移到其他机器时,注意参数文件的相对路径问题。- 密码等敏感信息:避免在脚本中明文硬编码密码。即使参数化,文件也是明文存储。对于安全要求高的测试,可以考虑在运行时从加密存储或外部系统中动态获取,但这需要更高级的脚本开发能力。
4.2 关联:处理服务器返回的动态数据
关联是脚本开发中最具挑战性的一环。它的目的是捕获服务器响应中动态变化的值(如会话IDJSESSIONID、订单号orderId、CSRF令牌_token),并将其保存到一个参数中,供后续请求使用。
为什么需要关联?因为很多请求具有状态性。例如,登录后服务器会分配一个唯一的Session ID,后续所有操作都必须携带这个ID,服务器才知道是谁在操作。如果脚本直接使用录制时捕获的固定ID,第二个虚拟用户回放时就会因为Session失效而失败。
关联的核心方法:VuGen提供了自动关联和手动关联两种方式。
自动关联(扫描与规则):
- 录制后自动扫描:录制完成后,点击菜单栏的“工具” -> “扫描关联规则”。VuGen会根据内置规则(如对
JSESSIONID、VIEWSTATE等常见动态值的规则)扫描整个脚本和快照,提示可能的关联点。你可以选择接受或拒绝。 - 回放时自动关联:在“运行时设置”(Run-time Settings) -> “互联网协议” -> “关联”中,启用“在回放期间检测到新动态参数时执行关联”。当回放脚本遇到与录制时不同的响应值时,VuGen会弹出对话框建议关联。这对于未知的动态值非常有用。
- 局限性:自动关联并非万能,对于自定义格式的响应(如JSON中的
“token”: “a1b2c3”)或者结构复杂的响应,它可能无法识别。
- 录制后自动扫描:录制完成后,点击菜单栏的“工具” -> “扫描关联规则”。VuGen会根据内置规则(如对
手动关联(必备技能):这是性能测试工程师必须掌握的核心技能。步骤是“找、存、用”。
- 第一步:找(Find the value)。比较录制和回放的服务器响应,定位动态值。使用VuGen的“对比”工具(Tools -> Compare with Vuser)最方便。它会高亮显示两次运行中响应内容的不同之处,那个不同的字符串很可能就是需要关联的动态值。
- 第二步:存(Save the value)。确定左右边界(LB/RB),使用
web_reg_save_param_ex(或更早版本的web_reg_save_param)函数将其捕获到参数中。关键点:这个函数必须放在触发该响应的请求之前(它是一个“注册型”函数)。 - 第三步:用(Use the parameter)。在后续需要该动态值的请求中,用参数(如
{CorrToken})替换原来的硬编码值。
一个经典的登录Session关联示例:假设登录后,服务器在响应头中返回一个Set-Cookie: JSESSIONID=ASDF1234GHJK;。
- 录制脚本:录制登录过程,脚本中后续请求的请求头里会包含
Cookie: JSESSIONID=ASDF1234GHJK。 - 回放失败:回放时,第一个虚拟用户用新的账号登录,服务器返回新的
JSESSIONID=ZXCV5678TYUI,但脚本后续请求仍发送旧的ASDF1234GHJK,导致服务器返回401/403错误。 - 手动关联:
- 在登录请求(
web_submit_form(“login”))之前,插入关联函数:web_reg_save_param_ex( “ParamName=Corr_JSESSIONID”, “LB=Set-Cookie: JSESSIONID=”, “RB=;”, “Search=Headers”, LAST); - 找到后续需要携带Cookie的请求(如查询请求),将其请求头中的
JSESSIONID=ASDF1234GHJK替换为JSESSIONID={Corr_JSESSIONID}。
- 在登录请求(
高级技巧:处理JSON/XML响应的关联现代API大量使用JSON响应。对于形如
{“token”: “abc123”, “userId”: 1001}的响应,我们可以使用边界更精确的web_reg_save_param_ex,或者使用LoadRunner的JSON/XPATH解析函数(如lr_json_string_to_object和lr_json_get_value,取决于VuGen版本)。这需要你熟悉JSON结构,并编写更灵活的提取逻辑。例如,先捕获整个JSON响应体,再从中解析出特定字段的值。
5. 脚本调试、验证与强化
5.1 回放调试与日志分析
脚本增强后,必须进行单用户回放调试,确保其逻辑正确。使用VuGen的“运行”(F5)或“验证”(Verify)功能。
关键检查点:
- 回放日志(Replay Log):这是最重要的调试信息源。务必将其级别设置为“扩展日志”(Extended Log)或至少“参数替换日志”(Parameter Substitution)。在日志中,你可以看到:
- 每个请求的发送和接收情况。
- 参数替换的实际值(显示
Notify: Parameter Substitution)。 - 关联函数捕获到的值(显示
Notify: Saving Parameter)。 - 事务的开始和结束时间。
- 任何警告(Warning)或错误(Error)信息。
- 回放快照(Replay Snapshot):与录制快照对比,直观地查看关键步骤的页面是否一致。
- 输出窗口(Output Window):查看脚本编译和运行的概要信息。
常见回放错误及排查思路:
| 错误现象 | 可能原因 | 排查步骤 |
|---|---|---|
Action.c(x): Error -26612: HTTP Status-Code=500 | 服务器内部错误。脚本逻辑可能正确,但服务器处理失败。 | 1. 检查参数化数据是否合法(如超长用户名)。 2. 检查关联是否成功,动态值是否被正确替换。 3. 查看服务器日志获取更详细错误信息。 |
Action.c(x): Error -27979: Requested form not found | 在提交表单时,VuGen找不到对应的表单。 | 1. 检查前一个请求的响应是否成功,页面是否正常加载。 2. 检查表单字段是否被正确关联或参数化。 3. 可能是动态表单名,需要关联 name属性。 |
Action.c(x): Error -35061: No match found for the requested parameter | 关联失败,未找到匹配的动态值。 | 1. 检查关联函数的左右边界(LB/RB)是否准确,是否包含了空格或换行。 2. 检查关联函数放置的位置是否正确(必须在产生该响应的请求之前)。 3. 使用“扩展日志”查看服务器返回的实际响应内容,重新确定边界。 |
事务失败(状态非PASS) | 事务内的操作未完成。 | 1. 检查事务的结束点lr_end_transaction是否被正确执行(例如,因为前面的错误导致脚本提前退出)。2. 检查事务的响应时间是否超长,触发了超时设置。 |
5.2 添加逻辑控制与错误处理
一个健壮的脚本不应在遇到非致命错误时就崩溃退出。我们需要添加逻辑判断和错误处理。
检查点(Text Check):用于验证页面内容是否符合预期,例如登录后页面是否包含“欢迎,XXX”字样。使用
web_reg_find或web_find函数。web_reg_find是注册型函数,效率更高,推荐使用。如果检查点失败,可以配合lr_fail_transaction将事务标记为失败,但脚本可以继续执行。web_reg_find(“Fail=NotFound”, “Search=Body”, “Text=欢迎您”, LAST); // 接下来是提交登录的请求 web_submit_form(...); // 如果上一步的响应体中找不到“欢迎您”,则`web_reg_find`会触发失败,事务状态会受影响。条件判断与流程控制:使用C语言的
if-else、switch、for、while等语句。例如,根据检查点结果决定后续流程:if (strstr(lr_eval_string(“{ResponseBody}”), “库存不足”) != NULL) { lr_output_message(“商品已售罄,执行备选方案。”); // 跳转到其他商品或结束迭代 return 0; } else { // 继续下单流程 web_submit_form(“place_order”); }错误处理与继续运行:在“运行时设置”(Run-time Settings)中,可以配置错误处理。建议在调试阶段设置为“在错误时暂停”,以便定位问题。在正式压测时,可以设置为“在错误时继续”,并指定“快照失败迭代”,这样单个Vuser的失败不会影响整个场景,并且我们能获取失败时的上下文信息用于分析。
6. 集成与高级应用考量
当单个脚本调试通过后,工作并未结束。我们需要考虑脚本在真实负载场景中的表现以及更复杂的测试需求。
6.1 在Controller中集成与验证
将脚本放入Controller设计场景时,需要进行最终验证:
- 参数文件路径:确保Controller能访问到脚本的参数文件(
.dat)。通常将脚本和dat文件夹一起打包上传到负载生成器是稳妥的做法。 - 运行时设置继承:Controller中的Vuser会继承VuGen脚本中的“运行时设置”,但可以在Controller层面进行覆盖(如统一设置超时时间、思考时间策略)。
- 运行一次Vuser:在场景设计界面,先使用“运行一次Vuser”功能,在目标负载生成器上执行单个脚本,确保环境依赖(如浏览器、网络、被测系统连通性)没有问题。
6.2 处理身份验证与加密通信
- HTTPS/SSL证书:对于HTTPS网站,录制时VuGen会自动处理证书。如果遇到证书错误,需要在“运行时设置”->“互联网协议”->“首选项”中,启用“忽略证书错误”或导入正确的证书。在负载生成器上也需要确保有相应的根证书信任。
- OAuth/Token认证:现代API常用OAuth 2.0等Token认证。脚本需要先调用一个认证接口(通常需要
client_id和client_secret),从响应中获取access_token,然后将此Token以Bearer形式添加到后续所有API请求的Header中(Authorization: Bearer {AccessToken})。这本质上是一个关联操作。 - 数字签名与加密参数:一些对安全性要求高的接口,参数可能被加密或需要数字签名。这无法通过简单的录制回放实现。你需要和开发人员确认加密/签名算法,然后在VuGen中使用C语言代码调用相应的加密库(如OpenSSL)或自己实现算法,动态生成参数值。这是脚本开发中的高级课题。
6.3 性能测试脚本的维护与版本管理
性能测试脚本不是一次性的。随着应用迭代,接口和前端可能发生变化,脚本也需要同步更新。
- 建立基线版本:为每个稳定的应用版本保存一份可用的脚本集。
- 版本控制:使用Git、SVN等工具对脚本进行版本管理,记录每次修改的原因(如:因登录接口变更更新关联规则)。
- 模块化与函数库:对于公共操作(如登录、退出),可以将其封装成自定义函数,放在
vuser_init或单独的头文件(.h)中,供多个Action调用。这极大地提升了脚本的可维护性和复用性。 - 定期回归:在每次应用发布前,用旧的脚本进行快速回放验证,确保核心业务流程的脚本依然有效。
脚本开发是性能测试的基石,一个粗糙的脚本会导致整个测试活动失去意义。从精准的协议选择开始,通过细致的录制、彻底地清理、合理地结构化,再注入参数化和关联的灵魂,最后经过严格的调试和强化,你才能得到一个真正能够模拟真实用户、产生可信负载的优质脚本。这个过程需要耐心、细心和对业务的深入理解。记住,你的脚本不是为你一个人运行,它将在压力下代表成千上万的虚拟用户与系统对话,它的每一行代码都影响着这场对话的真实性与测试结论的有效性。多花时间在脚本开发上,是在为整个性能测试项目的成功打下最牢固的基础。