文章目录

| 项目名称 | 年会抽奖系统 | 版本号 | / |
|---|---|---|---|
| 发布类型 | 全量发布 | 测试负责人 | 独断万古他化 |
| 测试完成日期 | 2026/04/02 | 联系方式 |
项目背景:
项目背景与意义
公司年会抽奖系统是企业内部活动的核心环节,承载着活跃现场氛围、传递企业文化、提升员工参与感与归属感的重要作用。系统的稳定性、公平性、操作流畅度,直接影响年会的整体体验与员工满意度。
通过项目开发与测试实践,我深入掌握了 Java、Spring Boot、MyBatis、Redis、MySQL 等后端技术栈,以及接口自动化、UI 自动化、性能压测等测试核心技能,并在实际场景中强化了对系统稳定性、数据一致性、并发安全性、用户体验与可维护性的理解,实现了将所学开发与测试知识融会贯通到项目中,加深了自己对全链路质量保障的理解与实践能力。
为保障年会现场抽奖环节的平稳落地,本次针对该系统开展了全维度质量验证工作,覆盖功能流程、接口交互、UI 操作、并发性能多个层面,通过多轮测试提前发现并修复逻辑漏洞、界面异常与性能瓶颈,确保系统在现场高并发、高频操作的压力下依然稳定可靠,实现公平、透明、零故障的抽奖体验,为年会活动的顺利开展保驾护航。
项目概述
> 已实现的主要功能
- 用户登录与注册
- 活动列表查看与创建
- 奖品列表查看与创建
- 人员列表查看与普通用户创建
- 抽奖实现与中奖记录查看
> 当前系统存在的不足
- 权限控制薄弱:无细粒度的权限管理,仅通过身份区分
- 抽奖过程缺少分布式锁,高并发下可能重复抽奖
- 缺少数据导出功能,中奖记录等数据无法导出
- 监控告警确实:无系统监控和异常告警机制
项目系统功能简析
1. 登录功能
- 用户需输入已注册的手机号和密码进行登录
- 支持两种登录方式:密码登录、短信验证码登录
- 登录成功后跳转至后台管理页面
- 未登录状态下访问受保护页面,将强制跳转至登录页
- 右下角提供"去注册"链接,可跳转至注册页面
2. 后台管理页
- 左侧显示导航菜单,包含三大模块:活动管理、奖品管理、人员管理
- 右上角提供"退出"按钮:退出当前用户,返回登录页
- 每个模块下包含二级菜单:
- 活动管理:活动列表、新建抽奖活动
- 奖品管理:奖品列表、创建奖品
- 人员管理:人员列表、注册用户
3. 活动列表页
- 展示已创建的活动列表,包括活动名称、描述、状态
- 支持分页查询
- 点击"查看详情"可跳转至抽奖页面
- 左侧导航栏可切换至其他功能模块
4. 新建活动页
- 填写活动名称、活动描述
- 从奖品列表中选择奖品并设置奖品等级、数量
- 从人员列表中选择参与抽奖的人员
- 点击"提交"创建活动,创建成功后跳转至活动列表
5. 奖品列表页
- 展示奖品列表,包括奖品名称、描述、图片、价值
- 支持分页查询
- 左侧导航栏可切换至创建奖品页面
6. 创建奖品页
- 填写奖品名称、描述、价值
- 支持上传奖品图片
- 点击"提交"创建奖品,创建成功后跳转至奖品列表
7. 人员列表页
- 展示已注册用户列表,包括用户名、邮箱、手机号、身份
- 支持按身份筛选(管理员/普通用户)
- 左侧导航栏可切换至注册用户页面
8. 注册用户页
- 填写用户名、邮箱、手机号、密码
- 系统自动设置为普通用户身份
- 点击"提交"完成注册,注册成功后跳转至人员列表
9. 抽奖展示页
- 展示活动名称、奖品信息(等级、名称、数量、图片)
- 提供两个操作按钮:
- 查看上一奖项:返回上一个奖项查看
- 开始抽奖:开始当前奖项的抽奖
- 抽奖过程展示人员名单滚动效果
- 点击"点我确定"确定中奖者,保存中奖记录
- 所有奖项抽完后展示完整中奖名单
- 提供"分享结果"按钮,可复制链接分享给他人查看
测试项目相关信息
- 项目在线地址:http://indiehub.icu/blogin.html
- 项目代码:https://gitee.com/indie-fly/project/tree/master/lottery-system
- 项目详解:抽奖系统项目详解专栏
测试安排:
| 模块 | 子模块 | 前端 | 开发 | 提测时间 | 测试 | 工时 | 排期 | 进度 | 备注 |
|---|---|---|---|---|---|---|---|---|---|
| 用户管理 | 注册、密码登录、验证码登录 | 独断万古他化 | 独断万古他化 | 3.20 | 独断万古他化 | 1d | 3.20-3.21 | 测试完成 | |
| 活动管理 | 活动创建、列表查询、详情查询 | 独断万古他化 | 独断万古他化 | 3.20 | 独断万古他化 | 1d | 3.20-3.21 | 测试完成 | |
| 奖品管理 | 奖品创建、图片上传、列表查询 | 独断万古他化 | 独断万古他化 | 3.22 | 独断万古他化 | 1d | 3.22-3.23 | 测试完成 | |
| 人员管理 | 人员列表、用户注册 | 独断万古他化 | 独断万古他化 | 3.22 | 独断万古他化 | 0.5d | 3.22-3.23 | 测试完成 | |
| 抽奖执行 | 抽奖流程、中奖保存、状态流转 | 独断万古他化 | 独断万古他化 | 3.24 | 独断万古他化 | 1.5d | 3.24-3.26 | 测试完成 | 核心 |
| 中奖记录 | 记录查询、缓存、分享 | 独断万古他化 | 独断万古他化 | 3.24 | 独断万古他化 | 1.5d | 3.24-3.26 | 测试完成 |
测试分类:
(1)功能测试:

