Roller: 抽奖系统测试的幕后剧本-测试报告
抽奖系统 - 测试报告
项目名称:抽奖系统
测试人员:LlvZi
测试时间:2025年5月25 - 2025年6月1
一、项目概述
该项目是一个操作简便、安全可靠的抽奖系统 。主要业务是抽奖,并支持管理员管理用户、奖品和抽奖活动,以配置抽奖内容。抽奖操作由管理员进行,支持依次抽取配置好的奖品和人数,并保证每人最多中一次奖 。系统对异常情况进行了处理,并对中奖人发送短信和邮件通知。项目采用了分层分模块设计,技术栈包括 Spring Boot3、JDK 17、MySQL、MyBatis、Redis、RabbitMQ、SLF4J + logback 等。
二、测试目标
- 验证抽奖系统各项核心功能(用户管理、奖品管理、活动管理、抽奖流程)是否符合需求。
- 确保系统在正常、异常及边界条件下均能稳定运行,特别是抽奖过程中的唯一性保证。
- 验证抽奖接口的异步处理及事务一致性。
- 验证系统安全性场景(如:身份验证、隐私数据加密)。
- 检查中奖通知(短信、邮件)的及时性和准确性。
三、测试范围
模块名称 | 涵盖功能 |
---|---|
用户模块 | 注册、登录、信息管理、身份验证 (JWT) |
奖品模块 | 奖品配置、状态管理 |
活动模块 | 活动创建、配置 (奖品、人员)、状态维护 |
抽奖模块 | 抽奖操作、异步处理、异常处理、唯一性保证 |
通知模块 | 短信通知、邮件通知 |
接口测试 | 所有REST接口状态码与返回结构校验 |
数据安全与隐私保护 | 密码加密 (加盐哈希)、手机号加密 |
四、测试方法与工具
类型 | 工具或技术栈 |
---|---|
功能测试 | Postman + 手动测试 |
接口测试 | Postman |
单元测试 | 编写测试代码验证模块正确性 |
安全性测试 | 页面强制登录、抽奖异常后的奖品唯一性校验 |
日志分析 | SLF4J + logback 记录和修复问题 |
用例管理 | Excel、XMind |
五、测试环境
环境项 | 配置 |
---|---|
操作系统 | Windows / Linux |
数据库 | MySQL |
Java版本 | JDK 17 |
后端框架 | Spring Boot3 |
缓存 | Redis |
消息队列 | RabbitMQ |
日志框架 | SLF4J + Logback |
应用部署 | 阿里云服务器部署 |
测试工具版本 | Postman v10、Selenium 4.0 |
六、测试用例统计
七、缺陷分析(Bug记录)
Bug编号 | 模块 | 严重程度 | 问题描述 | 状态 |
---|---|---|---|---|
BUG-001 | 抽奖模块 | 高 | (示例) 极端并发情况下,可能出现一人中多次奖的情况 | 待复测 |
BUG-002 | 通知模块 | 中 | (示例) 邮件通知发送失败时,未提供重试机制 | 待修复 |
BUG-003 | 登录模块 | 中 | (示例) JWT token 过期后,前端未及时引导用户重新登录 | 待修复 |
BUG-004 | 异步处理 | 低 | (示例) 死信队列消息处理后,日志记录不够详细 | 已修复 |
八、结论与建议
✅ 测试结论:
抽奖系统核心功能基本稳定,能够支持基本的抽奖活动需求。异步抽奖设计有效提升了用户体验,事务一致性和消息可靠性通过回滚和死信队列得到保障。身份验证和数据加密措施在一定程度上保障了系统安全。
🔧 建议:
- 加强并发测试: 针对抽奖场景进行更严格的并发测试,确保在高并发下抽奖公平性和唯一性的持续有效。
- 优化消息通知的重试机制: 进一步完善短信和邮件通知的重试策略和失败告警机制,确保通知的送达率。
- 提升前端错误处理和用户体验: 针对异步抽奖接口返回成功后前端的展示,可以增加更丰富的兜底方案,例如在数据未完全落库前提供加载状态或更友好的提示。
- 探索更全面的安全测试: 考虑引入自动化安全扫描工具,进一步发现潜在的安全漏洞,如XSS、CSRF等。
- 完善日志监控和告警: 针对异步处理、死信队列等关键流程,设置更细致的日志级别和告警,便于及时发现和解决问题。
九、附录
-
测试用例Excel文档 (部分)
-
-
Postman 接口测试集合
-
项目部署文档
*部分代码展示:
package tests;import common.Utils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;import java.time.Duration;public class BloginTest extends Utils {public static String url = "http://60.205.7.136:8082/blogin.html";public BloginTest() {};public void LoginTest() throws InterruptedException {driver.get(url);// 首先验证标题String expectedTitle = driver.getTitle();assert expectedTitle.equals("管理员登录界面");// 模拟登陆测试driver.findElement(By.cssSelector("#phoneNumber")).sendKeys("18039295275");driver.findElement(By.cssSelector("#password")).sendKeys("123456");driver.findElement(By.cssSelector("#loginForm > button")).click();Thread.sleep(1000);String expectedTitle1 = driver.getTitle();System.out.println(expectedTitle1);assert expectedTitle1.equals("后台官迷");System.out.println("模拟登陆成功!");}// 检查是否加载成功public void LoginRight() throws InterruptedException {driver.get(url);// 手机号 + 密码登录// 检查是否有 user和 passworddriver.findElement(By.cssSelector("#phoneNumber")).sendKeys("18039295275");driver.findElement(By.cssSelector("#password")).sendKeys("123456");System.out.println("手机号+密码登录界面测试成功!");// Thread.sleep(2000);// 验证码登录driver.findElement(By.cssSelector("body > div > div.login-container.col-sm-6.col-md-6.col-lg-5.col-xl-5 > div.tab-box > span:nth-child(2)")).click();//Thread.sleep(1000);driver.findElement(By.cssSelector("#loginMobile")).sendKeys("18039295275");driver.findElement(By.cssSelector("#getVerificationCode"));driver.findElement(By.cssSelector("#verificationCode")).sendKeys("123456");System.out.println("手机号+验证码登录界面测试成功!");}// 正常登录 验证码登录由于无法使用阿里云短信服务故无法验证 --》直接看手机是否收到手机号public void LoginSubmitRight() {driver.findElement(By.cssSelector("body > div > div.login-container.col-sm-6.col-md-6.col-lg-5.col-xl-5 > div.tab-box > span.tab-span.active")).click();driver.navigate().refresh();// 手机号 + 密码登录driver.findElement(By.cssSelector("#phoneNumber")).sendKeys("18039295275");driver.findElement(By.cssSelector("#password")).sendKeys("123456");driver.findElement(By.cssSelector("#loginForm > button")).click();String expectedTitle1 = driver.getTitle();assert expectedTitle1.equals("后台管理");System.out.println("手机号+密码正常登录成功");}/*** 登录失败测试--手机+密码登录*/public void LoginSubmitError1() throws InterruptedException {driver.findElement(By.cssSelector("body > div.header-box > div.user-box > div > span")).click();// 返回上一界面 因为上一个测试是“登录成功”,成功之后跳转到list界面了
// driver.navigate().back();
// driver.navigate().refresh();Thread.sleep(2000);// 用户名正确 + 密码错误driver.findElement(By.cssSelector("#phoneNumber")).sendKeys("18039295275");driver.findElement(By.cssSelector("#password")).sendKeys("123451");driver.findElement(By.cssSelector("#loginForm > button")).click();// 由于设置的有弹窗需要解决一下 alert只能通过显示等待获取WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));Alert alert = wait.until(ExpectedConditions.alertIsPresent());String text = alert.getText();assert text.equals("登录失败!密码错误");alert.accept();System.out.println("登录失败--用户名正确--密码错误--验证成功");// driver.quit();}/*** 登录失败测试--用户名 || 密码为空*/public void LoginSubmitError2() throws InterruptedException {// 刷新 重新输入driver.navigate().refresh();// Thread.sleep(3000);driver.findElement(By.cssSelector("#phoneNumber")).sendKeys("");driver.findElement(By.cssSelector("#password")).sendKeys("123451");driver.findElement(By.cssSelector("#loginForm > button")).click();String phoneNumberIsNULLText = driver.findElement(By.cssSelector("#phoneNumber-error")).getText();phoneNumberIsNULLText.equals("请输入您的手机号");System.out.println("登录失败测试--用户名 || 密码为空 验证成功");}/*** 登录失败测试--检查是否存在sql注入问题 弹出"登录失败,密码或者用户名错误!"*/public void LoginSubmitError3() throws InterruptedException {// 刷新 重新输入driver.navigate().refresh();// Thread.sleep(3000);driver.findElement(By.cssSelector("#phoneNumber")).sendKeys("admin' -- ");driver.findElement(By.cssSelector("#password")).sendKeys("123456");driver.findElement(By.cssSelector("#loginForm > button")).click();// 由于设置的有弹窗需要解决一下 alert只能通过显示等待获取WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));Alert alert = wait.until(ExpectedConditions.alertIsPresent());String text = alert.getText();assert text.equals("登录失败!登录方式不存在");alert.accept();System.out.println("登录失败测试--检查是否存在sql注入问题--sql注入验证成功");// driver.quit();}public void registerTest() {driver.navigate().refresh();driver.findElement(By.cssSelector("body > div > div.login-container.col-sm-6.col-md-6.col-lg-5.col-xl-5 > div.register-link > a")).click();String expectedTitle = driver.getTitle();assert expectedTitle.equals("注册页面");System.out.println("成功进入注册页面");driver.findElement(By.cssSelector("#name")).sendKeys("yj");driver.findElement(By.cssSelector("#mail")).sendKeys("2314394022@qq.com");driver.findElement(By.cssSelector("#phoneNumber")).sendKeys("18039295222");driver.findElement(By.cssSelector("#password")).sendKeys("123456");driver.findElement(By.cssSelector("#registerForm > button")).click();System.out.println("注册成功!");WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));Alert alert = wait.until(ExpectedConditions.alertIsPresent());alert.accept();}
}
package tests;import common.Utils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;import java.time.Duration;/*** 创建活动接口测试*/
public class CreateActivityTest extends Utils {public static String url = "http://60.205.7.136:8082/admin.html";public CreateActivityTest() {}public void createActivityRight() throws InterruptedException {// 假设已经登录driver.findElement(By.cssSelector("#createActivity")).click();driver.switchTo().frame("contentFrame");driver.findElement(By.cssSelector("#activityName")).sendKeys("test2");driver.findElement(By.cssSelector("#description")).sendKeys("test2");Thread.sleep(2000);// 点击圈选奖品--跳出奖品选择模态框(重点是如何对模态框进行补货)driver.findElement(By.cssSelector("#buttonPrizes")).click();WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));WebElement prizeModal = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("prizesModal")));prizeModal.findElement(By.cssSelector("#prize-20")).click();// 模拟圈选一个奖品prizeModal.findElement(By.cssSelector("#prizesModal > div > div.form-btn-box > button.btn.btn-primary")).click();Thread.sleep(2000);// 点击全选人员driver.findElement(By.cssSelector("#buttonUsers")).click();WebDriverWait wait1 = new WebDriverWait(driver, Duration.ofSeconds(20));WebElement userModal = wait1.until(ExpectedConditions.visibilityOfElementLocated(By.id("usersModal")));userModal.findElement(By.cssSelector("#user-41")).click();userModal.findElement(By.cssSelector("#usersModal > div > div.form-btn-box > button.btn.btn-primary")).click();Thread.sleep(2000);driver.findElement(By.cssSelector("#createActivity")).click();Alert alert = wait1.until(ExpectedConditions.alertIsPresent());alert.accept();System.out.println("活动创建成功!");}
}package tests;import common.Utils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;import java.time.Duration;public class CreatePrizeTest extends Utils {public static String url = "http://60.205.7.136:8082/admin.html";public CreatePrizeTest() {}public void createPrizeRight() {// 假设已经登录driver.findElement(By.cssSelector("body > div.cont-box > div.sidebar > ul > li:nth-child(2) > ul > li:nth-child(2) > a")).click();driver.switchTo().frame("contentFrame");driver.findElement(By.cssSelector("#prizeName")).sendKeys("华为手机");driver.findElement(By.cssSelector("#price")).sendKeys("5000");driver.findElement(By.cssSelector("#description")).sendKeys("遥遥领先");WebElement element = driver.findElement(By.xpath("/html/body/div/div/div/div[2]/div/div/input"));element.sendKeys("C:\\Users\\绿字\\Desktop\\抽奖系统\\OIP-C (1).jpg");driver.findElement(By.cssSelector("body > div > button")).click();WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));Alert alert = wait.until(ExpectedConditions.alertIsPresent());alert.accept();System.out.println("奖品上传成功");}}