☰
抽奖系统测试复盘:从功能用例到并发与库存一致性验证
2026/10/11 14:25:16 网站建设 项目流程

“开心大转盘”交到手里的时候,我一度以为这活儿很轻松。转盘抽奖嘛,按钮、转一圈、掉个奖品,完事。可真开始写用例,才发现这个“小功能”横跨前端动画、后端并发、库存扣减、账户流水、反作弊,几乎把一套活动系统该有的坑全占了。这篇文章不是正式的项目总结ppt,而是我这次从头到尾测下来的完整复盘,包括我是怎么圈定测试范围、怎么设计用例、怎么把概率和并发验证明白,以及那几个让我加班的线上级别bug到底是怎么挖出来的。如果你接下来要测类似的活动转盘、抽奖小程序或者秒杀类功能,这份思路可以直接抄。

1. 测试目标与范围界定

1.1 这个“转盘”是什么,问题藏在哪里

“开心大转盘”是一个H5活动页面,也嵌在微信小程序里。用户每天登录可以获得一次抽奖机会,完成分享、签到等任务还能追加次数。点击“开始抽奖”后,转盘动画转一圈,最终弹窗展示中奖结果,奖品包括实物礼品、优惠券、积分,还有“谢谢参与”。

看起来只有三个动作:登录、点击、领奖。但真正拆解后,至少涉及四个子系统:前端展示系统负责动画和交互,活动后端负责抽奖逻辑和次数控制,奖品中心负责库存扣减和发放,用户账户系统负责记录流水。测试的目标不只是“转盘能不能转”,而是回答几个关键问题:抽奖结果公不公平、次数会不会被乱扣、库存会不会超发、高并发下系统扛不扛得住、异常网络下会不会重复发奖。

这个项目最容易被低估的地方在于,用户感知的“转盘结果”和后端真正执行的中奖逻辑不是一回事。前端动画只是一个播放器,真正决定中什么奖的是后端接口返回的中奖结果。如果两边的角度计算、奖品顺序、状态同步出现任何偏差,用户就会觉得“被套路了”。所以我在测试设计里把前后端一致性当成最高优先级,而不是简单点点点。

1.2 测试范围与不测试范围

这次测试周期排了五个工作日。覆盖四块:功能测试、兼容性测试、性能测试、安全风控测试。功能测试是主线,重点验证抽奖流程、次数控制、库存扣减、中奖记录和奖品发放。兼容性主要覆盖微信小程序、iOS Safari、安卓微信内置浏览器和PC端Chrome,各挑主流真机跑一遍。性能集中在抽奖接口和活动页首屏加载,因为运营那边预估活动峰值会有数千人同时在线。安全风控重点看越权调用、刷接口、篡改金额和次数绕过。

同时也明确了不测的部分:奖品物流供应链、财务对账、后台运营管理系统的完整功能,这些由其他团队负责,我只做接口联调验证。这个边界很重要,不然测试范围无限扩大,最后哪个都测不深。我在测试报告里专门写了一段“不在本次范围内”,避免后续扯皮。

1.3 环境搭建与数据准备

测试环境不能用线上真实配置,这是抽奖类项目最基础的纪律。我在测试环境单独建了一套奖品数据,库存数量故意设置得很小,比如实物奖品库存设为3,优惠券库存设为5,方便做并发超发的验证。中奖概率也分成两组:一组是真实运营配置的概率,用来做随机性统计验证;另一组是把一等奖概率临时调成100%,快速走通发奖全流程。

账号方面,准备了普通用户、未登录用户、黑名单用户、以及两个绑定同一手机号的账号,用来验证用户唯一性和防刷逻辑。数据库独立,所有修改都有备份。这里有个小教训:测试环境一定不要跟开发联调环境混用,否则你在这边压测,开发在那边改代码,一个重启就能把你所有测试数据清掉,浪费时间不说,还容易误判缺陷。

2. 核心测试设计与用例拆解

2.1 功能骨架:先理清前端和后端的对等关系