- 功能测试结果:所以测试全部通过,通过率 100%
(2)自动化测试:
本项目自动化测试从接口自动化和UI自动化两个层面进行测试,具体详细的测试详解如下链接:
- 接口自动化详解: Python+Pytest 接口自动化测试实战 ------ 抽奖系统接口测试框架设计与实现
- UI自动化详解: Selenium 实战 ------ 抽奖系统 UI 自动化测试框架搭建
本测试报告下只做简单总结与描述。
1> UI 自动化测试:
编写Web测试用例

创建自动化架构
测试框架采用分层设计,最底层是公共工具类 ,负责浏览器驱动的创建和管理、截图功能、通用等待方法等。中间层是各个页面的测试类,每个类对应一个页面,封装该页面的所有操作方法。最上层是测试入口类,负责按照业务流程组织测试用例的执行顺序。并且设置了images文件夹,专门存放自动化过程中关键步骤的截图留存,以便后续查看是否有异常问题。

自动化测试工具准备
公共类处理代码:
java
/**
* Selenium WebDriver 工具类
* 提供浏览器驱动管理、截图、弹窗处理等公共方法
*/
public class Utils {
/** WebDriver 驱动对象,静态变量保证全局只有一个驱动实例 */
public static WebDriver driver = null;
/** 显式等待对象,用于等待特定条件成立 */
public WebDriverWait wait = null;
/** 测试基础URL地址 */
public static String baseUrl = "http://43.143.84.36:8081";
/**
* 构造函数,初始化驱动并打开指定URL
* @param url 要访问的页面地址
*/
public Utils(String url) {
driver = createDriver();
driver.get(url);
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
/**
* 默认构造函数,不打开新页面
* 用于已经切换到目标页面的场景
*/
public Utils() {
if (driver == null) {
driver = createDriver();
}
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
/**
* 创建 WebDriver 驱动对象
* 使用单例模式,确保整个测试过程只创建一个浏览器实例
* @return WebDriver 实例
*/
public static WebDriver createDriver() {
if (null == driver) {
// 使用 WebDriverManager 自动下载并配置 ChromeDriver
// 指定 Chrome 版本号,确保驱动版本匹配
WebDriverManager.chromedriver().driverVersion("146.0.7680.178").setup();
// 配置 Chrome 浏览器选项
ChromeOptions options = new ChromeOptions();
// 允许远程源访问,解决跨域问题
options.addArguments("--remote-allow-origins=*");
// 禁用浏览器通知弹窗
options.addArguments("--disable-notifications");
// 创建 Chrome 驱动实例
driver = new ChromeDriver(options);
// 设置隐式等待时间(全局等待,查找元素时最多等待5秒)
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
// 最大化浏览器窗口
driver.manage().window().maximize();
}
return driver;
}
/**
* 屏幕截图方法
* 将截图保存到 ./src/test/java/images/日期/方法名-时间.png
* @param str 截图文件名前缀,通常使用测试方法名
* @throws IOException 文件操作异常
*/
public void ScreenShot(String str) throws IOException {
// 日期格式化器,用于生成目录名(年-月-日)
SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd");
// 时间格式化器,用于生成文件名后缀(时分秒毫秒)
SimpleDateFormat sim2 = new SimpleDateFormat("HHmmssSS");
String dirTime = sim1.format(System.currentTimeMillis());
String fileTime = sim2.format(System.currentTimeMillis());
// 构建截图文件完整路径
String fileName = "./src/test/java/images/" + dirTime + "/" + str + "-" + fileTime + ".png";
// 执行截图并保存到文件
File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(srcFile, new File(fileName));
}
/**
* 处理 JavaScript Alert 弹窗
* 等待弹窗出现并点击确定按钮
*/
public void alertAccept() {
// 显式等待弹窗出现
wait.until(ExpectedConditions.alertIsPresent());
// 切换到弹窗并点击确定
Alert alert = driver.switchTo().alert();
alert.accept();
}
/**
* 线程休眠方法
* 用于在测试过程中添加固定等待时间
* @param millis 休眠时间(毫秒)
*/
public void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 关闭浏览器并释放资源
* 测试结束后调用,清理驱动对象
*/
public void quit() {
if (driver != null) {
driver.quit();
driver = null;
}
}
}
测试用例tests的编写
① 登录页面
- 检查页面的正常加载:
- 检查成功登录:
- 检查登录失败:
脚本测试过程:

② 奖品创建与奖品列表页面
- 奖品正常创建
- 奖品异常创建
- 校验页面元素正常
- 校验列表内容正常
- 分页功能是否正常
脚本测试过程:

③ 普通用户注册与人员列表页面
- 校验页面元素正常
- 校验列表内容正常
- 用户异常创建(空/错误/格式异常/已被注册等等)
- 用户正常创建
脚本测试过程:

④ 创建活动与活动列表页面
- 校验页面元素
- 测试 奖品/人员 圈选模态框
- 创建活动异常(数量异常等等)
- 创建活动正常(单奖品-单用户/多奖品-多用户)
- 活动列表各元素展示无误
脚本测试过程:

⑤ 抽奖页面
- 校验抽奖页
- 按照抽奖流程脚本执行自动抽奖
脚本测试过程:

2> 接口自动化:
编写接口测试用例
接口自动化测试从不同的模块设计测试用例:选取选择功能复杂、逻辑分支多;对业务影响大、风险高;重复性高的接口进行自动化测试,这部分接口通常都是核心接口,使用率高自然更需要保证这些接口的质量。

创建自动化框架
采用分层设计的思想,将框架划分为测试用例层、业务逻辑层、接口封装层、工具支持层和数据层。

测试用例的编写
完整的测试代码参考码云链接:lottery_system_ApiAutoTest
创建奖品示例:
java
class TestCreatePrize:
url = host + "/prize/create"
schema = {
"type": "object",
"required": ["code","data","msg"],
"additionalProperties": False,
"properties": {
"code": {
"type": "number"
},
"data": {
"type": ["number","null"]
},
"msg": {
"type": "string"
}
}
}
# 未登录状态下
@pytest.mark.order(11)
def test_create_nologin(self):
r = Request().post(url=self.url,raise_for_status=False)
assert r.status_code == 401
# 登录状态下 ------ 正常创建
@pytest.mark.order(14)
def test_cteate_success(self):
header = {
"user_token":get_yaml("data.yaml","data.user_token")
}
params = {
"prizeName": "吹风机",
"description": "吹风机",
"price": 100
}
form_data = {
"param":json.dumps(params)
}
img_path = Path(__file__).resolve().parent.parent / "data" / "images" / "吹风机.png"
with open(img_path, "rb") as f:
files = {"prizePic": ("吹风机.png", f, "image/png")}
r = Request().post(url=self.url, headers=header, data=form_data, files=files, raise_for_status=False)
validate(r.json(),schema=self.schema)
assert r.json()["code"] == 200
# 登录状态下 ------ 异常创建
@pytest.mark.order(12)
@pytest.mark.parametrize("prize",[
{
"prizeName": "",
"description": "吹风机",
"price": 100,
"msg":"奖品名不能为空"
},
{
"prizeName": "吹风机",
"description": "吹风机",
"price": "",
"msg":"奖品价格不能为空"
}
])
def test_create_fail(self,prize):
header = {
"user_token": get_yaml("data.yaml", "data.user_token")
}
params = {
"prizeName": prize["prizeName"],
"description": prize["description"],
"price": prize["price"]
}
form_data = {
"param":json.dumps(params)
}
img_path = Path(__file__).resolve().parent.parent / "data" / "images" / "吹风机.png"
with open(img_path, "rb") as f:
files = {"prizePic": ("吹风机.png", f, "image/png")}
r = Request().post(url=self.url, headers=header, data=form_data, files=files, raise_for_status=False)
validate(r.json(), schema=self.schema)
assert r.json()["code"] == 500
@pytest.mark.order(13)
def test_create_nofile(self):
header = {
"user_token": get_yaml("data.yaml", "data.user_token")
}
params = {
"prizeName": "吹风机",
"description": "吹风机",
"price": 100
}
form_data = {
"param": json.dumps(params)
}
r = Request().post(url=self.url, headers=header, data=form_data, raise_for_status=False)
validate(r.json(), schema=self.schema)
assert r.json()["code"] == 500
数据流转
完整数据流转链路:
bash
注册成功 → 保存用户数据(手机号、密码、邮箱、用户ID)
↓
登录成功 → 保存Token和身份信息
↓
用户列表 → 保存NORMAL用户ID和姓名
↓
奖品列表 → 保存奖品ID列表
↓
创建活动 → 使用奖品ID和用户数据,保存活动ID及关联数据
↓
抽奖 → 使用活动ID、奖品ID、用户数据执行抽奖
↓
中奖记录/活动详情 → 使用活动ID查询结果
用例执行顺序:
执行顺序的设计原则是:先执行数据生产者,后执行数据消费者。
| Order | 测试模块 | 测试内容 | 数据产出 |
|---|---|---|---|
| 1-4 | 注册模块 | 完成管理员和普通用户注册 | 用户数据(手机号、密码、邮箱等) |
| 5-6 | 验证码模块 | 发送验证码测试 | 无 |
| 7-8 | 登录模块 | 管理员和普通用户登录 | Token、身份信息 |
| 9-10 | 用户列表 | 查询用户列表 | NORMAL用户ID和姓名 |
| 11-16 | 奖品模块 | 创建奖品、获取奖品列表 | 奖品ID列表 |
| 17-21 | 活动模块 | 创建活动、获取活动列表 | 活动ID及关联数据 |
| 22-24 | 抽奖模块 | 执行抽奖操作 | 中奖记录 |
| 25-30 | 记录模块 | 查询中奖记录、活动详情 | 无 |
测试结果
通过allure 生成在线的html 测试报告:测试通过率 100%,涉及92条测试用例,总耗时13秒左右。

脚本优化建议:在数据生产者类执行完毕后,使用多线程的方式同步执行其他消费者类,减少后续消费者类等待耗时。
该测试报告支持查看每一条测试用例的执行,且有日志打印,可以查看该次测试的具体情况。

(3)性能测试:
本项目性能压测以 梯度加压 + 平滑压测 的方式进行测试,也就是负载测试;
具体详细的性能测试详解如下链接:抽奖系统性能负载测试:基于 JMeter 的梯度加压与本地缓存优化全流程
1> 压测架构

2> 压测结果
在优化接口(二级缓存+接口限流)后,200 并发下梯度进行压测,总压测时间近11分钟,表现稳定,所有测试接口响应最大值都在 200ms 以下,且异常值为 0,
聚合报告:

响应时间表:

TPS(吞吐量)表

发现TPS仍能够继续增加,还未到达明显的平滑拐点,说明该系统还能够承受更大的并发数,有待下一步继续加压测试。
3> 压测报告
整体状态: 所有接口请求 100% 成功,无错误,APDEX(应用性能指数)满分(1.000),性能表现优秀。
关键指标:
- 总请求数:594,855 次
- 平均响应时间:7.98ms,最慢响应仅 222ms
- 吞吐量:917.90 TPS,无性能瓶颈
- 各接口平均响应时间均在 10ms 左右,满足高并发场景需求

性能报告图标页:
在 200 并发梯度压测过程中,所有接口响应稳定、无异常波动,平均和分位延迟均维持在低水平;吞吐量、网络收发量与并发数同步变化,无阻塞,系统和链路状态健康,整体性能表现良好。

测试总结:
本次项目围绕系统全生命周期质量保障,构建了 "功能验证 + 自动化回归 + 性能压测" 三位一体的测试覆盖体系:
- 功能测试全覆盖: 完成用户管理、活动管理、奖品管理、人员管理、抽奖流程、中奖记录 6 大核心模块的全流程验证,覆盖正常 / 异常场景,用例通过率 100%,确保业务逻辑无漏洞。
- 自动化测试双维度落地: 基于 Selenium 实现 UI 自动化,覆盖核心页面操作流程;基于 3Python+Pytest 搭建接口自动化框架,完成 92 条接口用例验证,实现高频回归场景的自动化覆盖,大幅提升测试效率与可复用性。
- 性能测试针对性验证: 针对抽奖等高并发核心场景开展梯度压测,验证 200 并发下系统稳定性,确认接口响应与异常处理能力满足现场使用需求,提前规避性能瓶颈。