摘要:本文以开源电商系统 ShopXO 为被测对象,围绕功能测试、Selenium UI 自动化测试与 JMeter 性能测试三条主线展开,完整记录测试目标、用例设计、工程结构、执行结果与瓶颈分析,并给出可落地的优化建议与实战 tips。
| 项目名称 | ShopXO 电商系统自动化测试项目 | 版本号 | 1.0-SNAPSHOT |
|---|---|---|---|
| 发布类型 | 测试负责人 | ||
| 测试完成日期 | 联系方式 | — |
项目背景
项目背景与意义
- 随着电商行业的持续发展,电商系统的稳定性直接关系到交易成败与用户体验。相较于博客、管理系统类项目,电商系统拥有更长的业务链路(注册 → 登录 → 浏览 → 加购 → 结算)和更复杂的角色分工(前台用户 + 后台管理员),是练习 Web 自动化测试的绝佳场景。
- 本项目以开源电商系统ShopXO为被测对象,使用Java + Selenium WebDriver从零搭建了一套覆盖前台用户视角与后台管理员视角的 UI 自动化测试工程,作为测试开发方向的求职项目。通过这个项目,我系统实践了显式等待、iframe 切换、多标签页切换、弹层交互、断言与自动截图等自动化核心技能,并锻炼了测试用例设计与测试报告编写能力。
项目概述
本项目针对部署在云服务器上的 ShopXO 电商系统进行测试,系统分为前台商城与后台管理两端。测试工程采用 Maven 管理依赖,以common.Utils提供统一的驱动创建与截图能力,按“页面即类”的方式组织测试用例,最终由runTests入口按业务顺序串联执行。
已实现的主要功能(被测系统)
- 前台:用户注册、用户登录/注销、商品搜索、商品详情浏览、加入购物车、购物车删除、订单结算、用户中心、收货地址管理
- 后台:管理员登录、未登录访问拦截、商品列表查询、商品价格修改、订单管理列表、用户管理与信息编辑
当前系统存在的不足(测试角度)
- 后台管理系统重度使用 iframe 嵌套布局,元素定位前必须先切换上下文
- 商品详情在新标签页打开,涉及窗口句柄切换
- 加入购物车为弹层交互而非页面跳转,需对弹层确认按钮单独处理
- 购物车记录 ID 为会话动态生成,无法用固定 ID 定位
项目的系统功能说明
1. 前台商城
- 注册:校验账号/密码格式,格式错误或为空时给出前端提示,全部正确则注册成功
- 登录:账号密码匹配后跳转回商城首页,顶部导航出现“退出”入口
- 商品搜索:输入关键词点击搜索,列表 AJAX 异步刷新出筛选结果
- 商品详情:点击商品在新标签页打开详情页,展示价格、库存、商品介绍
- 购物车:详情页点击“加入购物车”弹出数量选择弹层,确认后加购成功;购物车页可删除记录
- 结算:购物车勾选商品后进入结算页,确认订单信息
- 用户中心:登录后可访问个人中心,支持在 iframe 表单中新增收货地址
2. 后台管理
- 管理员登录:独立的 admin 登录入口,登录成功进入后台首页(订单总量等数据看板)
- 访问拦截:未登录直接访问后台地址会被强制重定向回登录页
- 商品维护:商品列表页(位于
goods/index.html的 iframe 中)支持按条件搜索、修改商品价格 - 订单管理:查看订单列表
- 用户管理:查看用户列表、编辑用户信息
测试目标
- 目标:核心业务流程(注册 → 登录 → 搜索 → 详情 → 加购 → 结算 → 用户中心)与后台四大管理模块测试通过率达到 100%
测试项目相关信息
- 被测前台地址:
http://118.89.173.170/ - 被测后台地址:
http://118.89.173.170/adminvfkegj.php?s=admin/logininfo.html - 测试工程:
d:\WEB\demo\autoTest2(Maven 工程,jar 打包) - 浏览器:Edge(msedgedriver 本地驱动)
测试安排
| 模块 | 子模块 | 开发 | 测试 | 工时 | 进度 | 备注 |
|---|---|---|---|---|---|---|
| 前台-账户 | 页面完整性、注册、登录 | 1d | 测试完成 | |||
| 前台-交易链路 | 搜索、详情、加购、结算 | 1d | 测试完成 | 依赖登录态,串联执行 | ||
| 前台-用户中心 | 访问中心、新增收货地址 | 0.5d | 测试完成 | 表单在 iframe 内 | ||
| 后台-登录 | 登录成功/失败/未登录拦截 | 0.5d | 测试完成 | |||
| 后台-商品 | 列表搜索、修改价格 | 0.5d | 测试完成 | 列表在 iframe 内 | ||
| 后台-订单/用户 | 订单列表、用户列表、编辑用户 | 0.5d | 测试完成 |
测试分类
(1) 功能测试
对前台注册、登录、搜索、加购、结算、用户中心与后台登录、商品、订单、用户管理进行手工功能测试,覆盖正常流与主要异常流(错误密码、空表单、未登录访问)。
- 功能测试结果:测试用例 100% 通过
(2) 自动化测试
① 编写 Web 测试用例
按照“一个页面/模块一个测试类”的原则,将用例划分为Front(前台)与Admin(后台)两个包:
src/main/java ├── common │ └── Utils.java // 驱动创建、显式等待、自动截图、驱动回收 ├── Front │ ├── FrontPage.java // 前台页面完整性(未登录/已登录) │ ├── Registe.java // 注册:成功 / 账号格式错误 / 密码格式错误 / 全空 │ ├── Login.java // 登录:成功 / 密码错误 │ ├── Browse.java // 搜索商品 / 查看商品详情(新标签页) │ ├── Cart.java // 加入购物车(弹层确认)/ 删除购物车 │ ├── Checkout.java // 结算 │ └── UserCenter.java // 访问用户中心 / 新增收货地址(iframe 表单) ├── Admin │ ├── Auth.java // 管理员登录 / 登录失败 / 未登录拦截 │ ├── Goods.java // 商品列表搜索 / 修改商品价格 │ ├── Order.java // 订单管理列表 │ └── User.java // 用户管理列表 / 编辑用户信息 └── runTests.java // 用例串联执行入口② 创建空项目
(1) 配置准备:添加需要的 pom.xml 依赖
<dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.51.0</version> </dependency> <dependency> <groupId>io.github.bonigarcia</groupId> <artifactId>webdrivermanager</artifactId> <version>6.4.0</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>(2) 创建驱动对象 driver
- 为了方便测试所有页面,创建一个全局 driver 对象供所有测试用例使用,配合隐式等待与显式等待(
WebDriverWait超时 20s)。 - 使用本地已下载的 msedgedriver,跳过 WebDriverManager 联网解析,避免网络不通时卡死在
setup()。
(3) 测试用例的划分
- 按照“前台 / 后台 → 页面 / 模块”二级分类,每个页面对应一个 Java 文件,页面下所有用例统一管理。
- 所有测试类继承
common.Utils,构造函数传入页面 URL 即自动完成驱动创建与页面访问。
自动化测试工具准备
package common; import org.apache.commons.io.FileUtils; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.edge.EdgeDriver; import org.openqa.selenium.edge.EdgeOptions; import org.openqa.selenium.support.ui.WebDriverWait; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.time.Duration; public class Utils { public static WebDriver driver = null; public static WebDriverWait wait = null; // 记录详情页 / 购物车 / 后台首页的当前 URL,供后续用例衔接 public static String currentURL = null; public static String cartcurrentURL = null; public static String AuthcurrentURL = null; public Utils(String url) { driver = createDriver(); driver.get(url); wait = new WebDriverWait(driver, Duration.ofSeconds(20)); } public static WebDriver createDriver() { if (driver == null) { // 使用本地已下载的驱动,跳过 WebDriverManager 联网解析(网络不通时会卡死在 setup()) System.setProperty("webdriver.edge.driver", "C:\\Users\\28320\\.cache\\selenium\\msedgedriver\\win64\\155.0.4283.62\\msedgedriver.exe"); EdgeOptions options = new EdgeOptions(); // 允许访问所有链接 options.addArguments("--remote-allow-origins=*"); driver = new EdgeDriver(options); // 隐式等待 driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); } return driver; } // 屏幕截图:src/main/java/images/日期/时-分-秒-毫秒.png public void ScreenShot(String str) throws IOException { SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat sim2 = new SimpleDateFormat("HH-mm-ss-SS"); String dirTime = sim1.format(System.currentTimeMillis()); String fileTime = sim2.format(System.currentTimeMillis()); String fileName = "src/main/java/images/" + dirTime + "/" + fileTime + ".png"; File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); FileUtils.copyFile(srcFile, new File(fileName)); } // 退出驱动,回收 msedgedriver.exe 进程,防止僵尸进程残留导致后续运行卡死 public static void quitDriver() { if (driver != null) { driver.quit(); driver = null; } } }- 后续测试用例通过继承这个工具类创建测试对象并访问 URL,用例执行中自动落盘截图,截图按“日期目录 + 毫秒级时间戳”命名,避免同名覆盖。
测试用例 tests 的编写
选择器策略:所有页面元素均使用浏览器真实采集的绝对 CSS 选择器,不使用手写简化选择器,保证定位稳定性与可复现性。
① 前台:注册页面
- 覆盖四种场景:注册成功、账号格式错误、密码格式错误、账号密码全为空,分别断言对应的前端校验提示。
public void registerSuc() throws IOException { // 输入符合格式的账号与密码,点击注册,断言注册成功提示并截图 }② 前台:登录页面
登录页面的基本功能测试:只有账号和密码匹配才能正常登录,密码错误则登录失败。
检查成功登录:
public void LoginSuc() throws IOException { // 校验登录页"欢迎登陆"标题出现 WebElement name = wait.until(ExpectedConditions.visibilityOfElementLocated( By.cssSelector("...登录页标题..."))); assert "欢迎登陆".equals(name.getText()); // 输入正确账号密码并点击登录(doubleClick 清空原值再输入) Actions action = new Actions(driver); action.doubleClick(user).sendKeys("wwwwwww").perform(); action.doubleClick(password).sendKeys("123456").perform(); dl.click(); // 等待页面跳转离开登录页,并断言顶部出现"退出"入口 wait.until(ExpectedConditions.not(ExpectedConditions.urlContains("loginInfo"))); wait.until(ExpectedConditions.visibilityOfElementLocated( By.cssSelector("div.header-top div.menu-hd em.logout a"))); ScreenShot(Thread.currentThread().getStackTrace()[1].getMethodName()); }检查登录失败:
public void LoginF() throws IOException { // 输入正确账号 + 错误密码,点击登录,页面停留在登录页 action.doubleClick(user).sendKeys("wzh").perform(); action.doubleClick(password).sendKeys("123").perform(); dl.click(); ScreenShot(Thread.currentThread().getStackTrace()[1].getMethodName()); }③ 前台:搜索商品与商品详情页
- 搜索:搜索为 AJAX 异步刷新,点击搜索按钮后必须等待列表从全量结果刷新为筛选结果,再执行断言,否则会因旧数据导致断言误判。
- 详情页:点击商品后在新标签页打开,必须检测新窗口句柄并切换过去再操作;同时将详情页 URL 记入
Utils.currentURL,供后续购物车用例衔接。
public void searchSuc() throws IOException { /* 输入关键词 → 点击搜索 → 等待列表刷新 → 断言 */ } public void details() throws IOException { /* 点击商品 → 切换新标签页 → 校验详情页元素 → 记录 currentURL */ }④ 前台:购物车与结算
- 加入购物车:点击“加入购物车”会弹出数量选择弹层,必须点击弹层的确认按钮才能完成加购,随后断言购物车出现该商品(购物车记录 ID 为会话动态生成,禁止硬编码)。
- 结算:依赖详情页设置的
currentURL与购物车状态,从购物车勾选商品进入结算页,断言订单信息。 - 删除购物车:结算完成后清理购物车记录,保证用例可重复执行。
⑤ 前台:用户中心
- 访问个人中心断言页面加载;新增收货地址表单位于 iframe 内部,操作前先切换到对应 iframe,填写收货人、手机号、地址后保存并断言列表新增记录。
⑥ 后台:管理员登录
- 覆盖三种场景:登录成功、密码错误登录失败、未登录直接访问后台被拦截回登录页。
- 后台首页为
div.admin + iframe布局,登录成功后需切换到index/init.html的 iframe 再断言“订单总量”看板。
public void AdminLogin() throws IOException { // 输入管理员账号密码并点击登录 user.sendKeys("admin"); password.sendKeys("wzh123456"); login.click(); // 后台首页常用功能菜单在 index/init.html 这个 iframe 里 driver.switchTo().frame(wait.until(ExpectedConditions.presenceOfElementLocated( By.cssSelector("iframe[src*='index/init.html']")))); WebElement element2 = wait.until(ExpectedConditions.elementToBeClickable( By.cssSelector("...订单总量..."))); assert "订单总量".equals(element2.getText()); // 等页面真正渲染完成再截图(DOM/图片/AJAX/图表) waitPageLoadComplete(); AuthcurrentURL = driver.getCurrentUrl(); }后台首页包含 ECharts 图表与 AJAX 统计数据,专门实现了五重等待的waitPageLoadComplete(),避免截到半张图:
public static void waitPageLoadComplete() { // 1. DOM 解析完成 wait.until(d -> "complete".equals(((JavascriptExecutor) d).executeScript("return document.readyState"))); // 2. 图片全部加载成功(naturalWidth>0 排除裂图) wait.until(d -> Boolean.TRUE.equals(((JavascriptExecutor) d).executeScript( "return Array.prototype.every.call(document.images, function(img){return img.complete && img.naturalWidth > 0;})"))); // 3. jQuery 的 AJAX 全部结束 wait.until(d -> Boolean.TRUE.equals(((JavascriptExecutor) d).executeScript( "return (typeof jQuery === 'undefined') || jQuery.active === 0"))); // 4. ECharts 的 canvas 已创建并完成绘制 wait.until(d -> Boolean.TRUE.equals(((JavascriptExecutor) d).executeScript( "return Array.prototype.every.call(document.querySelectorAll('canvas'), function(c){return c.width > 0 && c.height > 0;})"))); // 5. 图表约 1 秒入场动画,动画没结束截图会截到半张图 try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }⑦ 后台:商品维护
- 商品列表搜索:列表位于
src为goods/index.html的 iframe 中,切换 iframe 后输入条件搜索;同样等待 AJAX 列表刷新完成再断言筛选结果。 - 修改价格:对搜索出的商品执行改价操作,保存后断言列表价格已更新。
⑧ 后台:订单管理与用户管理
- 订单管理:进入订单列表页,断言列表正常加载。
- 用户管理:进入用户列表页,执行编辑用户信息操作并保存断言。
(3) 用例串联执行
所有用例由runTests.main按业务顺序串联执行,前序用例的登录态与页面状态是后续用例的前置条件;finally块保证无论成功失败都退出驱动,避免msedgedriver.exe残留:
public static void main(String[] args) throws IOException, InterruptedException { // ===================================================Front================= // 页面完整性 → 注册(4种场景) → 登录 → 搜索 → 详情(设置currentURL) // → 加入购物车(依赖currentURL) → 结算 → 删除购物车 → 用户中心 → 新增收货地址 // ===================================================Admin================= try { Auth auth = new Auth(); auth.AdminNotLogin(); // 未登录拦截 auth.AdminLoginF(); // 登录失败 auth.AdminLogin(); // 登录成功 Goods goods = new Goods(); goods.productlist(); // 商品列表搜索 goods.changePrice(); // 修改价格 Order order = new Order(); order.OrderPage(); // 订单列表 User user = new User(); user.userPage(); // 用户列表 user.EditInformation(); // 编辑用户信息 } finally { Utils.quitDriver(); } }- 自动化测试结果:全部用例串联执行 BUILD SUCCESS,全程无异常,截图按日期正常落盘。
总结测试 tips
- 选择器来自浏览器真实采集:绝对 CSS 选择器虽然长,但定位稳定、可复现,面试中也能体现严谨的工程习惯。
- 异步操作必须等刷新再断言:搜索、列表筛选等 AJAX 场景,不等待就用旧数据断言,会出现“假通过”。
- 三类上下文切换要记牢:新标签页(窗口句柄)、iframe(frame 切换)、弹层(弹层内确认按钮),电商系统全部都会遇到。
- 动态数据禁止硬编码:购物车记录 ID 等会话级动态值,必须在运行时实时获取。
- 状态依赖显式传递:用
Utils.currentURL等静态字段在用例间传递 URL,保证串联执行时状态可衔接。 - 截图时机有讲究:后台含图表的页面要等 DOM、图片、AJAX、canvas、动画五重就绪后再截图,否则截到半成品。
- 驱动必须兜底回收:
try-finally + quitDriver(),防止僵尸进程拖死后续运行。
性能测试
功能测试与自动化测试回答“做得对不对”,性能测试回答“快不快、稳不稳”。本章使用 JMeter 5.5 对同一套 ShopXO 系统做协议层压测,重点验证核心页面的响应时间、登录接口的并发能力,并定位系统瓶颈。
性能测试目标
- 验证首页、搜索、商品详情在 5 / 10 / 20 并发下的响应时间与稳定性
- 验证前台登录接口的并发处理能力与成功率
- 通过“总耗时”与“首字节时间”的拆分,区分服务端处理能力与网络传输开销
- 定位瓶颈并给出可执行的优化建议
测试环境与工具
| 项目 | 说明 |
|---|---|
| 被测系统 | ShopXO 电商演示站(前台商城 + 后台管理) |
| 部署地址 | http://118.89.173.170(端口 80) |
| 压测工具 | Apache JMeter 5.5(非 GUI 命令行模式执行) |
| 施压方式 | 协议层 HTTP 请求,Cookie 管理器维持PHPSESSID会话 |
| 施压端网络 | 公网家庭宽带,实测接收速率约 255 KB/s(约 2 Mbps) |
| 测试脚本 | D:\WEB\demo\autoTest2-perf\plan\汇总报告.jmx(浏览)、login.jmx(登录) |
| 测试数据 | D:\WEB\demo\autoTest2-perf\data\users.csv(testperf01~05 / 123456) |
| 结果数据 | D:\WEB\demo\autoTest2-perf\result\*.jtl与对应 HTML 报告 |
性能场景设计
| 场景 | 业务链路 | 关键请求 | 参数化与断言 |
|---|---|---|---|
| 浏览场景 | 首页 → 搜索 → 商品详情 | GET /、POST /?s=search/index.html(wd=手机)、GET /?s=goods/index/id/32.html | 商品 ID 用 CSV 轮换;每个请求断言响应码 200;请求间加 1~2 秒随机思考时间 |
| 登录场景 | 登录页 → 提交登录 → 购物车 | GET /?s=user/loginInfo.html、POST /?s=user/login.html(accounts、pwd、type=username)、GET /?s=cart/index.html | 账号用 CSV 轮换;JSON 断言$.code == 0;购物车页断言包含“购物车” |
说明:
- 浏览场景只压 HTML 本体,不勾选“从 HTML 文件获取所有内含的资源”。原因是首页内含资源合计约 9.7 MB,会把测试变成带宽测试,掩盖服务端真实处理能力。
- 登录场景使用独立线程组和独立计划文件,避免与浏览场景同时施压导致数据混淆。
- 登录接口必须携带
type=username,缺失会返回{"code":-1,"msg":"登录类型有误"};账号不存在返回code:-3。
性能指标与通过标准
| 指标 | 通过标准 | 实测结论 |
|---|---|---|
| 错误率 | < 1% | 全部场景 0%,达标 |
| 登录接口 P95 | ≤ 2000 ms | 20 并发 494 ms,达标 |
| 核心页面 P95 | ≤ 2000 ms | 5 并发已 6.5~10.3 秒,不达标 |
| 服务端首字节 P95 | ≤ 1000 ms | 20 并发最高 992 ms,达标 |
测试执行方式
采用阶梯加压,每轮持续 180 秒,非 GUI 模式执行:
| 轮次 | 线程数(并发) | Ramp-Up | 持续时间 |
|---|---|---|---|
| 第 1 轮 | 5 | 15 s | 180 s |
| 第 2 轮 | 10 | 30 s | 180 s |
| 第 3 轮 | 20 | 60 s | 180 s |
命令行示例:
jmeter -n -t plan\login.jmx -l result\login-20.jtl -e -o result\login-20-report过程记录:首轮压测误勾选了“获取所有内含资源”,5 并发下事务平均耗时 149 秒、首页单次传输 9.7 MB、错误率 11.11%。该轮数据不作为性能结论,但直接暴露了“首屏静态资源过重”这一体验问题,后续分析保留该发现。
测试结果
页面与响应体大小
| 页面 | 平均响应体大小 |
|---|---|
| 首页 | 671.6 KB |
| 搜索结果页 | 620.5 KB |
| 商品详情页 | 626.6 KB |
| 购物车页 | 602.2 KB |
| 登录页 | 72.7 KB |
| 登录接口响应 | 约 2 KB |
浏览场景结果(HTML 本体)
| 并发 | 页面 | 样本 | 平均耗时 | P95 | 首字节 Avg | 首字节 P95 | 异常 |
|---|---|---|---|---|---|---|---|
| 5 | 首页 | 51 | 3,852 ms | 6,530 ms | 293 ms | 410 ms | 0 |
| 5 | 搜索 | 49 | 5,407 ms | 9,894 ms | 248 ms | 345 ms | 0 |
| 5 | 商品详情 | 48 | 5,535 ms | 10,267 ms | 270 ms | 376 ms | 0 |
| 5 | 浏览事务 | 48 | 14,969 ms | 24,448 ms | 803 ms | 977 ms | 0 |
| 10 | 首页 | 57 | 9,037 ms | 20,495 ms | 348 ms | 603 ms | 0 |
| 10 | 搜索 | 52 | 10,369 ms | 18,727 ms | 283 ms | 398 ms | 0 |
| 10 | 商品详情 | 48 | 10,132 ms | 17,575 ms | 344 ms | 561 ms | 0 |
| 10 | 浏览事务 | 48 | 29,066 ms | 54,566 ms | 935 ms | 1,650 ms | 0 |
| 20 | 首页 | 61 | 18,021 ms | 42,573 ms | 434 ms | 992 ms | 0 |
| 20 | 搜索 | 53 | 20,136 ms | 40,965 ms | 376 ms | 932 ms | 0 |
| 20 | 商品详情 | 44 | 19,393 ms | 37,570 ms | 465 ms | 873 ms | 0 |
| 20 | 浏览事务 | 46 | 52,072 ms | 107,070 ms | 1,007 ms | 1,886 ms | 0 |
吞吐量:三轮各完成 148 / 157 / 158 个页面请求,约0.85 页/秒,并发提高后基本不再增长。
登录场景结果
| 并发 | 登录次数 | 登录提交 Avg / P95 / P99 | 登录首字节 Avg / P95 | 购物车页 Avg / P95 | 登录事务 Avg / P95 | 登录 TPS | 异常 |
|---|---|---|---|---|---|---|---|
| 5 | 129 | 247 / 342 / 525 ms | 246 / 342 ms | 4,186 / 7,248 ms | 5,297 / 8,547 ms | 0.72 次/秒 | 0 |
| 10 | 143 | 253 / 382 / 633 ms | 253 / 382 ms | 8,377 / 14,941 ms | 10,324 / 17,666 ms | 0.79 次/秒 | 0 |
| 20 | 141 | 281 / 494 / 660 ms | 280 / 494 ms | 17,003 / 36,751 ms | 20,625 / 44,029 ms | 0.78 次/秒 | 0 |
结果分析
- 后端接口层健康,不是瓶颈。登录接口并发从 5 提到 20(4 倍),平均耗时仅从 247 ms 增至 281 ms,P95 从 342 ms 增至 494 ms;登录响应体只有约 2 KB,下载耗时几乎为 0。三组页面请求的服务端首字节平均仅 248~465 ms,P95 最高 992 ms,均低于 1 秒。
- 页面体积过大是首要瓶颈。ShopXO 把大量样式、脚本和数据内联在 HTML 中,单个页面 600~670 KB。以实测 255 KB/s 的接收速率计算,仅下载一个页面就需 2.4~2.6 秒;5 个并发用户共享带宽后,单页耗时被拉长到 4~5.5 秒。总耗时中约 92%~95% 是下载时间,服务端处理只占 250~450 ms。
- 系统吞吐量已经封顶。浏览场景并发 5/10/20 时,180 秒内完成的页面数分别为 148 / 157 / 158,吞吐量稳定在 0.85 页/秒;登录场景为 0.72 / 0.79 / 0.78 次/秒。并发翻倍只让单用户等待更久,没有带来吞吐提升,属于典型的带宽瓶颈形态。
- 稳定性良好。六轮压测(浏览 3 轮 + 登录 3 轮)累计数千次请求,错误率全部为 0%,未出现 5xx、超时、连接重置;5 个账号循环登录也没有出现互踢或限流。
优化建议
| 建议 | 预期收益 |
|---|---|
| nginx 开启 gzip / Brotli 压缩 | HTML 体积可降至约 1/4,首屏下载时间下降 60% 以上 |
| 静态资源合并、压缩,图片转 WebP 并接入 CDN | 降低单页字节数,减少带宽占用,提升多地访问速度 |
| 首页/详情页做页面级缓存或 Redis 缓存 | 降低 PHP 渲染与数据库查询压力 |
| 开启 HTTP/2 与长连接 | 减少并发连接开销(实测首次连接 18 ms,复用后为 0) |
| 在云主机内网复测 | 排除公网带宽干扰,得到服务器真实容量上限 |
测试局限
- 施压端为 2 Mbps 公网宽带,
总耗时受带宽支配,不能直接等同于服务器容量;服务端能力应以首字节时间和错误率为准。 - 未覆盖加入购物车 POST、结算、后台管理场景,也未做 30 分钟稳定性(soak)测试。
- 仅 5 个测试账号,最大并发 20,未做更高并发与连接数极限测试。
- JMeter 不执行 JavaScript,搜索结果列表的 AJAX 异步加载请求未单独压测。
- 未采集服务器 CPU、内存、PHP-FPM、MySQL 等资源指标,瓶颈定位基于响应数据推断。
- 数据为单日单次结果,未做多轮重复验证。
性能测试结论
ShopXO 系统在后端接口层表现稳定:20 并发下登录接口 P95 为 494 ms、错误率 0%,服务端首字节始终低于 1 秒,具备继续承载更高并发的基础。当前用户体验的主要短板是前端页面体积过大(600~670 KB/页)叠加有限带宽,导致首屏 4~5 秒以上、系统吞吐封顶在 0.85 页/秒。优先实施 gzip/Brotli 压缩、静态资源优化和 CDN 后,预计首屏时间可下降 60% 以上,再复测即可形成“优化前 / 优化后”的对比数据。
性能测试 tips
- 分清
elapsed与Latency:前者含下载耗时,后者是首字节时间;带宽受限时,判断服务端能力只能看 Latency。 - 不要轻易勾选“获取所有内含资源”:一个 9.7 MB 的首页会把性能测试变成带宽测试,结果失去分析价值。
- 持续时间必须配合“循环次数 = 无限”:只勾调度器、循环仍为 1,每个线程只会跑一次,样本量严重不足。
- 账号池要大于等于并发数:同一账号并发登录会互踢,错误看起来像性能问题。
- GUI 只用于调试,正式压测用命令行:
jmeter -n -t plan.jmx -l result.jtl -e -o report,避免监听器自身开销污染结果。 - CSV 数据文件设置要成对配置:
遇到文件结束符再次循环 = True、停止线程 = False,否则 5 个账号跑完 5 次就结束。