写用例前,我先画了一张功能逻辑图,把“转盘”拆成了几个相对独立又前后咬合的模块:页面展示、登录鉴权、抽奖机会获取、抽奖动作触发、后端中奖判定、动画播放、结果弹窗、奖品落库、我的奖品列表。每个模块都有明确的输入输出,测试时互不干扰。

举例来说,“用户点击抽奖按钮”这个动作,在前端会经历一个防重复点击的置灰过程,同时向后端发送抽奖请求。但前端的置灰只是体验层面的保护,如果用户通过脚本直接调用接口,这个保护完全不生效。所以我在用例里单独设计了“绕过前端按钮,直接请求接口连续调用5次”的场景,验证后端是否真的做了次数校验。

前端动画和后端结果的一致性,是转盘项目特有的测试点。我要求开发在接口返回里带上中奖奖品ID和奖品名称,然后在每一次测试中记录屏幕上指针最终停下的区域,再从后端日志里拿到返回的奖品ID,两边比对。这种用例听起来简单,但实际上我能在一个下午抓到三个不一致的问题,有些是起始角度算错了,有些是奖品顺序跟前端配置不匹配。

2.2 抽奖次数和并发防重的测试设计

次数控制是最容易出低级bug的环节。规则是:每天首次登录送一次,完成一次分享送一次,每个用户每天最多累计三次。测试用例覆盖了几个维度:正常消耗、任务增加、跨天重置、重复提交、并发消耗。

正常消耗很容易测,麻烦的是并发。我设计了一个场景:同一个用户同时发起两个抽奖请求,期望结果是一个成功一个被拒绝。第一次测试就出现了bug:两个请求都返回成功,是因为后端先扣次数再调抽奖接口,两个请求同时读到剩余次数为1,都以为自己是最后一个。修复办法是把“校验次数、扣减次数、执行抽奖”放到同一个事务里,顺序串行。

防重复提交也要分两层看。前端在动画播放期间把按钮置灰,这是第一道防线。但是用户在弱网环境下连续点了两次,前端置灰还没生效时第二个请求已经发出去了。所以后端必须有幂等机制,比如通过userId+活动日生成唯一抽奖流水号,重复请求直接返回已有结果。这个机制我在测试里重点验证了:用两个线程同时发送两个完全相同的请求,断言结果只有一条抽奖流水。

2.3 随机性与中奖概率验证

随机性测试不能只验证“能转出不同奖品”,还得验证“多次结果符合配置概率”。我把真实概率配置列出:一等奖1%,二等奖3%,三等奖10%,四等奖20%,其余为谢谢参与,另有两个活动专属奖各8%。概率加起来是100%,零头都做了处理。

验证方法是循环调用抽奖接口10000次,统计每个奖项的出现次数,然后用置信区间判断偏差是否合理。比如一等奖期望出现100次,对于1%概率,n=10000时,标准差大约是9.95,所以合理范围大致落在80到120次之间。我实际跑出来的结果是一等奖105次,二等奖312次,三等奖998次,整体在可接受范围内。

除了统计验证,还要关注随机数本身的质量。后端早期用Math.random(),在单机高并发下问题不大,但在多线程环境下会出现重复随机数,因为同一个种子在多线程竞争时可能生成相同的序列。我在压测时发现,同一毫秒内的并发请求,返回的中奖结果排布存在明显的重复模式。后来开发把随机数生成改成线程安全的ThreadLocalRandom,重复模式才消失。

2.4 前端动画与异常状态一致性

动画测试要从用户体感出发,不能只看功能正常。我列了几个场景:转盘转动时长是否合理、指针停止位置是否准确、点击后快速离开页面再回来状态是否恢复、网络断开时按钮状态是否卡死、动画结束后中奖弹窗是否正常弹出。

