上个月我们组负责的运营活动里有个“庆典抽奖助手”模块从开发到上线时间卡得很紧。功能开发完测试这边压力全给到我一方面要用Selenium把Web端的抽奖主流程、奖品展示、中奖记录这些页面功能跑通回归另一方面还得用Jmeter把抽奖接口在高并发下的表现压出来别上线当天被用户点垮。整个项目是个标准的Java后端服务前端页面走浏览器给我的测试组合就是“Java Jmeter Selenium”三件套。这篇文章就把当时的测试报告思路和执行过程复盘一下重点说Selenium脚本怎么写才稳、Jmeter脚本怎么设计才有参考价值以及我们在压测里抓出来的几个真实Bug。如果你也是负责活动类项目测试的同行或者正打算给一个Java Web项目搭一套“自动化回归 压力测试”的最小可用方案这篇文章应该能给你一些可以直接抄走的思路。1. 项目背景与测试方案设计1.1 抽奖助手的业务链路与测试目标这个庆典抽奖助手本质是一个Web抽奖活动页前端页面在浏览器里展示奖品轮播图、抽奖按钮、剩余抽奖次数和中奖记录后端用Java提供抽奖接口。用户点击按钮后页面请求后端接口完成“校验资格、执行抽奖算法、扣减库存、写入中奖记录、返回结果”这一串动作前端再根据返回结果弹窗显示“中奖”或“未中奖”。测试目标分两条线。功能线要保证主流程在页面上一路点下来没有问题包括登录状态校验、抽奖次数扣减、奖品库存变化、中奖弹窗文案、中奖记录列表刷新等。性能线要验证抽奖接口在大量用户同时点击时响应时间还在可接受范围内且不能出现奖品超发、数据错乱这类严重问题。我前期的重点其实是梳理清楚哪些环节是高风险点。抽奖和普通查询接口不一样它同时涉及库存扣减和中奖记录写入属于典型的“并发写”场景。测试用例设计必须覆盖“同一用户重复点击”和“多用户同时抽同一个奖品”两种情况否则功能测完了压测一跑就原形毕露。1.2 功能与性能双线并行的测试策略我在这次项目里没有把功能测试和性能测试做成先后关系而是两条线并行。原因很简单开发一边在改Bug我这边可以一边用Selenium把稳定的页面主流程自动化跑起来等接口稳定了再用Jmeter开始压测。如果功能完全通过后再启动压测时间根本不够。Selenium这部分主要做页面级回归价值在于保证“用户真实操作的链路”不出问题。Jmeter这部分直接打接口价值在于通过高并发找出代码层面的并发隐患。两条线一个是“前端视角”一个是“后端视角”恰好互补。还有一点值得说Jmeter压测发现的并发问题修复后用Selenium再从页面上完整回归一遍能确认问题在用户侧已经完全感知不到。这个“先压测发现问题再功能回归验证修复”的闭环是这次测试最有价值的部分。1.3 环境与工具选型版本搭配工具选型这块没有太多纠结。Web自动化我用的是Selenium 4.x配合Java编写脚本原因是我们组Java技术栈为主脚本语言统一成Java后续维护交接都方便。Jmeter用的是当时最新的5.x稳定版直接从官网下载解压就能跑没有折腾安装这件事。浏览器用的Chrome配套的ChromeDriver版本必须严格对应浏览器版本。这里踩过一个小坑Chrome自动升级后ChromeDriver没跟着更新脚本一跑就报SessionNotCreatedException。后来我把Driver的版本管理交给WebDriverManager这个库自动匹配浏览器版本这个坑就再没出现过。测试环境单独部署了一套数据库是独立的否则功能测试和性能测试的脏数据会污染正式数据。Jmeter压测的时候服务端日志也同步打开了慢查询记录方便定位性能瓶颈。2. Selenium功能自动化用例设计与实操细节2.1 用PO模式落地页面对象页面自动化最忌讳把元素定位和业务操作全堆在一个类里那写出来的脚本又长又没法维护。我用的是Page Object模式也就是PO模式一个页面一个类把页面上的元素定位和操作封装成方法测试用例只关注业务逻辑不直接接触元素。以东页页面为例我建了三个页面对象登录页、活动主页、中奖记录弹窗。活动主页里封装了clickDrawButton()、getPrizeName()、getRemainTimes()等方法中奖记录弹窗封装了waitForRecordList()和getFirstRecordPrize()。测试用例里写的就是“打开页面、点击抽奖、等待弹窗、断言奖品文案”这种可读性很强的代码。PO模式最大的好处是页面元素一变只需要改动页面对象里的定位方式测试用例不用动。这次活动页中途改过一次按钮文案和样式我只需要更新按钮的定位表达式二十几条用例全部继续跑通这个维护成本的优势非常明显。2.2 元素定位的顽固问题与解决实例活动页的元素定位是这次Selenium实操里最磨人的部分。页面用了大量图片按钮和动态样式抽奖按钮的文案是图片形式没法用文字定位我最后改用CSS类名加相对位置组合定位优先用id或者data-test属性这类稳定标识。比较典型的一个问题是“剩余抽奖次数”这个文本它是一个动态数字每次抽完就减一。用普通的By.id能定位到元素但断言文本时会发现前一次的数字还在。解决方法是使用WebDriverWait配合ExpectedConditions.textToBe等待元素文本变成期望值再执行下一步这样就不会因为页面刷新慢导致断言失败。此外抽奖结果弹窗的出现时机也不固定有时快有时慢。我统一封装了一个waitForElementVisible(element, 10)方法所有关键操作前都等待元素可见超时时间10秒。后来用这个方法替代了零散的Thread.sleep脚本稳定性提升非常明显。2.3 网页横向滑动与左右滚动可见的处理活动中奖记录那块是个横向滚动的奖品展示区域奖品卡片在视觉上超出可视区Selenium默认只能操作可视区的元素这就需要处理横向滑动。我试过用Actions类模拟鼠标拖拽但效果不稳定。后来改用JavaScript直接操作滚动通过document.querySelector定位横向滚动容器然后用scrollBy方法把滚动条往右移。等目标奖品卡片完全进入可视区之后再继续点击操作。具体代码思路是这样先获取滚动容器的当前位置判断目标元素的左边距是否超出容器可视宽度如果超出就执行横向滚动。滚动完成之后我总会加一个短暂等待确认元素位置稳定后再点击。这个处理方式在我的实测里很稳几分钟就能把一组奖品卡片全部遍历一遍。2.4 断言、重试与稳定性的平衡功能自动化跑得稳不稳断言设计占一半。抽奖结果断言我不光看弹窗文案还会查数据库里的中奖记录这就涉及Selenium和JDBC结合。中奖后页面显示“恭喜获得XX奖品”数据库里也插入了一条对应记录两边都检查才算用例通过。另一个提高稳定性的手段是失败重试。我用TestNG的IRetryAnalyzer接口实现了失败用例自动重试重试两次仍然失败才标记为失败。因为抽奖场景受网络波动影响较大一次偶发性超时不应直接判定功能故障。最后提一个很重要的实践体会自动化用例的数量不是越多越好关键是维护成本和执行稳定性。我这次核心用例只维护了二十几条但每条都覆盖了完整主流程和关键断言执行时间不到20分钟晚上提交代码后自动跑一遍足够保证活动页回归不出大问题。3. Jmeter性能压测脚本写法和执行记录3.1 压测场景与线程组设计Jmeter压测抽奖接口前我得先想清楚模拟什么场景。活动上线后最典型的压力是整点开抢瞬间大量用户同时点抽奖按钮。所以线程组设计成固定并发数、压满一定时间的方式而不是一次性把所有线程全发出去。我设置了350个并发用户持续时间10分钟配合阶梯线程组插件分三档逐步加压先100并发预热1分钟再250并发压3分钟最后350并发压满剩余时间。目的是模拟用户逐渐涌入的过程同时观察系统在压力递增下响应时间的变化趋势。抽奖接口是POST请求参数包含用户ID、活动ID和抽奖来源。用户ID我通过CSV文件参数化每次请求取不同值避免所有并发请求都打在同一用户上这更贴近真实情况也能测出系统对多用户并发抽奖的处理能力。3.2 BeanShell断言处理抽奖响应压测不只是看接口返回200就完事业务结果的正确性更重要。抽奖接口返回体里有个businessCode字段0代表中奖1代表未中奖同时带prizeType表示奖品类型。要在Jmeter里校验这些业务字段就得用断言。Jmeter自带的JSON断言能校验字段值但遇到需要逻辑判断的场景我还是直接用BeanShell断言脚本。比如我要断言“中奖时返回里必须带奖品ID且概率权重对应的奖品类型不是最低档”这段逻辑JSON断言写不了BeanShell脚本却很简单。BeanShell断言里我还会写一行Failurefalse来重置状态防止前面的断言标记影响当前请求的结果。这个细节很重要否则一次失败之后后面所有请求都会被标记为失败压测报告就废了。3.3 压测执行、监控与结果数据读取Jmeter压测有两种跑法Windows图形界面和Linux命令行。活动生产环境是Linux服务器我顺手在Linux上也跑了一遍命令行压测这样压力机的网络环境和生产环境更接近。命令行执行很直接jmeter -n -t lottery_draw.jmx -l result.jtl -e -o html_report这行命令的意思是后台运行测试计划把结果写到result.jtl日志文件最后生成HTML可视化报告到html_report目录。整个压测过程中不需要盯着界面压完直接看报告里的聚合数据就行。压测过程中我还遇到过一个实际需求在Linux压测期间想实时看到接口返回内容验证数据格式有没有异常但命令行模式下看不到请求响应。解决方法是加一个“Simple Data Writer”监听器单独把响应数据写入文件命令行跑的时候定时查看这个文件。这个方法很好用既不干扰压测线程又能抓取响应现场。3.4 压测数据如何反哺Java后端优化压测报告出来第一件事是看三个指标TPS每秒事务数、平均响应时间、错误率。我把测试数据整理成表格发现350并发时抽奖接口平均响应时间230毫秒P99是850毫秒TPS峰值到1200错误率0.02%整体在可接受范围但距离“高性能”还有优化空间。其中有个数据直接暴露了问题并发从250升到350时平均响应时间从180毫秒跳到230毫秒说明系统开始出现排队。配合Jmeter插件查看活跃线程数会发现数据库连接池在高并发下出现等待这直接告诉开发“连接池配置偏小需要调大”。这些压测数据我不是丢给开发就不管了。我在报告里标注了具体哪个环节是瓶颈包括SQL执行时间、连接池等待时间、Redis访问耗时开发根据这些定位到了一次慢SQL查询最后通过增加索引和优化查询条件把P99降到了500毫秒以内。这是压测最有价值的部分数据能直接指导代码优化方向。4. 问题排查实录并发Bug修复验证4.1 经典超发问题库存扣减的原子性压测跑完第一轮我检查数据库里奖品库存和中奖记录的匹配情况发现了一个严重问题库存只有100件的蓝牙耳机中奖记录却有120条。这就是典型的奖品超发当时的处理逻辑一定是“先查库存是否大于0再执行扣减”这个“查-扣”之间不是原子操作并发请求一到全部通过检查库存就扣超了。这个问题的修复方式是代码层面改造把扣减库存的SQL直接写成带条件更新例如UPDATE prize SET stock stock - 1 WHERE id ? AND stock 0这样“检查库存并扣减”一步完成。执行后如果影响行数为0就说明库存已经不足返回未中奖从根源上杜绝了超发。我修复后立刻重新压了一轮1000并发下100件库存的奖品中奖记录稳定在100条。这个验证结果让开发很信服也让测试报告有了实实在在的结论。4.2 数据一致性验证从功能到压测的闭环关于数据一致性这次项目里还有一个点值得单独说。开发修完超发Bug后我不仅在Jmeter里重新压测确认了库存数据正确还用Selenium从页面端完整走了一遍真实抽奖流程点击抽奖按钮、看到中奖弹窗、刷新中奖记录页、去数据库比对记录。两条线都验证通过才敢在报告里写“数据一致性验证通过”。我始终认为性能和功能在“数据一致性”这个点上必须互相印证。压测确认了并发情况下没有超发功能回归确认了用户在页面上看到的和数据库里记录的完全一致两者结合才能说明这个Bug真正修好了而不只是某个环节的数据对了。4.3 压测中的典型失败案例复盘这次压测中有一个失败案例印象很深压测进行到第7分钟突然出现一批请求响应超时错误率从0.02%飙升到15%。当时我第一反应是怀疑数据库连接但通过Jmeter查看响应内容发现报错信息指向的是Redis连接超时而不是数据库。排查后发现是活动页的奖品轮播数据被缓存到了Redis压测期间同一时间大量请求查询缓存连接数超过Redis配置的上限导致连接等待排队超时。开发调整了Redis连接池参数同时给缓存查询加了本地内存缓存做二级缓存问题就消失了。复盘这个案例我想特别强调一点压测时一定要同时关注中间件的状态只看接口响应时间是远远不够的。Redis、数据库、消息队列任何一个环节被压垮都会以“接口变慢”的形式反映出来但真正的瓶颈在更下层。Jmeter配合服务端的中间件监控才能准确定位问题。4.4 常见问题速查表我整理了一份这次测试过程中遇到的常见问题速查表方便大家直接对号入座问题现象可能原因处理建议抽奖库存超发查询库存和扣减库存非原子操作改为带条件的update语句或使用Redis事务脚本接口响应时间随并发明显变长数据库连接池不足或慢SQL调大连接池定位慢SQL并优化索引Selenium点击按钮没反应元素被遮盖或未进入可点击状态使用WebDriverWait等待可点击状态或JS点击横向滚动区域元素不可见元素超出可视区未触发滚动用JavaScript的scrollBy方法滚动容器Jmeter断言全部失败断言脚本未重置Failure状态在BeanShell脚本里设置Failurefalse压测日志乱码响应编码不是UTF-8在Jmeter配置里设置响应编码或查看原始响应字节这个速查表我每次项目结束都会持续补充时间长了就形成自己的问题库下次遇到类似现象能快速定位方向。5. 写给大家的实操建议项目结束后我对这套“Java Jmeter Selenium”组合有了更实际的体会。先说Selenium如果活动页面经常改版一定要把元素定位和业务逻辑分层维护不然脚本改动成本会拖垮整个回归节奏。再说Jmeter设计压测场景之前必须先想清楚业务的高峰形态不是随便填一个并发数就开跑的这次如果不是按阶梯加压也很难观察到响应时间在压力递增时的明显变化。另一个让我印象很深的是性能测试发现的问题最终还是要回到功能自动化里做一次完整验证。用户不关心你压测时TPS是多少用户关心的是页面上能不能正常抽奖、中奖记录对不对。测试报告的价值恰恰是把这两件事对应起来。最后分享一个我在后续项目中一直用的小技巧测试报告不要只写结论要把环境参数、压测模型、问题定位过程、修复前后数据对比全部保存下来。这些原始记录在项目上线后出现线上问题时是排查的第一手资料比任何口头总结都管用。