最近重新把黑马点评这个项目翻出来,不是为了补业务功能,而是想试试用 Apifox 把它的一套真实接口做成自动化测试场景,然后直接上多并发线程去跑。说实话,多数人跑通项目后顶多用 Apifox 单独调试几个接口,看看返回 JSON 就完了。但项目里真正容易出问题的地方,恰恰藏在并发场景里:秒杀券库存扣减、点赞次数重复计算、登录态互相挤掉。Apifox 自动化测试模块的价值,就是把这些场景固化成用例,再通过并发执行模式模拟几十个甚至上百个用户同时操作。这篇文章记录的就是从零到一的完整过程,适合正在学黑马点评、想补一点测试实战经验的 Java 学习者,也适合刚接触接口自动化、想让测试用例跑出并发效果的同学。
1. 为什么把黑马点评作为接口自动化与并发测试的练兵场
1.1 黑马点评的业务接口全景:从登录到秒杀
黑马点评是个非常典型的单体后端服务,Spring Boot 做主体,Redis 扛缓存和分布式场景,MySQL 存业务数据。它的接口设计很规整,不是那种纯教学用的假接口,而是真正有业务含义的地址。你把项目启动起来后,能接触到的接口大致分成下面几块:
| 模块 | 典型接口 | 并发关注点 |
|---|---|---|
| 登录注册 | /user/code、/user/login | 登录态生成与互斥 |
| 商铺模块 | /shop-type/list、/shop/list、/shop/{id} | 查询缓存与 DB 压力 |
| 点评模块 | /blog/hot、/blog、/blog/{id} | 数据写入与列表一致性 |
| 点赞模块 | /blog/like/{id} | 重复点赞、计数准确性 |
| 签到模块 | /user/sign、/user/score | 重复签到、幂等性 |
| 秒杀模块 | /voucher-order/seckill/{id} | 库存超卖、一人一单 |
这几个模块里,登录和秒杀是最适合做并发验证的,因为黑马点评的登录态存储在 Redis 中,秒杀又涉及库存扣减,这两块都属于典型的"高并发一上来就露馅"的业务。点评和点赞虽然看起来轻,但并发场景下也会出现重复写入、计数错乱的问题。我把这些接口连成一条完整用户操作链路,再用 Apifox 跑自动化场景,本质上就是把"用户刚刚做了什么"翻译成测试用例,让机器替我去重复操作。
1.2 Apifox在"自动化+并发"场景中的角色定位
很多人对 Apifox 的印象停留在"替代 Postman 的调试工具",这其实低估了它。Apifox 从设计上就把接口调试、文档管理、自动化测试、数据 Mock 做在了一起,尤其适合我这种不想为了一个项目同时装三四套工具的懒人。
在自动化测试这边,Apifox 支持创建测试场景,场景里可以按顺序编排多个接口步骤,步骤之间能互相提取参数,还能写断言、跑前置/后置脚本。这些能力组合起来,一套完整的业务链路就能固化成可重复执行的用例。更关键的是,Apifox 的测试场景支持并发执行模式,可以把一份用户行为脚本复制成 N 份并行跑。用这个模式做中小规模的多用户并发模拟,比手动开十几个终端、刷新几十次页面要靠谱得多。
当然也要说清楚,Apifox 的主场是接口自动化和功能回归,不是专业的压测工具。真要去测极限 QPS、观察服务器资源曲线,JMeter 或专门的性能测试平台更合适。但作为一个学习项目,用 Apifox 做 10 个、50 个、100 个线程的并发验证,看业务逻辑会不会出问题,完全够用。这个定位就是我们后面所有操作的大前提。
2. 先把基础打牢:环境初始化与关键接口用例
2.1 项目、数据库和Redis的准备
在写任何测试用例之前,得先把黑马点评项目本身跑起来。这个步骤看起来简单,但很多人会在这里埋坑。我建议你在本地搞一套独立的 MySQL 和 Redis,不要用生产环境或者别人共享的数据库,原因很简单:并发测试会往数据库里写大量脏数据,评论可能重复几十条,秒杀订单可能刷好几页,这些数据清理起来很麻烦。
项目启动要注意几件事:
- 修改
application.yml里的数据库连接、Redis 连接,确保本机服务能正常启动; - 短信验证码接口
/user/code在项目里通常只是把验证码打到日志里,你要打开控制台看日志,确认能拿到验证码; - 准备好至少 20 个测试手机号,格式随便,比如
18800000001到18800000020,后面做并发模拟和用户隔离会用到; - 提前确认秒杀券有足够的库存,我一般会在测试前直接改数据库,把某张券的库存字段设置为 50,避免测试跑到一半库存不足导致结果失真。
项目起来之后,先手动在 Apifox 里调一下登录接口,确认环境通、响应结构正常,再往下做用例。
2.2 环境变量与登录态提取:别让token成为绊脚石
Apifox 的环境变量机制是整套自动化场景的地基。我习惯新建一个"黑马点评-测试环境",把下面几个变量维护进去:
| 变量名 | 示例值 | 说明 |
|---|---|---|
base_url | http://localhost:8080 | 服务根地址 |
phone | 18800000001 | 默认测试手机号 |
code | 123456 | 验证码,根据日志填写 |
user_token | 空 | 登录后回填的 token |
黑马点评的登录流程分成两步。第一步请求/user/code,传手机号;第二步请求/user/login,传手机号和验证码,登录成功之后,接口返回的data字段是个字符串 token,后续所有业务接口都要在请求头里带Authorization: {{user_token}}。
问题在于,每次自动化测试执行登录时,token 都是新生成的,不能手填。解决办法是在登录接口后置脚本里把它取出来,再设置到环境变量里。Apifox 的后置脚本兼容 Postman 的pm对象,也支持apt对象,我的写法是这样的:
// 登录接口的后置脚本 const body = pm.response.json(); if (body.code === 1) { const token = body.data; apt.setEnvironmentVariable("user_token", token); } else { console.log("登录失败:" + JSON.stringify(body)); }这里有个细节:黑马点评的会话超时策略是按 Redis 里的 key 过期时间计算的,如果 token 失效,后续接口会返回统一的未登录错误码。所以我在每个涉及用户态的业务接口断言里,都加了一条"如果 code 等于未登录错误码,整个场景直接标记失败",避免靠肉眼去翻几千条执行记录。
2.3 接口间参数传递与断言设置
黑马点评这种业务链路的接口之间有明显的依赖关系。比如你要给某个店铺发表点评,得先拿到店铺 ID;你要测试秒杀,得先知道某张券的 ID。这些值如果写死在用例里,换环境就得全部重配,一点都不自动化。
我一般这样处理:上游接口返回响应后,用后置脚本提取关键 ID,写入环境变量。举个例子,查询商铺列表接口/shop/list返回的是一个店铺数组,我要取第一个店铺的 ID:
// /shop/list 的后置脚本 const shopList = pm.response.json().data; if (shopList && shopList.length > 0) { apt.setEnvironmentVariable("shop_id", shopList[0].id); apt.setEnvironmentVariable("shop_name", shopList[0].name); }下游接口直接在请求参数里写{{shop_id}}。这样整个链路就变成了"取 ID -> 传参 -> 业务操作"的流水线。
断言部分,我不仅仅检查 HTTP 状态码。黑马点评这类业务系统,HTTP 200 不代表业务成功,比如秒杀接口可能返回 200,但业务 code 是 0,提示"库存不足"。所以我的断言原则是三层:
- 第一层:断言 HTTP 状态码是 200;
- 第二层:断言响应体里的
code字段等于 1; - 第三层:根据业务补断言,比如秒杀成功之后去查订单,订单数据必须存在。
Apifox 界面可以直接添加断言,也可以用脚本写。我更喜欢脚本,因为可以一次性把多个条件组合起来:
pm.response.to.have.status(200); const res = pm.response.json(); pm.expect(res.code).to.eql(1); pm.expect(pm.response.responseTime).to.be.below(800);响应时间断言不是硬性要求,但对并发测试很友好,它能帮我在早期发现服务响应变慢的趋势。
3. 编排业务链路:从"测单个接口"到"跑一条完整点评流程"
3.1 场景编排的正确顺序
单个接口用例准备得差不多之后,就该把它们组合成测试场景了。Apifox 的测试场景概念非常适合这种业务编排:一个场景就是一个虚拟用户会执行的完整操作流程。
我给黑马点评设计的核心场景叫"黑马点评-核心链路-用户操作",步骤顺序如下:
- 调用
/user/code获取验证码; - 调用
/user/login登录,后置脚本提取 token; - 调用
/shop/list获取店铺列表,提取店铺 ID; - 调用
/shop/{id}查看店铺详情; - 调用
/blog发表一条点评,提取点评 ID; - 调用
/blog/like/{id}给自己刚发的点评点赞; - 调用
/user/sign签到; - 查询签到状态,确认签到成功。
这个顺序完全模拟真实用户的路径:先进来、浏览、发内容、做互动、打卡签到。场景步骤之间是严格依赖的,所以执行方式必须是顺序执行。这里要注意一个容易犯的错:不要在业务依赖还没满足的时候,为了追求"看起来快"而把步骤设置成并行。比如点赞接口依赖点评接口返回的 ID,点评没发完就点赞,大概率拿不到正确参数。
另外黑马点评里还有一类接口适合做并行步骤,比如"查看热门点评/blog/hot"和"查询店铺类型/shop-type/list",它们之间没有数据依赖。Apifox 场景支持步骤并行执行,我一般把这种互不依赖的查询步骤并行化,能让整个场景跑得快一些,也更接近真实使用中用户同时刷多个页面的情况。
3.2 数据驱动:用多个账号模拟不同用户
做并发测试最怕什么?最怕所有线程都用同一个账号,那测出来的结果根本不能代表"多个用户同时操作"的真实情况。所以我在场景里引入了数据驱动。
Apifox 的测试场景支持测试数据集功能,我准备好一张账号参数表:
| 参数名 | 数据集1 | 数据集2 | 数据集3 |
|---|---|---|---|
| phone | 18800000001 | 18800000002 | 18800000003 |
| code | 123456 | 123456 | 123456 |
| blog_content | 并发测试点评01 | 并发测试点评02 | 并发测试点评03 |
场景里的/user/code和/user/login请求参数改成引用数据集字段,Apifox 在执行时会自动把每一组数据独立传入,每个虚拟用户就等于携带了不同的身份和不同的业务数据。
这里有个很实际的坑:黑马点评的验证码接口设计得比较简单,多次发送验证码给同一个手机号,Redis 里只保留最后一次。多线程并发执行时,如果所有线程共用一个验证码值,有的线程可能拿到过期的 code,导致登录失败。用数据集给不同线程分配不同手机号,再配合项目日志里拿到的验证码,可以最大程度避免这种串号问题。
数据驱动还有个额外好处:并发测试产生的点评内容、签到记录都归属不同用户,后续清理数据时,我能按手机号前缀去定位,不会误删别人或者分不清哪些是测试数据。
3.3 跑通单线程链路是并发测试的前提
这个标题看起来像废话,但很多人就是会跳过。我在刚接触自动化测试时吃过亏:场景里的小接口单独调都是好的,排成链路后跑一次就有两三个断言失败。如果这种情况下直接开 100 个并发,失败日志瞬间几千条,根本没法定位是业务问题、参数问题还是断言写错了。
所以我的规矩是:任何并发测试之前,先把场景用单用户模式跑通,连续跑 5 次,成功率必须是 100%。注意是连续跑 5 次,不是跑一次看通过就完事。因为有些问题是概率性的,比如 token 提取偶尔失败、响应偶尔超时,单跑一次可能碰巧没触发。
单线程跑通后,还要做一件锦上添花的事:把场景里的请求间隔稍微调整一下。Apifox 场景步骤之间可以设置等待时间,我一般设为 200 毫秒到 500 毫秒,模拟真实用户的操作间隔。这个值在并发模式下也保留,因为真实用户不会是机器一样零延迟地连续点按钮。如果完全不设置间隔,压出来的不是业务瓶颈,而是 TCP 连接建立和请求序列化的极限,参考价值会打折。
4. 多并发线程测试实操:参数、步骤与常见误区
4.1 并发线程数怎么定:不是越大越好
很多人一听并发测试,第一反应是直接上 1000 个线程。但在黑马点评这种学习项目上,这样做没有任何意义,反而会把自己搞糊涂。原因有两个:
第一,Apifox 本身不是专业性能压测工具,它在本地并发执行时,线程是受你本机 CPU、内存、文件描述符限制的。一台普通笔记本可能跑到两三百线程时,Apifox 客户端自己已经开始卡顿,这时候测出来的数据根本不能反映服务的真实能力。
第二,黑马点评项目默认配置下,Tomcat 线程池、数据库连接池都不大。本地开发环境连接数通常只有 10 到 20 个,你一上来跑 500 个并发,服务端直接大量连接超时,日志全是异常,业务逻辑的问题反而看不出来。
我推荐从低到高逐步加压,一般分三组:
| 组别 | 并发线程数 | 循环次数 | 目的 |
|---|---|---|---|
| 第一组 | 10 | 1 | 冒烟验证,确保场景跑通 |
| 第二组 | 30 | 1 | 观察轻微并发下的表现 |
| 第三组 | 50 | 3 | 稳定性和业务正确性验证 |
如果你有更高的追求,可以继续加到 100、200,但每加一组,都要看看服务端日志有没有连接池打满、有没有大量的超时重试。记住一句话:并发测试的目的是暴露问题和验证修复,不是为了制造一个刺眼的数字发朋友圈。
4.2 Apifox中配置并发的完整步骤
Apifox 做并发执行,核心操作是把之前编排好的那个用户链路场景作为脚本,然后在运行配置里把执行方式从"单用户串行"切换成"多用户并发"。我按实际操作顺序整理如下:
- 在 Apifox 左侧进入"测试场景"模块,找到"黑马点评-核心链路-用户操作"场景;
- 点击"运行配置",先确认测试数据集已经挂上,确保每组数据都被使用;
- 把"执行模式"切换为并发执行(部分版本的入口在场景运行界面的高级设置里,名称可能叫"多线程执行"或"并发用户数",不同版本界面有差异,找不到就挨个点击运行配置的选项看说明);
- 设置并发用户数为 10,循环次数为 1;
- 确认每个请求的"导入变量"里都有独立 token,避免所有线程共用同一个登录态;
- 点击"开始运行",切换到实时执行页面,观察各个步骤的状态。
如果你使用的 Apifox 版本暂时没有并发执行入口,也有一个土办法:把同一个测试场景复制成多份,分别绑定不同的环境配置,然后在电脑上开多个窗口同时点运行。这个办法能达到"多线程并行发起请求"的近似效果,缺点是统计报告比较分散,需要自己手动汇总。
执行过程中我习惯开着黑马点评的日志终端,同时用命令查看 Redis 的键数量,以及 MySQL 的连接数变化。Apifox 界面只能看到外部表现,真正的服务端压力、缓存命中情况还是要靠后端日志和数据库状态说话。
4.3 最容易踩的坑:共享登录态与数据冲突
并发测试跑起来后,我遇到的第一个大坑就是"假并发":明明设置了 30 个并发用户,结果所有请求用的都是同一个 token,服务端把这些请求全部识别成同一个用户。表现出来就是点赞接口只记录了一次,签到也只成功一次,并发效果等于零。
原因很简单:登录步骤的后置脚本把 token 写进了全局环境变量,多个线程之间共享了这同一个变量。正确做法是确保每个线程的数据集里都有独立的账号和独立 token,并发执行时各个线程各取各的值。如果 Apifox 客户端仍出现共享变量覆盖的问题,可以把 token 的存储作用域从环境变量改成"步骤内变量"或者"测试场景内变量",让它的生命周期绑定到单个虚拟用户上,这样就隔离了。
第二个常见坑是数据冲突。比如 30 个线程同时发表点评,点评内容字段如果都是同一个字符串,数据库里会出现 30 条一模一样的内容。这本身能证明"并发写入正常",但它会给断言带来干扰。我习惯在数据集的blog_content字段里加入序号,保证各线程内容唯一。
第三个坑是秒杀券库存数量设置不对。黑马点评的秒杀逻辑里,库存预检查、扣减、订单创建是核心链路。如果测试前库存只有 5 件,却开 30 个线程,那么 25 个线程必然失败,这个结果虽然真实,但会让断言分析和性能指标的计算变得混乱。我一般会把库存设置为并发数的一半或等于并发数,方便观察成功订单的分布。
5. 并发结果解读与业务问题定位
5.1 看懂报告:成功率、响应时间、TPS哪一项先看
Apifox 跑完一轮并发后,会给出执行总览,包括总请求数、成功数、失败数、平均响应时间、最大响应时间等。我要强调一个观点:第一眼看的不是平均响应时间,而是成功率。
成功率如果掉下去了,说明业务逻辑已经出现错误响应,这时候平均响应时间再好看都没有意义。举个例子,数据库连接池被打满后,请求可能在池边排队 3 秒,最后返回超时。平均响应时间被拖累是结果,连接池耗尽才是原因。
我的分析顺序是这样的:
| 观察顺序 | 指标 | 关注什么 |
|---|---|---|
| 1 | 成功率 | 有没有业务错误、超时、异常响应 |
| 2 | 错误类型 | 是超时、连接拒绝,还是业务 code 不为 1 |
| 3 | 平均响应时间 / 最大响应时间 | 是否出现明显恶化 |
| 4 | 请求吞吐量 | 单位时间处理能力,仅作参考 |
| 5 | 服务端日志 | 有没有异常堆栈、锁等待、SQL 慢查询 |
在并发场景下,有几个错误值得重点截图:Connection refused、Read timed out、业务 code 返回 0 但 HTTP 是 200。这些分别对应服务端拒绝连接、服务端处理过慢、业务规则主动拦截。三种错误的修法完全不同,把它们混在"失败"这一个标签里看,定位效率会很低。
5.2 典型业务问题:秒杀超卖、重复点赞、签到幂等
当并发测试真的跑出问题,整个过程的收获才是最大的。我在黑马点评里反复验证过几个经典问题,这里挑三个最典型的讲。
第一个是秒杀超卖。把库存设置为 10,并发线程开到 50,跑完后去查库存,发现变成了负数,或者成功的订单数大于库存数。这个问题的根因一般是扣减库存的逻辑不具备原子性:先查询库存,判断库存大于 0,再执行UPDATE扣减。两个线程同时查到库存是 1,都通过了判断,各自扣减,库存就变成 -1 了。用 Apifox 复现这个问题的标准做法是:场景里串联"登录 -> 秒杀下单 -> 查询订单",断言秒杀接口返回成功的同时,再单独跑一条 SQL(或在脚本里查库存接口)验证库存没有变负。Apifox 支持在场景步骤之间调用数据库操作,可以直接查 MySQL,这个功能很适合做这类数据一致性断言。
第二个是重复点赞。同一篇文章,同一用户快速点了两次赞,数据库里可能出现两条点赞记录,或者点赞计数被加了两次。排查这类问题,不能只看接口是否返回成功,要在断言里检查"点赞的唯一约束"是否生效。运行完并发场景后,再去查点赞表,看同一用户对同一点评是否存在多条记录。
第三个是签到幂等。黑马点评的签到功能通常用 Redis 的 Set 或 BitMap 存储签到记录,并发下如果判断"是否已签到"和"写入签到记录"这两个操作之间没有加锁,同一个用户可能被记录签到多次,导致签到统计异常。
这三个问题的共同点是:接口从表面看都返回成功,但数据库或 Redis 里的数据经不起推敲。这也是我一直强调"自动化测试要加上业务断言"的原因——不能只断言接口不报错,还要断言数据状态符合预期。
5.3 从"复现问题"到"验证修复"的闭环
并发测试发现问题只是第一步,更重要的是形成"复现 -> 定位 -> 修复 -> 回归"的闭环。我习惯把每一次并发执行的所有请求记录导出,包括请求头、请求体、响应体,这样定位问题时不用去猜。
比如秒杀超卖问题,我的复现路径是这样的:
- 用 Apifox 单独跑一次"秒杀场景",确认单用户下功能正常;
- 开 50 个并发,循环 3 次,记录失败率和库存变化;
- 打开项目日志,搜索秒杀相关的异常信息,找到具体是哪一行代码报错或者哪里出现了竞态条件;
- 修复后,原封不动地重新执行同一场景、同一并发数、同一库存数量;
- 对比修复前后的结果表。
对比表是个很好用的工具,我经常把结果整理成下面这样的表格,贴在项目笔记里:
| 验证项 | 并发数 | 修复前结果 | 修复后结果 |
|---|---|---|---|
| 秒杀下单成功率 | 50 | 60%(30个失败) | 100% |
| 库存最终值 | 50 | -20(超卖) | 0(恰好耗尽) |
| 订单总数 | 50 | 70条 | 50条 |
| 平均下单响应时间 | 50 | 650ms | 230ms |
这张表一贴出来,问题到底解没解决,一眼就能看出来,不需要写一堆文字。
6. 收尾建议:这套方案的上限与扩展路线
6.1 一个适合学习的并发测试模式
如果你只是想快速体验黑马点评的并发测试,我建议你固定一套标准场景,不要每次都重头搭。我现在长期保留两个场景:
- 场景 A:"用户核心链路",包含登录、浏览、发点评、点赞、签到,主要用于功能回归和并发冒烟;
- 场景 B:"秒杀并发验证",只包含登录、秒杀下单、订单查询,并把数据库库存检查的断言写进去,专门用来验证秒杀的并发正确性。
每次改完项目代码,先跑一遍场景 A 的单用户模式,确认没破坏原有功能;再跑一遍场景 B 的 50 线程并发模式,确认并发正确性没问题。这套流程下来,比手动开页面操作至少快十倍,而且每次的结果都可追溯。
顺便说一个我踩过的坑:并发测试产生的脏数据如果不清理,第二次跑的时候会影响断言。比如第一次跑产生了 50 条重复点评,第二次再做数据校验时,查询结果会混入上一轮的脏数据。我的做法是专门写一个clean_test_data.sql脚本,跑完测试就按测试手机号前缀清空相关表,保证环境干净可复用。
6.2 想往更专业方向走的三个建议
如果这套实践让你觉得"接口自动化 + 并发测试"有意思,想往更专业的方向发展,我有三个很现实的建议。
第一,把 Apifox 换成或补充 JMeter 做真正意义上的压力测试。Apifox 的强项是业务场景自动化和接口正确性验证,但专业的性能回归需要更细粒度的线程组、分布式压测、服务器资源监控。学过 Apifox 之后再学 JMeter,很多概念是相通的,曲线很平滑。
第二,不要满足于"接口返回成功"。把断言从"检查 code 等于 1"升级到"检查业务数据的变化是否符合预期"。比如秒杀断言库存数量,点赞断言数据库点赞记录,签到断言 Redis 的 BitMap 状态。这才是自动化测试的精髓:它保护的不仅是接口本身,更是背后的业务规则。
第三,把测试场景接入持续集成流程。你可以把 Apifox 的测试场景挂在项目的流水线里,每次代码提交后自动执行一次自动化回归,执行结果直接回传给负责人。这样并发测试就不再是"偶尔想起来跑一下"的事,而是成为质量保障的一部分。
最后分享一点个人经验:刚开始做并发测试时,遇到服务崩溃不必慌,黑马点评这类学习项目本来就是在"压一压、挂一挂、修一修"的过程中成长的。关键是每次测试都要留下报告、日志、前后对比,这些才是你真正积累下来的东西,比那一瞬间看到的高并发数字值钱得多。