有一个低概率问题让我印象很深:在iOS微信浏览器里,动画播放过程中锁屏再解锁,页面渲染会暂停,但接口已经返回了中奖结果。用户重新打开页面时,只看到转盘停在一个随意位置,没有中奖弹窗,但是去“我的奖品”里又能看到奖品。这个体验非常割裂,前端后来改成在页面恢复可见时主动拉取一次抽奖状态,如果发现存在未展示的中奖记录就自动补弹窗。

另外,快速点击和网络超时也很容易出现“请求发了两次”的情况。我测试时用弱网工具模拟了超时场景:前端提交抽奖请求后超时,用户以为没点上又点了一次,结果后端收到两个请求。如果后端没有幂等处理,用户会被扣两次次数,还可能抽到两个奖。这类问题通过日志和接口层唯一ID方案解决,单纯靠前端防抖是堵不住的。

3. 执行实录与关键结果分析

3.1 功能用例执行统计

功能用例一共82条,第一轮执行通过71条,失败11条。失败问题集中在中奖状态同步、次数扣减、库存超发和动画偏差。我按模块做了个简单的统计表。

功能模块用例数通过失败通过率
页面展示与登录12120100%
抽奖机会获取1613381.25%
抽奖动作与后端判定2016480%
结果弹窗与状态同步1411378.57%
奖品发放与流水记录2019195%

最浪费时间的一个缺陷是“中奖后不弹窗”。从界面看像前端样式问题,排查很久发现是后端返回的奖品对象在某种情况下缺少awardType字段,前端拿到后走了默认分支,直接把弹窗吞掉了。这个问题的本质是前后端接口字段约定不完整,前端没有对缺失字段做兜底。测试经验是:接口返回的结构化字段,一定要对“缺失字段”和“空字符串”做用例覆盖,不要只测正常值。

3.2 兼容性测试:真机矩阵与典型问题

兼容性测试用了一批真机,包括iPhone 12、iPhone 14、小米10、华为P50、荣耀V30,配合Android的Chrome和微信内置浏览器,再加上PC端Chrome浏览器。核心看三个点:动画流畅度、按钮交互、数据状态恢复。

安卓低端机上转盘动画明显卡顿,帧率只有十几帧。原因不是JS逻辑,而是页面用了大量box-shadow和渐变导致GPU合成压力过大。优化方案是把转盘的每个奖品区域提前切成固定尺寸的背景图,减少实时绘制。优化后帧率提升到稳定50帧以上,这个数据我是通过开发者工具的FPS面板记录下来的。

另一个兼容性问题更隐蔽:部分安卓手机的微信内置浏览器在开启隐私模式后,localStorage写入会失败。前端把“当天已抽奖次数”缓存到了本地,结果隐私模式下读取失败,用户明明还有抽奖次数,却总是显示“今日已抽完”。修复方向是让所有次数判断以后端返回为准,前端缓存只用来做展示加速,不再作为唯一数据源。

3.3 性能压测:从500QPS到1500QPS

性能测试的目标很明确:抽奖接口至少支持500 QPS,接口响应P95小于500ms,同时保证库存不超发。压测工具用的JMeter,先灌入一批模拟用户数据,然后按梯度加压。

第一轮200并发持续5分钟,接口平均响应时间150ms,P95在280ms左右,数据库状态正常。第二轮直接升到800并发,数据库连接池先被打满,大量操作超时,P95飙到2秒以上。查日志发现抽奖接口内部除了更新库存,还要写流水表,两条SQL放在同一个事务里,每个请求都占用数据库连接,而连接池初始配置只有30,完全不够用。

优化方案是拆分事务:核心事务只做库存扣减和发奖记录,流水表改成异步批量写入。调整后重新压测,800并发下P95稳定在350ms左右,继续加压到1500 QPS也没出现超时或超发。不过压测还暴露了另一个问题:服务器CPU在300并发时单核打满,定位是随机数生成存在锁竞争,换成ThreadLocalRandom才解决。

性能测试报告里我不只写“通过”,还保留了每次压测的响应时间曲线和错误率数据。这样开发后续做版本迭代时,可以直接对比数据判断性能是变好还是变差。

3.4 安全风控:越权调用与刷量拦截

