目录
一、项目的介绍
本项目是基于 Spring 开发的前后端分离的论坛系统。
在该项目的开发中使用到 SpringBoot 构建项目基础框架 、SpringMVC 开发 Web 应用程序 及 Mybatis-Plus 来操作数据库等。
二、项目功能
本项目实现了登录,注册,展示用户信息,修改用户信息,发布帖子,查看帖子,点赞帖子,回复帖子,用户间私聊等功能。
登录界面:
注册界面:
论坛页面展示:
帖子内容与帖子回复:
用户信息展示:
收到的私信:
三、测试项目
1.编写测试用例

2.执行部分测试用例
登录界面测试:
1)输入正确的用户名与密码 -- 登录成功进入首页
2)输入错误的用户名 -- 提示用户名错误
3)输入错误的密码 -- 提示密码错误
注册界面测试:
1)输入基本的注册信息 -- 成功注册并跳转登录界面
2)输入存在的用户名 -- 提示用户名已存在
3)密码与确认密码不一致 -- 提示检查确认密码
个人中心测试:
修改个人信息 -- 提示修改成功并更换页面信息:
修改前:
修改后:
发帖测试:
发送帖子 -- 提示发送成功并在首页显示帖子 :
发送私信测试
给用户发送私信 -- 提示发送成功,接受者收到私信
3.自动化测试
1)编写论坛系统的界面测试用例
2)添加相关Maven依赖(pom.xml)
<dependencies>
<!-- 用来自动下载浏览器驱动 -->
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<!-- 用来自动化Web浏览器操作 -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.0.0</version>
</dependency>
<!-- 用来使用屏幕截图操作 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
3)编写自动化代码测试部分测试用例
Utils类:
用来定义不同页面测试所用到的公共属性(例如:浏览器驱动),定义一些公共方法,(例如:屏幕截图,创建浏览器驱动)package common; import io.github.bonigarcia.wdm.WebDriverManager; import org.apache.commons.io.FileUtils; import org.openqa.selenium.By; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; 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 WebDriverWait wait = null; public Utils(String url) { //调用driver对象 driver = createDriver(); // 显示等待 wait = new WebDriverWait(driver, Duration.ofSeconds(2)); // 浏览器跳转到 URL 界面 driver.get(url); } // 创建浏览器驱动 private WebDriver createDriver() { if(driver == null) { //下载驱动 WebDriverManager.chromedriver().setup(); ChromeOptions options = new ChromeOptions(); //允许访问所有的链接 options.addArguments("--remote-allow-origins=*"); driver = new ChromeDriver(options); //隐式等待 driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3)); } return driver; } // 屏幕截图 public void screenshot(String str,String describe) 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()); // ./src/test/java/images/2025-04-14/test01/描述13972011.png String filename = "./src/test/java/images/"+ dirTime + "/" + str + "/" + describe +fileTime + ".png"; File image = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); FileUtils.copyFile(image,new File(filename)); } // 从提示弹窗中获取文本 public String getTestFromAler(String path) { String text = driver.findElement(By.xpath(path)).getText(); text = text.replace("\n","").substring(3); return text; } }
LoginTest类:
用来自动化测试登录界面package tests; import common.Utils; import org.openqa.selenium.By; import java.io.IOException; // 自动化测试登录界面 继承 Utils 类就能使用 Utils 创建好的浏览器驱动和一些公共方法 public class LoginTest extends Utils { // 登录界面的URL private static String url = "http://127.0.0.1:58080/sign-in.html"; public LoginTest() { super(url); } // 检查元素是否存在 -- 不存在就报错 public void checkEleExist() { // 检查用户名输入框是否存在 driver.findElement(By.cssSelector("#username")); // 检查密码输入框是否存在 driver.findElement(By.cssSelector("#password")); // 检查登录按钮是否存在 driver.findElement(By.cssSelector("#submit")); // 检查登录按钮是否存在 driver.findElement(By.cssSelector("#submit")); // 检查点击注册是否存在 driver.findElement(By.cssSelector("body > div > div > div > div:nth-child(1) > div > div.text-center.text-muted.mt-3 > a")); // 检查图标是否存在 driver.findElement(By.cssSelector("body > div > div > div > div:nth-child(1) > div > div.text-center.mb-4 > img")); } // 登录成功测试 public void checkLoginSuccess() throws IOException { //先清空输入框 防止有信息遗留在输入框上 driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password")).clear(); driver.findElement(By.cssSelector("#username")).sendKeys("zhangsan"); driver.findElement(By.cssSelector("#password")).sendKeys("123456"); driver.findElement(By.cssSelector("#submit")).click(); // 检查首页的昵称存不存 存在--登录成功 不存在 -- 登录失败 driver.findElement(By.cssSelector("#index_nav_nickname")); } // 登录失败测试 public void LoginFail() throws InterruptedException { //先清空输入框 防止有信息遗留在输入框上 driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password")).clear(); // 1. 不输入用户名 driver.findElement(By.cssSelector("#username")).sendKeys(""); driver.findElement(By.cssSelector("#password")).sendKeys("123456"); driver.findElement(By.cssSelector("#submit")).click(); // 获取请输入用户名提示 String text = driver.findElement(By.cssSelector("#signInForm > div.mb-3 > div")).getText(); // 要是获取的提示不正确报错 assert "用户名不能为空".equals(text); // 2. 输入不存在的用户名 driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password")).clear(); driver.findElement(By.cssSelector("#username")).sendKeys("zhang"); driver.findElement(By.cssSelector("#password")).sendKeys("123456"); driver.findElement(By.cssSelector("#submit")).click(); // 从提示弹窗中获取文本 String text2 = getTestFromAler("/html/body/div[2]/div[1]"); // 要是获取的提示不正确报错 assert "用户不存在".equals(text2); // 3. 输入错误的密码 driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password")).clear(); driver.findElement(By.cssSelector("#username")).sendKeys("zhangsan"); driver.findElement(By.cssSelector("#password")).sendKeys("666"); driver.findElement(By.cssSelector("#submit")).click(); // 从提示弹窗中获取文本 String text3 = getTestFromAler("/html/body/div[2]/div[2]"); // 要是获取的提示不正确报错 assert "密码错误".equals(text3); } }
RegisterTest类:
用来自动化测试注册界面package tests; import common.Utils; import org.openqa.selenium.By; import java.util.UUID; public class RegisterTest extends Utils { // 注册界面的URL private static String url = "http://127.0.0.1:58080/sign-up.html"; public RegisterTest() { super(url); } // 检查元素是否存在 -- 不存在就报错 public void checkEleExist() { // 检查用户名输入框是否存在 driver.findElement(By.cssSelector("#username")); // 检查昵称输入框是否存在 driver.findElement(By.cssSelector("#nickname")); // 检查密码输入框是否存在 driver.findElement(By.cssSelector("#password")); // 检查确认密码是否存在 driver.findElement(By.cssSelector("#passwordRepeat")); // 检同意条款是否存在 driver.findElement(By.cssSelector("#policy")); // 检查注册按钮是否存在 driver.findElement(By.cssSelector("#submit")); // 检查图标是否存在 driver.findElement(By.cssSelector("body > div > div > div.text-center.mb-4 > img")); } // 测试注册成功 public void checkRegisterSuccess() { // 先清除输入框内容 inputBoxClear(); // 获取随机字符串,避免用户名重复 String userName = getUserName(); // 输入正确的用户名,昵称,密码与确认密码,勾选同意使用条款能否注册成功 driver.findElement(By.cssSelector("#username")).sendKeys(userName); driver.findElement(By.cssSelector("#nickname")).sendKeys(userName); driver.findElement(By.cssSelector("#password")).sendKeys("123456"); driver.findElement(By.cssSelector("#passwordRepeat")).sendKeys("123456"); driver.findElement(By.cssSelector("#policy")).click(); driver.findElement(By.cssSelector("#submit")).click(); // 注册成功后会跳转登录界面 看看能不能获取到登录页面的元素 String text = driver.findElement(By.cssSelector("body > div > div > div > div:nth-child(1) > div > div.card.card-md > div > h2")).getText(); // 要是获取的提示不正确报错 assert "用户登录".equals(text); } // 测试注册失败 public void checkRegisterFail() { // 注册用户名存在的用户 driver.findElement(By.cssSelector("#username")).sendKeys("zhangsan"); driver.findElement(By.cssSelector("#nickname")).sendKeys("张三"); driver.findElement(By.cssSelector("#password")).sendKeys("123456"); driver.findElement(By.cssSelector("#passwordRepeat")).sendKeys("123456"); driver.findElement(By.cssSelector("#policy")).click(); driver.findElement(By.cssSelector("#submit")).click(); String text = getTestFromAler("/html/body/div[2]/div[1]"); // 要是获取的提示不正确报错 assert "用户名已存在".equals(text); // 先清除输入框内容 inputBoxClear(); String userName = getUserName(); // 密码与确认密码不一样 driver.findElement(By.cssSelector("#username")).sendKeys(userName); driver.findElement(By.cssSelector("#nickname")).sendKeys(userName); driver.findElement(By.cssSelector("#password")).sendKeys("123456"); driver.findElement(By.cssSelector("#passwordRepeat")).sendKeys("111111"); driver.findElement(By.cssSelector("#submit")).click(); // 打印提示框信息 String text1 = driver.findElement(By.cssSelector("#signUpForm > div > div:nth-child(5) > div > div")).getText(); // 要是获取的提示不正确报错 assert "请检查确认密码".equals(text1); } // 用来清除注册界面输入框的内容 private void inputBoxClear() { driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#nickname")).clear(); driver.findElement(By.cssSelector("#password")).clear(); driver.findElement(By.cssSelector("#passwordRepeat")).clear(); } // 获取随机用户名 private String getUserName() { UUID uuid = UUID.randomUUID(); String username = String.valueOf(uuid); username = username.substring(1,8); return username; } }
HomePageTest类:
用来自动化测试首页package tests; import common.Utils; import org.openqa.selenium.By; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.Keys; import java.io.IOException; public class homePageTest extends Utils { public static String url = "http://127.0.0.1:58080/index.html"; public homePageTest() { super(url); } public void checkUnLogin() throws IOException { // 截图查看未登录访问首页的结果 screenshot("homePage","测试未登录访问首页"); } // 检查元素是否存在 -- 不存在就报错 public void checkEleExist() throws IOException { // 检查首页是否显示用户昵称 driver.findElement(By.cssSelector("#index_nav_nickname")); // 检查发布帖子按钮是否存在 driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post")); // 检查首页标签是否存在 driver.findElement(By.cssSelector("#article_list_board_title")); // 检查帖子是否存在 driver.findElement(By.cssSelector("#artical-items-body > div:nth-child(1) > div > div.col > div.text-truncate > a > strong")); // 检查板块是否存在 driver.findElement(By.cssSelector("#topBoardList > li:nth-child(2) > a > span.nav-link-title")); // 检查私信图标是否存在 driver.findElement(By.cssSelector("body > div.page > header.navbar.navbar-expand-md.navbar-light.d-print-none > div > div > div:nth-child(2) > div > a > svg")); // 截图查看首页的结果 screenshot("homePage","检查首页元素"); } // 测试能否成功发帖 public void checkPost() throws InterruptedException { // 点击发布帖子 driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post")).click(); // 写入帖子标题 driver.findElement(By.cssSelector("#article_post_title")).sendKeys("自动化测试标题"); // tab键跳转到输入内容的位置 driver.findElement(By.cssSelector("#article_post_title")).sendKeys(Keys.TAB); // 输入帖子内容 driver.switchTo().activeElement().sendKeys("自动化测试内容"); // 等待50ms到输入帖子内容 Thread.sleep(50); // 由于发布帖子按钮在页面下需要使用模拟使用鼠标滚轮滚到底部 ((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)"); // 强制等待0.5让页面滚到底部 Thread.sleep(500); // 点击发布 driver.findElement(By.cssSelector("#article_post_submit")).click(); // 发送成功后会跳转到首页,获取首篇帖子标题看是否一致 String title = driver.findElement(By.cssSelector("#artical-items-body > div:nth-child(1) > div > div.col > div.text-truncate > a > strong")).getText(); // 要是获取的提示不正确报错 assert "自动化测试标题".equals(title); } // 测试没有标题能否成功发帖 public void checkPostNotTitle() throws InterruptedException { // 点击发布帖子 driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post")).click(); driver.findElement(By.cssSelector("#article_post_title")); // tab键跳转到输入内容的位置 driver.findElement(By.cssSelector("#article_post_title")).sendKeys(Keys.TAB); // 输入帖子内容 driver.switchTo().activeElement().sendKeys("自动化测试内容"); // 等待50ms到输入帖子内容 Thread.sleep(50); // 由于发布帖子按钮在页面下需要使用模拟使用鼠标滚轮滚到底部 ((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)"); // 强制等待0.5让页面滚到底部 Thread.sleep(500); // 点击发布 driver.findElement(By.cssSelector("#article_post_submit")).click(); String text = getTestFromAler("/html/body/div[4]/div[1]"); // 要是获取的提示不正确报错 assert "请输入帖子标题".equals(text); } // 测试没有内容能否成功发帖 public void checkPostNotContent() throws InterruptedException { // 点击发布帖子 driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post")).click(); // 写入帖子标题 driver.findElement(By.cssSelector("#article_post_title")).sendKeys("自动化测试标题"); // 由于发布帖子按钮在页面下需要使用模拟使用鼠标滚轮滚到底部 ((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)"); // 强制等待0.5让页面滚到底部 Thread.sleep(500); // 点击发布 driver.findElement(By.cssSelector("#article_post_submit")).click(); String text2 = getTestFromAler("/html/body/div[4]/div[1]"); // 要是获取的提示不正确报错 assert "请输入帖子内容".equals(text2); } // 测试点击帖子能否跳转到帖子详情 public void checkPostSkip() { // 点击帖子 driver.findElement(By.cssSelector("#artical-items-body > div:nth-child(2) > div > div.col > div.text-truncate > a > strong")).click(); // 每个帖子都有一个点赞按钮 // 获取点赞按钮 存在 -- 成功 不存在 -- 报错 driver.findElement(By.cssSelector("#details_btn_like_count")); } }
4.性能测试
使用 jmeter 对登录页面,论坛首页,帖子详情页进行梯度压力测试
配置 100 个线程,每 2s 增加 10 个线程对页面进行测试梯度压力测试
响应时间:
通过折线图可以看出随着线程数的增加系统的响应时间越来越长。
TPS(每秒事务数):
通过折线图可以看出系统的TPS随线程数的增加而增加一段时间后再一段区间波动
汇总报告:
通过汇总报告可以看出性能测试整体数据的变化
运行命令生成性能测试报告:
JMeter测试报告是⼀个全面而详细的文档,它提供了关于测试执行结果的详细信息。