文章目录
一、项目介绍
项目简介
该项目是一个门店点餐系统,采用前后端分离的方式实现,后端框架是SSM,前端框架是 Vue。系统角色分为管理员和普通用户,不同的用户登录进入不同的页面。
功能介绍
- 登录:用户和管理员输入手机号和密码进行登录
- 注册:用户可以输入手机号、用户名、密码和确认密码进行注册。
- 注销:注销后,删除本地的记录和更新服务器中的登录状态。
- 下单:用户可以勾选列表中的菜品,添加备注信息,然后点击结算进行下单。
- 评论:用户可以在自己的下的订单中,选择菜品进行评论。
- 取消订单:用户可以取消自己未开始制作的订单。
- 管理菜品:管理员可以添加菜品、编辑菜品、添加或删除菜品类型,上架或下架菜品。
- 管理订单:管理员可以更新订单状态。
- 管理评论:管理员可以对评论进行删除。
界面截图
用户界面
管理员界面
二、功能测试
功能测试用例
发现的 BUG 和 解决方法
在功能测试的过程中,发现了两个小 BUG 。
注册功能
在注册功能中,后端的参数校验做的不到位:
- 后端没有校验手机号长度(11位)
- 后端没有校验手机号是否全数字
- 后端没有校验用户名长度(2~10)
绕过前端直接向后端发送请求,就可以注册这些非法的参数
解决方法:
后端注册接口中添加参数校验
if (user.getPhone().length() != 11) {
return JsonResult.failure(-1, "手机号长度错误!");
}
if (!StringUtils.hasLength(user.getUsername()) || !StringUtils.hasLength(user.getPassword())) {
return JsonResult.failure(-1, "用户名或密码不能为空!");
}
if (user.getUsername().length() < 2 || user.getUsername().length() > 10) {
return JsonResult.failure(-1, "用户名长度错误!");
}
效果:
上传图片功能
在上传头像,图片太大时,后端报错,使用抓包工具 Fiddler 抓包发现:
{
"code": 500,
"msg": "Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field file exceeds its maximum permitted size of 1048576 bytes.",
"data": null
}
- 报错原因是Spring Boot 默认的上传文件大小限制为 1MB
检查代码时发现,前后端对上传的图片大小和格式都没有做校验。
解决方法:
前端发请求前,对图片大小和格式进行校验:
beforeAvatarUpload(file) {
let type = file.type;
const flg1 = !(type === 'image/jpeg' || type === 'image/png')
const flg2 = file.size / 1024 / 1024 > 1
if (flg1) {
this.$message.error('上传头像图片只能是 JPG 或 PNG 格式!')
}
if (flg2) {
this.$message.error('上传头像图片大小不能超过 1MB!')
}
return !flg1 && !flg2
}
后端对图片格式进行校验:
String fileName = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
if (!".jpg".equals(fileName) && !".png".equals(fileName)) {
return JsonResult.failure(-1, "上传头像图片只能是 JPG 或 PNG 格式!");
}
效果:
三、 自动化测试
使用 Junit + Selenium 进行自动化测试。
自动化测试用例
选择了部分核心功能,设计自动化测试用例。
用户功能自动化测试
登录功能
自动化测试代码:
@Order(1)
@ParameterizedTest
@CsvFileSource(resources = "loginUser.csv")
public void login(String phone, String password) throws InterruptedException {
// 打开登录页面
webDriver.get("http://43.142.48.195/");
// 智能等待, 超时时间3秒
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
// 输入账号
webDriver.findElement(By.xpath("//*[@id=\"app\"]/div/div[2]/form/div[1]/div/div[1]/input"))
.sendKeys(phone);
// 输入密码
webDriver.findElement(By.xpath("//*[@id=\"app\"]/div/div[2]/form/div[2]/div/div/input"))
.sendKeys(password);
// 点击登录
webDriver.findElement(By.xpath("//*[@id=\"app\"]/div/div[2]/form/div[3]/div/button")).click();
sleep(200);
// 检验页面是否跳转到 用户菜品列表页
String curUrl = webDriver.getCurrentUrl();
Assertions.assertEquals("http://43.142.48.195/#/customer/customerDish", curUrl);
// 检验是否为用户
String status = webDriver.findElement(By.xpath("//*[@id=\"app\"]/div/section/header/div/div[1]/span[2]")).getText();
Assertions.assertEquals("用户", status);
// 检验菜品数量
checkDishCount();
}
private void checkDishCount() {
// 数据库中的上架的菜品数量
int expectedCount = 22;
// 页面显示的菜品数量
int actualCount = webDriver.findElements(By.cssSelector(".el-checkbox__inner")).size();
Assertions.assertEquals(expectedCount, actualCount);
}
CSV 文件:
搜索和重置
自动化测试代码:
@Order(2)
@Test
public void searchForDishes() throws InterruptedException {
// 搜索框输入 ”饭“
webDriver.findElement(By.cssSelector("#app > div > section > section > main > div > div:nth-child(1) > div:nth-child(4) > div > input"))
.sendKeys("饭");
// 点击搜索
webDriver