安全测试放在最后半天,重点验证几个问题:未登录用户能不能直接调抽奖接口、普通用户能不能绕过活动白名单、能不能把奖品金额参数改大、能不能用脚本高频刷抽奖次数。

实测发现了两个必须修复的问题。第一,抽奖接口虽然校验了登录态,但没有校验用户是否在活动白名单内,普通用户通过手动拼接参数进入活动页,可以正常调用抽奖接口。第二,后端更新每日次数时用了前端传入的用户ID,而不是从会话里取当前登录用户ID,导致传别人ID也可以触发抽奖,虽然不会给别人扣次数,但接口语义不对,存在越权风险。

修复方案不复杂:用户身份统一从session获取,活动资格和每日次数都由后端会话实时校验,前端传入的任何用户ID都不作为信任依据。这类问题在功能测试里很难被发现,必须专门写一层“恶意请求”用例。我在测试报告里把这两个问题标为严重等级,开发改了两天才完全收口。

4. 典型缺陷与排查技巧实录

4.1 指针停在分割线上的概率争议

测试过程中运营反馈,有用户抱怨转盘指针“总是停在两个奖品分区的边界上”,怀疑系统故意不给人中奖。这个反馈听起来像玄学,但我排查后确认是真实bug。

原因是奖品分区的角度定义不一致。前端用CSS旋转角度计算指针位置,后端按奖品数量等分扇形角度,两个角度之间存在大约3到5度的偏差。结果就是视觉上指针明明停在三等奖区域,后端判断却落到相邻的谢谢参与区域。看起来像是“中奖概率被调低了”,实际是“视觉区域跟实际判定区域错位”。

定位方法不复杂:写个脚本连续跑100次抽奖,每次记录接口返回的奖品ID,同时截图记录指针停止位置,然后人工比对。比对后发现二等奖的视觉区域明显比实际判定区域宽,某些边界情况下用户看到的和系统判定的完全对不上。最后设计同学调整了奖品分区背景图,让视觉边界和判定角度严格对齐。这个bug给所有做转盘类活动的人提了个醒:前端视觉和后端逻辑必须共用一份角度配置,不能各写各的。

4.2 库存超发的并发血缘

库存超发是最严重的线上事故类型。测试环境用3个库存的奖品做并发验证,5个用户同时抽奖,结果成功发出去5份。这个bug的直接原因很典型:代码先执行“select库存”,判断大于0,再执行“update库存减1”,两个操作之间存在时间差。并发请求同时读到库存还有剩余,就都通过了检查。

修正方案是改成带条件的原子更新,核心就是这一条SQL:

UPDATE prize_stock SET stock = stock - 1 WHERE prize_id = ? AND stock > 0;

用数据库影响行数判断是否扣减成功,如果返回0说明库存不足,直接返回“未中奖”。这个改动看起来简单,但很多项目在开发初期库存充足,根本走不到“库存不足”这个分支,等到线上活动流量一大,问题就集中爆发。测试时应该主动把库存调到1、3、5这种小数值,再叠加并发请求,否则真正的问题根本测不出来。

4.3 中奖记录与发奖记录对不上

还有一个让人挠头的问题:用户抽中了奖,但在“我的奖品”里看不到。排查时我先对比了两个数据表的同一时间点记录量,发现抽奖流水表比发奖记录表多了不少数据。问题出在发奖逻辑上:部分奖品类型的中奖结果先写入流水表,随后再异步写入发奖记录表,异步任务在某类奖品上出现了消费延迟,而且没有失败重试机制。

从系统层面看,“中奖”和“到账”本质上是两个独立的流程。抽到奖只是产生了一条中奖凭证,发奖是另一个服务根据凭证推送奖励。两者之间必须明确一致性等级:实物和积分这类核心奖,必须保证最终一致,失败要有补偿。修复时给发奖增加了消息队列,消费失败进入死信队列,运营后台可以手动补发。

