目录
一、项目背景
- 为了满足个人和小型团队快速搭建博客的需求,我开发了一个简易博客系统。该系统采用轻量级技术栈,注重易用性和快速部署,让用户无需复杂配置即可轻松发布和管理文章。支持Markdown写作,同时保持代码简洁、易于二次开发。无论是技术分享、生活记录还是知识整理,这个博客系统都能提供简单高效的解决方案。
- 项目使用了前后端分离的方法实现,前后端数据使用ajax进行交互,后端使用 Spring MVC 和 MyBatis 进行开发。
- 该项目一共有四个页面:博客登陆页、博客列表页、博客详情页、博客编辑页。一共实现了登录、注销、发布博客、编辑博客、删除博客、拦截器强制登录等功能。
二、功能简介
本项目一共实现了登录、注销、发布博客、编辑博客、删除博客、拦截器强制登录等功能:
- 登录功能:本项目是以个人和小型团队为目标群体进行开发的,因此没有做注册功能,用户的用户名和密码是已经存在的写在数据里的。当用户正确输入用户名和密码时,点击登录即可跳转至博客列表页。用户在未登录状态下,点击主页和写博客中的“发布文章”则会被拦截至登录页面。
- 注销功能:登录后,页面导航栏区会出现注销按钮,点击注销则会删除user_token信息,想要再次访问其他页面时由于token验证失败则会拦截至登录页进行重新登陆。
- 发布/编辑博客:未登录状态下,进入写博客页面可以正常编辑文章内容,但是点击发布文章时会发布失败并且提醒当前状态未登录。登录状态下,发布博客必须确保标题和内容不为空才可以正常发布。发布成功会跳转至博客列表页,在博客列表页可以看到博客标题、发布时间以及博客内容。
- 删除/编辑博客:用户在博客列表页可以看到团队成员发布的博客信息,点击博客即可进入博客详情页,进入博客详情页后,后端实现了获取作者信息的功能,前端进行验证当前登录用户是否为作者,如果当前登录用户是该文章的作者,则前端页面会在文章下面展示出“编辑”和“删除”按钮,反之则不会展示,只能浏览不能操作。
三、测试分类
3.1 功能测试
1)测试用例
2)功能测试结果
1.登录成功:输入正确的用户名和密码,点击登录,跳转至博客列表页
结果与预期结果一致 。
登录失败:输入错误的用户名或密码、用户名或密码为空都会登录失败,跳转至loginerr页
结果与预期结果一致。
2. 写博客:输入完整的标题和内容,点击发表文章,发布成功-跳转至博客列表页
结果与预期结果一致。
标题为空或者内容为空都无法发布
结果与预期结果一致。
3. 编辑博客/删除博客:只有身份匹配才会出现按钮
结果与预期结果一致。
点击删除则会把这篇标题为“Hello World”的博客删除并返回博客列表页
结果与预期结果一致。
4.注销:点击“注销”跳转至博客登录页
结果与预期结果一致。
5.测试结果:测试用例100%通过
3.2 自动化测试
1)自动化测试覆盖模块
该项目一共有四个页面:博客登陆页、博客列表页、博客详情页、博客编辑页。一共实现了登录、注销、发布博客、编辑博客、删除博客、拦截器强制登录等功能。
本次自动化测试覆盖了:博客登陆页、博客列表页、博客详情页、博客编辑页。
2)编写Web测试用例
3)代码编写
1. 首先在pom.xml文件中导入所需的依赖:
<dependencies>
<!--驱动管理-->
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<!--selenium库-->
<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>
2.有一些代码需要频繁使用所以创建公共配置类Utils:接下来创建的测试类均继承该类。
package common;
import io.github.bonigarcia.wdm.WebDriverManager;
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.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;
public class Utils {
public static WebDriver driver;
public static 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(2));
}
return driver;
}
public Utils(String url) {
//调用driver对象
driver = createDriver();
//访问url
driver.get(url);
}
public void getScreenShot() throws IOException {
/*保存格式:
* ./src/test/image/
* /2025-01-01/
* /test01-082530.png
* /test02-082530.png
* /2025-01-02/
* /test01-082530.png
* /test02-082530.png
* */
String callerMethodName = Thread.currentThread().getStackTrace()[2].getMethodName();
// 屏幕截图
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/image/2025-01-01/test01-082530.png
String filename = "./src/test/image/" + dirTime + "/" + callerMethodName + "-" + fileTime + ".png";
File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
//srcFile放到指定的位置
FileUtils.copyFile(srcFile, new File(filename));
}
}
3.创建各个页面的测试类:
博客登陆页 LoginPage:
package tests;
import common.Utils;
import org.openqa.selenium.By;
import java.io.IOException;
public class LoginPage extends Utils {
public static String url = "http://127.0.0.1:8080/blog_login.html";
public LoginPage() {
super(url);
}
/*
检查页面是否加载成功
*/
public void loginPageRight() throws InterruptedException {
//通过查看页面元素是否存在来检查页面加载成功与否
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));
//登录输入框
driver.findElement(By.cssSelector("body > div.container-login > div"));
}
//检查登录功能---成功登录
public void loginSuc() {
//确保输入框内没有内容
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#username")).sendKeys("闻人翊悬");
driver.findElement(By.cssSelector("#password")).sendKeys("123456");
driver.findElement(By.cssSelector("#submit")).click();
//检查点击登录之后是否登录成功
driver.findElement(By.cssSelector("body > div.container > div.left > div"));
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)"));
//页面标题来检查是否登录成功
String title = driver.getTitle();
assert title.equals("博客列表页"); //断言需要配置 -ea -Dfile.encoding=UTF-8
driver.navigate().back();
}
//检查登录功能---登录失败
public void loginFail() throws IOException {
//确保输入框内没有内容
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#password")).clear();
//driver.navigate().refresh(); //也可以通过刷新实现
driver.findElement(By.cssSelector("#username")).sendKeys("闻人翊悬");
driver.findElement(By.cssSelector("#password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//登录失败会跳转到登录失败页面
String res = driver.findElement(By.cssSelector("body")).getText();
getScreenShot();
assert res.equals("用户名或密码错误!");
}
}
博客列表页 ListPage:
package tests;
import common.Utils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import java.io.IOException;
public class ListPage extends Utils {
public static String url = "http://127.0.0.1:8080/blog_list.html";
public ListPage() {
super(url);
}
//登录状态下---访问列表页面
public void listByLogin() throws IOException {
//检查元素
driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a"));
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.date"));
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)"));
getScreenShot();
}
//未登录状态下---访问列表页面
public void listPageByNoLogin() throws IOException {
//列表页未登录
//处理弹窗警告
Alert alert = driver.switchTo().alert();
alert.accept();
//跳转到登录页面
String expect = driver.getTitle();
getScreenShot();
assert expect.equals("博客登陆页");
}
}
博客编辑页 EditPage:
package tests;
import common.Utils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import java.io.IOException;
public class EditPage extends Utils {
public static String url = "http://127.0.0.1:8080/blog_edit.html";
public EditPage() {
super(url);
}
//写博客成功
public void editSuc() throws IOException, InterruptedException {
//填写标题
driver.findElement(By.cssSelector("#title")).sendKeys("AutoTest01");
//因为输入框使用的是第三方插件markdown,所以不能使用sendKeys输入内容
//方法一:填写内容时,有默认内容可以不再填写
//方法二: 通过键鼠操作来实现
/*
1.鼠标先挪动到博客内容区域
2.双击鼠标将内容删掉:鼠标双击内容+键盘DELETE
3.输入内容
*/
// WebElement ele = driver.findElement(By.cssSelector("#editor > div.CodeMirror.cm-s-default.CodeMirror-wrap.CodeMirror-empty"));
// Actions actions = new Actions(driver);
// //perform作用:为了在页面看到效果
// Thread.sleep(3000);
// actions.doubleClick(ele).perform();
// Thread.sleep(3000);
// actions.keyDown(Keys.DELETE).perform();
// actions.keyDown(ele,Keys.DELETE).perform();
// Thread.sleep(3000);
// actions.moveToElement(ele).sendKeys("键盘鼠标操作输入博客内容").perform();
// Thread.sleep(3000);
getScreenShot();
//点击发布
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(1000);
//检查
String title = driver.getTitle();
assert title.equals("博客列表页");
}
//未登录状态下,写博客不会发表
public void editPageByNoLogin() throws IOException {
driver.findElement(By.cssSelector("#title")).sendKeys("博客标题");
driver.findElement(By.cssSelector("#submit")).click();
//处理弹窗警告
Alert alert = driver.switchTo().alert();
alert.accept();
//跳转到登录页面
String expect = driver.getTitle();
getScreenShot();
assert expect.equals("博客登陆页");
}
}
博客详情页 DetailPage:
package tests;
import common.Utils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import java.io.IOException;
public class DetailPage extends Utils {
public static String url = "http://127.0.0.1:8080/blog_detail.html";
public DetailPage() {
super(url);
}
//登录状态下详情页可以打开,不加 blogId则会 弹窗:内部错误,请联系管理员
public void detailSucNoId() throws IOException {
//处理弹窗警告
Alert alert = driver.switchTo().alert();
alert.accept();
String title = driver.getTitle();
assert title.equals("博客详情页");
getScreenShot();
//返回主页
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")).click();
}
//登录状态下详情页可以打开,并且加了 blogId
public void detailSucById() throws IOException {
driver.get("http://127.0.0.1:8080/blog_detail.html?blogId=1");
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title"));
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.date"));
driver.findElement(By.cssSelector("#detail > p"));
getScreenShot();
}
//未登录状态下,访问博客详情页会拦截,跳转至登录页
public void detailNoLogin() throws IOException {
driver.findElement(By.cssSelector("body > div.container-login > div"));
String title = driver.getTitle();
assert title.equals("博客登陆页");
getScreenShot();
}
}
RunTest:
import common.Utils;
import tests.*;
import java.io.IOException;
public class RunTest {
public static void main(String[] args) throws InterruptedException, IOException {
// 未登录时自动化测试
ListPage listPage = new ListPage();
listPage.listPageByNoLogin();
EditPage editPage = new EditPage();
editPage.editPageByNoLogin();
DetailPage detailPage = new DetailPage();
detailPage.detailNoLogin();
//登录状态下 自动化测试
LoginPage loginPage = new LoginPage();
loginPage.loginPageRight();
loginPage.loginSuc();
loginPage.loginFail();
ListPage listPage1 = new ListPage();
listPage1.listByLogin();
EditPage editPage1 = new EditPage();
editPage1.editSuc();
DetailPage detailPage1 = new DetailPage();
detailPage1.detailSucNoId();
detailPage1.detailSucById();
Utils.driver.quit();
}
}
代码仓库地址:https://gitee.com/nan-yiriel-l/test/tree/master/code/BlogAutoTest
4)结果分析
本次代码自动化测试用例数量:10
自动化测试结果:pass:10/10、fail:0/10
自动化测试时遇到的问题及解决办法:
- 登录状态的测试用例和未登录状态的测试用例穿插运行,导致部分测试用例之间衔接不上。解决方法是注意测试用例间的先后执行顺序
- UnexpectedAlertPresentException异常。原因是没有处理弹窗,导致后续操作无法进行。解决方法是在进行后续操作之前先处理弹窗
3.3 测试总结
本次测试针对博客系统的核心功能进行了验证测试,包括用户登录、文章发布、文章编辑、文章删除、用户注销、强制登录登。通过功能测试,确认系统业务流程符合预期,界面交互流畅,关键功能点均能正常运行。同时,采用自动化测试覆盖了主要功能模块,确保系统稳定性和可靠性。整体测试结果表明,该博客系统主要功能完整、运行稳定,具备良好的用户体验和可维护性,已达到预期发布标准。