资讯动态

庆典抽奖系统测试实战:Jmeter并发压测与Selenium自动化回归

发布时间:2026/10/2 9:04:12 来源:尧图企业网站定制
临近年会行政那边突然提了个需求做一个庆典抽奖助手。开发用Java两天把后端搭了起来奖品管理、抽奖规则、中奖记录、大屏展示一应俱全。作为测试我接到的任务是——在庆典现场几百号人同时按手机之前把这套Java写的抽奖助手用Jmeter压到位再用Selenium把核心UI流程自动化跑起来确保活动现场不出幺蛾子。这篇东西算是我的完整体验复盘写给那些同样要接手活动类系统测试的同学。抽奖这类系统的测试和普通业务系统不太一样它有一个特点现场不能重来。线上出bug可以回滚庆典现场出了问题几百人盯着大屏那是没法说我们修一下再抽的。所以测试的重点不是覆盖多少条用例而是把最可能在现场爆发的风险提前摁死。1. 抽奖系统上线前我要验证什么1.1 庆典场景的特殊性决定了测试边界接到这个测试任务后的第一反应不是急着写用例而是先弄清楚这个系统的使用场景到底长什么样。抽奖助手的使用高峰极其集中——庆典现场某个环节主持人说开始抽奖全场几百人同时点屏幕。这个场景和电商秒杀高度相似都是短时间内的流量尖峰但比秒杀更棘手秒杀失败可以提示已抢光抽奖如果没反应现场气氛直接冷掉。再去跟开发过了一遍功能清单确认了核心功能是这些管理员后台配置奖品奖品名称、数量、中奖概率、抽奖轮次参与用户通过手机页面参与抽奖输入手机号或工号验证身份后点击抽奖中奖记录实时落库前端大屏滚动展示中奖名单管理员可查看、导出中奖记录这里有个容易被忽视的点抽奖助手的用户身份验证不能做得太重。庆典现场用户就是扫个码进来如果还要注册、登录、填一堆资料流程就断了。所以实际上系统用的是手机号验证码的轻量校验这在后面的性能和自动化测试里都有影响——每个用户身份的生成方式要贴近真实场景。1.2 Java后端的功能点拆解与用例策略功能用例的拆解我按抽奖前、抽奖中、抽奖后三段来分比按模块拆更贴合现场逻辑。抽奖前要验证奖品配置的规则约束比如奖品数量不能为负、概率之和不能超过100%、同一轮次不能重复抽抽奖中要验证核心的抽奖算法包括概率命中是否准确、库存扣减是否精确、并发请求下会不会超发抽奖后要验证记录落库、大屏展示推送、管理员导出数据的准确性。用例策略上我优先把现场最容易翻车的场景排在了最前面。中等优先级的大概有二十多条低优先级的比如后台样式展示这些都排在了后面。后来事实证明这个排序是对的真正出问题的恰恰就是并发下的库存准确性和重复点击这两个现场高频场景。1.3 测试环境与数据准备的几个细节测试环境直接借用了预发布环境数据库是独立的MySQL实例配置和线上基本一致最后压测的数据才有效。数据准备这边做过一次返工原因是刚开始准备的测试用户全是同一批手机号段导致在按手机号做负载均衡的环节出现了单节点热点后来重新生成了多号段数据才解决。还有一个容易被忽略的细节是奖品数据的初始化。压测时如果奖品库存本来就设置得很大比如10万件那并发下的扣减压力根本体现不出来。我最终把核心压测奖品的库存设定在真实场景的量级——一等奖3件、二等奖10件、三等奖50件这才暴露了后面要讲的高并发超发问题。2. Jmeter压测从50并发加到500抽奖接口发生了什么2.1 接口梳理与Beanshell断言的取舍抽奖场景的接口链路不算复杂但压测不能只压抽奖那一个接口否则结果没有说服力。我梳理出四条核心链路身份验证、查询当前轮次与奖品信息、抽奖动作、查询中奖记录并展示。其中抽奖动作是最核心的接口也是最容易出现并发问题的点。在Jmeter里我用HTTP请求采样器来逐个模拟这些接口。身份验证这步需要动态参数直接从CSV文件里读入预先生成的手机号抽奖接口的请求体里有轮次ID和用户标识轮次ID从查询接口的响应里提取。这里就是Jmeter关联的常规操作用JSON提取器把上一个请求返回的轮次ID取出来传给抽奖请求。断言方面我用了两种。一种是响应断言直接校验HTTP状态码和返回的JSON里有没有success字段另一种是Beanshell断言。很多人对Beanshell断言有点怵其实它的价值在于能做复杂逻辑校验。比如抽奖接口返回的JSON里有一个prizeLevel字段我需要断言用户抽中了三等奖时返回的奖品名必须等于预设值这种跨字段的逻辑校验用响应断言很难写用Beanshell就很灵活String response new String(prev.getResponseData(), UTF-8); if (response.contains(\prizeLevel\:3)) { if (!response.contains(三等奖)) { Failure true; FailureMessage 奖品等级与奖品名称不匹配; } }这个断言在后面的问题排查里帮了大忙。压测跑到200并发以上时就是通过这个断言发现了一批响应虽然状态码是200、错误率为0但实际返回的奖品信息已经和配置不一致了——这是只看聚合报告根本发现不了的问题。2.2 阶梯加压的配置过程与参数解读压测不能一上来就500并发猛压我采用的是阶梯加压50并发、100并发、200并发、500并发每一档持续3分钟观察各项指标后再升下一档。这样能看到系统在什么量级开始出现性能拐点而不是直接压垮后拿到一堆无意义的数据。线程组的配置有几个关键参数值得说说。并发数即线程数Ramp-Up Period我设置为线程数除以10意思是50并发时5秒内启动全部线程500并发时50秒内启动完毕。这个参数不能设得太激进否则前几秒所有请求同时打到服务器测出来的响应时间会虚高不代表真实情况下用户逐渐进入页面的表现。监听器我加了聚合报告和查看结果树。聚合报告负责出整体指标结果树负责看单条请求的返回内容。这里提醒一句正式压测时不要开着结果树跑它能吃掉大量本地资源影响压测客户端本身的性能。我实际跑的时候是把结果树关掉的只看错误日志。2.3 聚合报告里的关键指标怎么看聚合报告跑完先看几个核心数字样本数、平均响应时间、吞吐量、错误率、P90/P95响应时间。我这里拿到的大致数据是这样并发数平均响应时间(ms)TPS错误率P95响应时间(ms)50864120%1451001286350%2202002018920%32050042311050.16%890这组数据出现了两个值得深挖的信号。第一个信号是500并发时错误率虽然只有0.16%看起来不高但结合Beanshell断言发现了更多逻辑层面的异常响应第二个信号是P95响应时间从200并发时的320ms跳到了500并发时的890ms接近三倍的增长说明系统在500并发已经明显吃紧。这时候我意识到一个问题单纯看TPS和响应时间是不够的。庆典现场一旦几百人同时抽奖奖品库存只有几十件真正要关注的不是接口响应多快而是中奖判断准不准。于是我把关注点转向了数据一致性重新组织了一轮针对奖品库存的压测验证这也直接引出了后面第四个章节里的关键问题。3. 用Selenium把庆典流程跑成自动化用例3.1 用例设计的核心思路不是点击抽奖那么简单Selenium自动化这部分我的目标很明确把管理员配置奖品、用户扫码进入页面、点击抽奖、查看中奖结果、大屏滚动展示这一整条核心链路自动跑起来保证每次版本迭代后不用靠人工去点一遍。技术上选用Selenium WebDriver配合Java测试框架用了TestNG便于做断言和用例依赖管理。用例设计上没有简单到打开页面、点一下按钮、看有没有弹窗就完事。我拆了8个核心用例覆盖两层视角管理员视角的奖品配置和记录导出用户视角的验证码登录、参与抽奖、结果查看。其中有一个用例专门验证抽奖按钮在点击后的置灰状态——如果按钮没有置灰就说明前端没有做防重复点击的处理这是个高风险信号。自动化用例跑在Chrome上driver版本必须和浏览器版本匹配这个老生常谈但每次都会有人踩。我本地Chrome升到新版后原来的ChromeDriver直接失效报错信息还特别有迷惑性提示什么Chrome failed to start实际上就是版本号对不上。3.2 元素定位与显式等待脚本稳定性的基础元素定位我优先用id和name实在没有才用XPath。抽奖页面是前后端分离的大部分按钮都有明确的id比如btn_lucky_draw、input_phone。XPath主要用在大屏展示区域的中奖名单条目定位上因为那些元素是动态渲染的类名经常带随机后缀。真正影响稳定性的还是等待策略。这里我吃过亏初期用隐式等待也就是driver.manage().timeouts().implicitlyWait()设置成10秒跑出来的结果就是脚本时好时坏。后来我系统性排查过隐式等待在元素存在但不可见、或者页面局部刷新时经常提前返回导致后续操作找不到元素。换成显式等待后通过率明显提升WebDriverWait wait new WebDriverWait(driver, Duration.ofSeconds(10)); WebElement drawButton wait.until(ExpectedConditions.elementToBeClickable(By.id(btn_lucky_draw))); drawButton.click();显式等待的核心是等条件成立而不是等时间过去elementToBeClickable会同时检查元素存在、可见、可用三点比单纯等元素出现要可靠得多。3.3 大屏中奖名单的滚动与可见性验证大屏展示是抽奖系统的门面中奖名单会持续滚动刷新这在自动化里是个特殊的验证点。普通元素直接findElement就能操作但大屏上不断滚动的列表元素可能在页面底部WebDriver默认不会自动滚动到不可见元素上进行某些操作。我用了JavaScript执行器来强制滚动先把目标中奖条目滚动到可视区域再判断其文本内容是否包含预期的中奖人姓名JavascriptExecutor js (JavascriptExecutor) driver; WebElement targetItem driver.findElement(By.xpath(//div[contains(class, award-list)]/div[contains(text(), 张伟)])); js.executeScript(arguments[0].scrollIntoView(true);, targetItem); boolean visible targetItem.isDisplayed();这个步骤在手工测试时很容易被忽略但自动化必须显式处理。此外因为列表是滚动的我增加了轮询机制每2秒检查一次目标条目是否出现最多等15秒。这模拟了用户在大屏上实际看到自己名字的过程也让用例不那么容易受滚动时序影响。3.4 第一次跑通全套用例的意外收获第一次跑通8条用例结果7过1挂挂掉的是重复点击抽奖按钮不产生重复中奖记录这个用例。测试脚本模拟了快速两次点击抽奖按钮断言中奖记录只有一条。结果系统生成了两条记录。这个发现后来和压测里暴露的幂等问题合并在一起排查了如果当时只做了手工测试这种瞬间双击的场景是很可能被漏掉的。另一件事也值得记录。跑通全套用例耗时大概4分钟其中大头是等待各种网络请求和动画效果。为了提升执行效率我在测试环境里把大屏滚动动画的间隔从5秒调到了1秒动画时长缩短会直接影响用例等待时间但不影响断言逻辑这样做是合理的取舍。4. 压测发现的三个问题排查链路完整复盘4.1 高并发下奖品超发从现象到根因的追溯这是整个测试过程中最值得说的问题。现象出现在200并发梯度奖品库存设置为50件压测结束后去数据库查中奖记录发现中奖人数是52。多出来2条。第一步先排除压测脚本的问题。检查了Jmeter的CSV数据源确认了50个并发用户没有重复手机号请求参数里的用户标识都是唯一的。第二步去查数据库日志和应用日志。发现在请求量最大的两秒内有两条抽奖请求同时读到库存为1然后同时执行了“库存减1、生成中奖记录”最终两条请求都判定为中奖但库存从1变成了-1。这就定位到了根因Java后端在库存扣减上用的是典型的先查询再扣减逻辑// 问题代码逻辑示意 int stock prizeService.getStock(prizeId); if (stock 0) { int newStock stock - 1; prizeService.updateStock(prizeId, newStock); winRecordService.createWinRecord(userId, prizeId); return 中奖; } return 未中奖;这个逻辑在单线程下没有问题但在并发场景下两个线程同时getStock都拿到1都判断大于0都去updateStock都创建了中奖记录就超发了。这是典型的读改写竞态条件。修复方案是开发那边改的用的是数据库乐观锁配合版本号机制UPDATE语句加上WHERE stock 0 AND version 上次读取的版本号同时用AtomicInteger在内存里做一层预扣减。修复后我用同样的压测场景复测数据验证显示中奖记录数与库存完全一致这个后面专门讲。4.2 同一用户重复点击抽奖的幂等缺口第二个问题其实Selenium自动化已经先发现了Jmeter压测又给了一次实锤。现象是快速点击两次抽奖按钮同一个用户生成了两条中奖记录。手工点击时反应没那么快一般发现不了但自动化脚本点击间隔可以精确控制在毫秒级一下就暴露了。排查下来根因有两个层面。前端层面抽奖按钮在点击后没有立即置灰也没有加请求中的状态锁用户在接口返回前可以继续点第二次后端层面抽奖接口没有做幂等处理没有校验这个用户在当前轮次是否已经抽过奖。修复方案是前后端一起改前端点击后立即disabled按钮同时显示抽奖中的loading状态后端在抽奖接口里加了幂等校验同一个用户ID加轮次ID作为唯一键第二次请求直接返回已参与本轮抽奖。4.3 Selenium脚本偶发找不到元素问题出在哪第三个问题属于自动化自身的稳定性问题但也间接反映了系统前端的一个隐患。压测结束后我重新跑Selenium回归10次执行里有2次在点击抽奖后等待结果弹窗这个环节失败报错是找不到弹窗元素。一开始怀疑是元素定位写错了检查XPath发现没问题。后来通过录制视频回放和一步一步调试发现点击抽奖按钮后页面要先发送请求等待后端返回成功后才弹出结果弹窗。网络正常的时候弹窗很快出现脚本能找到网络波动时弹窗延迟到500毫秒以上脚本在点击后立即去找弹窗元素自然找不到。这个问题的本质是异步流程的时序问题。修复方式就是前面讲到的显式等待把点击后查询弹窗改成了点击后等待弹窗元素可见最多等10秒。改完之后连续执行20次全部通过。这也让我养成一个习惯凡是涉及异步交互的步骤一律用显式等待绝不依赖隐式等待或固定sleep。5. 修复后的复测验证性能数据和回归结果对比5.1 修复方案的验证思路修复完成后的复测不是简单地把压测再跑一遍而是要针对修复点做定向验证。我设计了三个维度的验证第一并发准确性能否在高负载下保持。把一等奖、二等奖、三等奖库存分别设为实际数量跑500并发持续5分钟结束后逐一核对这些奖品的中奖记录数量是否精确等于库存配置。第二幂等逻辑是否生效。用Jmeter模拟同一个手机号在极短时间内连续发送3次抽奖请求断言结果里只有第一次返回中奖或未中奖的业务结果后两次必须返回重复参与。第三原来的48项功能用例是否受到影响。这里直接跑Selenium的回归用例套件确认修复没有破坏正常流程。5.2 复测数据对比与稳定性表现复测结果对比修复前后关键指标改善很明显指标修复前(500并发)修复后(500并发)变化平均响应时间423ms356ms-67msP95响应时间890ms620ms-270ms错误率0.16%0%清零中奖记录精确度多出2条与库存一致修复这边的响应时间改善其实不是修复并发问题的直接效果而是开发在改动过程中顺手把查询抽奖结果时的SQL加了索引顺手优化了一把。但数据一致性这个核心指标的修复是实实在在的三轮奖品库存分别设置为3件、10件、50件500并发下跑完中奖记录分别是3、10、50一条不多一条不少。Selenium回归那边8条核心用例连续执行5轮共40次执行只有一次因为测试环境网络闪断导致失败重试后通过。整体稳定性从最初的偶发失败提升到了可以放心作为发布前回归检查的水平。6. 一点测试心得庆典抽奖系统到底该怎么测抽奖助手这个项目做完我最大的体会是活动类系统测试的核心不是功能覆盖率而是风险预判。庆典现场不可能给你留修复时间所以你要提前把所有现场可能爆炸的场景找到并拆掉引信。具体到工具选型上Jmeter和Selenium这个组合搭得很顺。Jmeter负责把并发压力打上去验证系统在流量尖峰下扛不扛得住、数据准不准Selenium负责把核心流程固化下来每次改动后能快速回归。前者解决人多会不会挂后者解决改了会不会坏正好是活动系统最关心的两个问题。如果你是第一次接手这类系统有几个建议可以拿走直接用压测时一定要把库存数据设置成真实活动的量级用一万件库存去压50件的活动压不出超发问题断言不能只看HTTP状态码和响应体里的success字段要用Beanshell这种能写逻辑的断言去校验业务字段间的关联Selenium脚本凡是涉及异步加载、弹窗、滚动刷新的环节务必用显式等待抽奖这类系统的幂等校验和前端按钮置灰缺一不可这两道防线少了任何一道现场都可能出现一个人中两次奖的尴尬局面最后就是回归验证。修复后一定要回到真实场景下复测而不是只看修复点本身这个项目之后我把这套Selenium用例沉淀成了活动类系统的基础回归套件后来公司办周年庆又组织了一次抽奖这次没有重新开发直接复用了这套系统和测试用例压测跑一遍、回归跑一遍上线后现场非常顺利。对我来说这就是测试这件事最有价值的时刻——你提前做的那点工作让几百人在现场多了一份安心。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