这类问题的排查思路是:先看日志有没有报错,再看两张表的数量差,最后检查异步任务是否积压。不要一上来就怀疑前端弹窗逻辑,那只会让你绕远路。

4.4 日志和复现:把偶现bug变成可定位问题

抽奖项目最怕偶现问题,因为抽奖动作是随机事件,网络环境复杂,很难稳定复现。我的做法是在测试环境强制要求开发在抽奖接口的入口和出口都打印请求参数和返回结果,并带上全局traceId。前端发起请求时生成traceId,后端透传,这样无论问题出现在前端还是后端,都能通过同一个traceId把整个调用链串起来。

复现并发问题时,靠人工点击基本不可能。我写了一个简单的并发脚本,同时开20个线程去跑抽奖接口,每跑完一轮都记录数据库库存和流水表,比对是否有超发、漏发、重复扣次数。偶现问题往往需要跑几百次才稳定出现,有了脚本就可以挂机跑一晚上,第二天看日志分析。

日志检查和自动化压测其实是绝配。压测时一旦发现错误率上升,先用traceId去日志平台查这条请求在后端每个环节的耗时和返回值,基本能定位是数据库问题还是代码逻辑问题。没有这些基础排查工具,遇到“偶尔失败”的问题只能靠猜,效率极低。

5. 复盘总结与改进清单

5.1 测试报告应该落在哪个位置

测试报告不是把用例结果堆上去就完事。我在报告开头放了一段“测试结论”,直接写清楚“当前版本是否建议上线”:结果是“有条件通过”,三个严重缺陷修复后必须回归验证,两个建议优化项可放到下个迭代。项目组拿到报告第一眼就知道当前状态,不用从头翻到尾猜结论。

报告正文按顺序包括:测试范围、环境说明、用例统计、缺陷列表、风险项、概率验证数据。缺陷列表里每个严重bug都带上截图和复现步骤,方便开发直接定位。风险项写的是“暂不阻塞上线但需要运营关注”的问题,比如分享任务在部分安卓机型上领取次数延迟。这样的报告才真正为决策服务,而不是为了应付归档。

写完报告后,我还单独给运营写了一段“上线前必查清单”,包括:确认线上奖品库存充足、中奖概率配置导入正确、白名单用户名单同步、消息队列服务正常。产品经理看了都说比之前的测试报告有用。

5.2 给开发、产品和测试自己的建议

给开发侧的建议,优先级最高的是:库存扣减原子化、用户身份从session获取、前端后端统一奖品角度配置。这三个是这次项目里真正造成线上事故和用户投诉的根因,只要改掉,很多问题都能从根源上避免。

给产品侧的建议是:活动规则里必须明确“抽奖结果以系统记录为准”,因为用户感知的视觉落点存在误差,如果没有这个说明,一旦出现边界情况就很难对用户解释。同时建议在活动规则里写清楚中奖概率的展示方式,符合平台合规要求。

测试侧我自己复盘了几条:抽奖类项目必须准备一套“小库存”测试数据,必须保留并发测试的基础脚本,必须预留探索性测试时间。这三件事看起来都很普通,但每次都会被排期挤掉,而恰恰是它们最有机会发现真实问题。

5.3 后续还能怎么扩展

测试完成后,我补做了一个简化版线上监控方案:抽奖接口错误率超过阈值自动报警,每天定时统计奖品发放数量和库存扣减数量,如果两者偏差超过设定比例就通知到群里。这个监控不依赖复杂平台,用定时任务加数据库查询就能实现。

我还在想一个更好的思路:把随机概率验证做成线上巡检,每天拉取前一天的抽奖结果做统计,自动判断中奖分布是否偏离配置概率。这样运营每次调整完概率配置,就不再靠人工跑测试来验证,而是有持续的数据监控。这次“开心大转盘”项目给了我一个很直观的感受:转盘类功能看似简单,但它的质量风险全部都藏在并发、一致性和状态边界里,只有把测试设计从“功能正确”往“数据守恒”方向去测,才能真正把问题挡在上线之前。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询