目录
一.项目简介
1.1项目概述
本项目名为知识殿堂,Web网站程序,采取了前后端分离的方法实现,使用MYSQL数据库存储用户数据以及文章数据,同时结合Redis记录当前的Seesion会话。
本项目的前端页面由八个页面构成:登陆页面、注册页面、公共博文列表、搜索页面、列表页面、个人中心页面、文章发表页面以及文章详情页面。 后端实现的功能有:验证码生成、用户登陆、退出登陆、修改密码、强制登陆、发表(修改、删除)文章,文章搜索,上传头像、修改个人信息等功能。
1.2测试目的
本次测试的目的主要在于以下几个方面:
-
功能验证: 测试用于验证个人博客的基本功能是否按预期工作。这包括用户能否正确注册、登录、发布文章、搜索功能,修改个人信息等核心功能。通过Selenium 等自动化测试工具可以确保这些功能的正确性和稳定性。
-
性能评估: 测试可以用于评估个人博客在不同负载条件下的性能表现。通过压力测试和性能测试,可以确定博客系统在同时处理多少用户、同时发布文章或评论时的响应时间和资源消耗情况。这有助于优化系统,确保其在高负载下依然能够保持稳定性和响应速度。
-
安全性检查: 测试用于评估个人博客的安全性。这包括检查用户认证和授权机制是否正确实现,以防止未授权访问和数据泄露。此外,还可以通过安全测试评估系统是否容易受到常见的安全攻击,如密码破解、文章等。
1.3测试范围
- 单元测试:针对代码中最小的可测试单元(如函数、方法)进行的测试。在个人博客项目中,可以编写单元测试来验证各个模块、服务、控制器等的功能是否按预期工作。例如,验证用户注册、登录逻辑的正确性,文章发布、获取文章列表等功能的各个边界条件和异常情况的处理。
-
集成测试: 集成测试是测试不同模块之间的集成,以验证它们在一起工作时是否如预期。在个人博客项目中,集成测试可以确保整个系统的各个组件(如数据库、服务、API等)在协同工作时没有问题。例如,测试用户管理模块与文章发布模块之间的集成,确保用户发布文章时能够正确保存到数据库并展示在博客页面上。
-
功能测试: 功能测试是测试整个功能或者用户场景的测试,以验证系统是否能够按照用户需求正常工作。在个人博客项目中,功能测试可以覆盖用户注册登录流程、文章发布与编辑流程、评论管理、用户权限管理等核心功能。这些测试通常模拟用户的实际操作,检查系统是否能够正确响应和处理用户的各种输入和操作。
二.测试环境
2.1开发环境
开发工具:jdk8.0版本、IntelliJ IDEA、linux云服务器
本项目的开发技术栈:SpringBoot2.0+MySQL数据库5.8+MyBatis-Plus+Redis
SpringBoot技术:开源的Java框架,用于快速构建基于Spring的应用程序。它简化了Spring应用的配置和部署,提供了各种开箱即用的功能,如自动配置、内嵌服务器等。
MySql数据库:关系型数据库管理系统,广泛用于Web应用程序的数据存储。它支持标准的SQL语法,具有良好的性能和稳定性,能够处理大量数据和复杂的查询。
MyBatis-Plus:MyBatis 的增强工具包,封装了通用的 CRUD 操作(增删改查),通过继承 BaseMapper
接口或者使用 IService
接口的默认实现,可以直接使用基础的 CRUD 方法,无需编写 XML 映射文件。
Redis:高性能的开源键值对存储数据库,广泛用于缓存、会话管理、消息队列等场景。它支持丰富的数据结构(如字符串、哈希、列表、集合等),并提供了各种操作这些数据结构的API。
2.2测试工具
测试工具:selenium、Loadrunner、JUnit、Postman工具
-
Selenium:
- 用途:主要用于自动化Web应用程序的测试。
- 特点:支持多种浏览器和操作系统,可以模拟用户在浏览器中的操作,如点击、输入文本等,适合于功能测试和回归测试。
-
Loadrunner:
- 用途:用于进行性能测试,可以模拟多种用户同时访问系统,评估系统的负载能力和性能表现。
- 特点:支持多种协议(如HTTP、HTTPS、Web Services等),能够生成大量的虚拟用户,通过监控和分析来识别系统的瓶颈和性能问题。
-
JUnit:
- 用途:是一个Java编程语言的单元测试框架,用于编写和运行重复测试。
- 特点:提供了断言功能和测试运行器,可以方便地组织和执行单元测试,是Java开发中常用的测试工具之一。
-
Postman:
- 用途:主要用于API测试和开发的工具,支持发送HTTP请求并查看响应。
- 特点:提供了友好的用户界面,可以创建和组织请求集合,支持环境变量和脚本编写,方便进行接口测试、自动化测试和性能测试的初步验证。
- Fiddler:网络调试工具,主要用于捕获、检查和修改 HTTP 流量。
三.功能展示
1.登陆-注册功能
由于我添加了验证码验证码,无法提出图片中的数字,因此我登陆页面我将无法自动化测试(技术不足的原因)。
注册功能,输入用户名和密码,会随机生成用户ID(唯一),以用户ID和密码才能登陆。
2.博客列表
如上图,登陆之后的页面当中,是存在搜索功能的,也可以查看文章和写博客的能力。而未登陆,只有一个列表,且无法查看。
3.个人中心
个人中心如上图,内部可以修改个人信息,密码,还有点击头像可以更换。
4.发布文章
可以选择指定的类型进行发表。
5.查看文章
如图,文章详情列表当中含有发布时间,以及访问量!
四.自动化功能测试
如上图,我们需要进行测试的模块。
4.1测试准备阶段
新建立一个Maven项目,引入依赖(selenium4、junit5等):
<!-- 添加selenium依赖-->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.0.0</version>
</dependency>
<!-- 添加junit5依赖-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.8.2</version>
<scope>test</scope>
</dependency>
创建驱动和释放类
创建一个驱动类,负责运行自动化Web应用程序,可继续接下来的操作:模拟用户在浏览器中的操作。同时我们将释放驱动也添加在同一个类当中。并设置驱动对象为静态的,就可以让创建和销毁驱动的步骤执行一次,其他类如果需要驱动直接继承该类。
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class Init {
private ChromeOptions options;
private WebDriver driver;
private Chrome chrome ;
@BeforeAll
static void SetUp() {
chrome = new Chrome();
options = new ChromeOptions();
options.setBinary("D:\\Chrome\\chrome.exe");
options.addArguments("--remote-allow-origins=*");
driver = new ChromeDriver(options);
}
@AfterAll
static void TearDown() {
driver.quit();
}
}
创建CSV文件操作类
public class CSVFileReader {
private static final String CSV_FILE_NAME = "User.csv";
private static long nextId = 1;
//读取资源
public List<User> readCSVFile() {
List<User> t1= new ArrayList<>();
try {
// 加载 CSV 文件资源
ClassPathResource resource = new ClassPathResource("User.csv");
// 获取文件的输入流
InputStream inputStream = resource.getInputStream();
// 使用 BufferedReader 逐行读取文件
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
boolean headerLine = true; // 标记是否为首行(表头)
while ((line = reader.readLine()) != null) {
if (headerLine) {
headerLine = false;
continue; // 跳过表头行
}
// 分割 CSV 行数据
String[] fields = line.split(",");
// 解析数据
if (fields.length >= 3) {
String id = fields[0].trim();
String username = fields[1].trim();
String password = fields[2].trim();
// 创建 Person 对象并添加到列表中
// 获取当前行的 ID 最大值
long currentId = Long.parseLong(id);
if (currentId >= nextId) {
nextId = currentId + 1; // 更新下一个可用的 ID
}
User user = new User(username,password);
t1.add(user);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return t1;
}
//写入资源
public void writerCSVFile(String username,String password) throws IOException {
ClassPathResource resource = new ClassPathResource(CSV_FILE_NAME);
File file = resource.getFile();
// 追加新数据到 CSV 文件
FileWriter writer = new FileWriter(file, true);
try (BufferedWriter bw = new BufferedWriter(writer)) {
long id = nextId++;
// 组装新数据行
String csvLine = String.format("%d,%s,%s\n", id, username, password);
// 写入到文件
bw.write(csvLine);
}
}
}
注:可能之后的你的保存路径和我不一样!
4.2博客注册-登陆测试
- 创建博客注册登陆测试类(Blog_LoginReg)继承Init类,获得驱动。
- 创建注册方法,将注册好的用户信息,通过element组件的方式保存在csv文件里。
- 创建登陆方法,将csv当中的信息作为传递值,看看是否可以传递
- 类上使用@TestMethodOrder(MethodOrderer.OrderAnnotation.class)注解,然后通过@Order注解设定运行顺序,一次性运行,判断是否出错。
注册测试方法:
@Order(1)
@Test
void RegSuccess() throws IOException, InterruptedException {
for(int i=0; i < 10;i++){
driver.get("http://127.0.0.1:8080/reg.html");
Random random = new Random();
String password = new String();
for(int k =0;k < 10;k++){
int n = random.nextInt(10);
password = password + n;
}
String username = "tq"+random.nextInt(100);
driver.findElement(By.cssSelector("#username")).sendKeys(username);
driver.findElement(By.cssSelector("#password1")).sendKeys(password);
driver.findElement(By.cssSelector("#password2")).sendKeys(password);
sleep(3000);
driver.findElement(By.cssSelector("#submit")).click();
//注册的数据保存在csv当中
csvFileReader.writerCSVFile(username,password);
}
}
会自动创建十个账户和密码,然后保存在自定义的csv文件当中,便于我们之后的登陆测试!
登陆测试
@Order(2)
@Test
void LoginSuccess() throws InterruptedException {
List<User> t1 = csvFileReader.readCSVFile();
for(User user : t1){
driver.get("http://127.0.0.1:8080/login.html");
driver.findElement(By.cssSelector("#username")).sendKeys(user.getName().trim());
driver.findElement(By.cssSelector("#password")).sendKeys(user.getPassword().trim());
driver.findElement(By.cssSelector("#submit")).click();
sleep(3000);
}
}
使用保存在csv文件里的数据,用于登陆测试,操作成功!
4.3博客列表测试
当列表中文章数目为0时,通过断言判断,从而停止;若不为0,则获取所有“ 查看文章” 的按钮,然后使用方法click()点击,再使用方法.navigate().back()返回到上一级目录,重复进行!
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class Blog_List extends Init {
CSVFileReader csvFileReader = new CSVFileReader();
@Order(1)
@Test
void ListSuccess() {
driver.get("http://127.0.0.1:8080/login.html");
int size = driver.findElements(By.cssSelector(".title")).size();
//如果为空,则断言不通过,文章数为0
Assertions.assertNotEquals(0, size);
List<WebElement> detailLinks = driver.findElements(By.cssSelector("a.detail"));
for (WebElement link : detailLinks) {
link.click();
driver.navigate().back();
}
}
}
4.4个人中心测试
个人中心测试:登陆-->主页-->我的,然后开始测试:1.查看、编辑和删除自己的文章。2.点击头像,更换!3.点击个人设置,然后修改密码+修改个人信息
1.查看、编辑和删除自己的文章。通过断言,判断是否成功!
@Order(2)
@Test
void articleSuccess() {
//查看文章
List<WebElement> detailLinks = driver.findElements(By.cssSelector("#createDiv > div:nth-child(n) > div.san > a:nth-child(1)"));
for (WebElement link : detailLinks) {
link.click();
driver.navigate().back();
}
//修改文章
List<WebElement> updateLinks = driver.findElements(By.cssSelector("#createDiv > div:nth-child(n) > div.san > a:nth-child(2)"));
for (WebElement link : detailLinks) {
link.click();
WebElement element = driver.findElement(By.cssSelector("#title"));
//将每一篇标题的末尾添加tq02
element.sendKeys(element.getAttribute("value")+"tq02");
driver.findElement(By.xpath("/html/body/div[2]/div[1]/button")).click();
driver.switchTo().alert().accept();
}
List<WebElement> SelectLinks = driver.findElements(By.cssSelector("#createDiv > div:nth-child(n) > div.san > a:nth-child(1)"));
for (WebElement link : detailLinks) {
link.click();
//判断是否添加了"tq02"
Assertions.assertTrue(driver.findElement(By.cssSelector("#title")).getText().endsWith("tq02"));;
driver.navigate().back();
}
//删除文章
String str = driver.findElement(By.cssSelector("#createDiv > div:nth-child(1) > div.title")).getText();
driver.findElement(By.cssSelector("#createDiv > div:nth-child(1) > div.san > a:nth-child(3)")).click();
// 进行断言
Assertions.assertNotEquals(driver.findElement(By.cssSelector("#createDiv > div:nth-child(1) > div.title")).getText(), str, "元素内容不符合预期");
}
2..点击头像,更换!
@Order(1)
@Test
void PhotoSuccess() {
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));
//点击头像
driver.findElement(By.cssSelector("#photo"));
//点击更换头像
driver.findElement(By.cssSelector("#myDialog2 > div > div > button:nth-child(1)"));
//上传头像
WebElement uploadInput =driver.findElement(By.xpath("//*[@id=\"photo1\"]"));
uploadInput.sendKeys("//本地照片路径");
driver.findElement(By.cssSelector("#myDialog2 > div > div > button:nth-child(2)"));
}
3.个人信息修改
@Order(3)
@Test
void InfoSuccess() throws InterruptedException {
//修改密码
driver.findElement(By.cssSelector("body > div.nav > div > div > div > button:nth-child(1)"));
driver.findElement(By.cssSelector("#password1")).sendKeys("12345");
driver.findElement(By.cssSelector("#password2")).sendKeys("54321");
driver.findElement(By.cssSelector("#password3")).sendKeys("54321");
driver.findElement(By.cssSelector("#myDialog > div > div.row1 > button:nth-child(1)")).click();
//测试是否注销,重新登陆
driver.findElement(By.cssSelector("body > div.nav > div > div > div > a")).click();
Assertions.assertEquals(driver.getCurrentUrl(),"http://127.0.0.1:8080/login.html","未返回登陆页面");
driver.findElement(By.cssSelector("#username")).sendKeys("tq02");
driver.findElement(By.cssSelector("#password")).sendKeys("54321");
driver.findElement(By.cssSelector("#submit")).click();
//登陆成功之后,修改用户信息
driver.get("http://127.0.0.1:8080/myblog_list.html");
driver.findElement(By.cssSelector("body > div.nav > div > div > div > button:nth-child(3)"));
driver.findElement(By.cssSelector("#username")).sendKeys("TQ01");
driver.findElement(By.cssSelector("#git")).sendKeys("tq.com");
driver.findElement(By.cssSelector("#myDialog3 > div > div.row1 > button:nth-child(1)")).click();
//验证用户名是否更改
Assertions.assertTrue(driver.findElement(By.cssSelector("#article")).getText().endsWith("tq01"));;
}
4.5博客发表测试
在这个界面下分别对打开网页是否正常、测试输入标题和正文博客是否可以正常发布、测试“写博客”按钮是否可以正常使用。
@Order(1)
@Test
public void issuance(){
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();
driver.findElement(By.cssSelector("#title")).sendKeys("自动化测试01");
driver.findElement(By.xpath("//*[@id=\"editorDiv\"]/div[1]/div[6]")).sendKeys("我爱自动化测试啊");
driver.findElement(By.xpath("//*[@id=\"editorDiv\"]/div[1]/div[6]")).sendKeys("我爱自动化测试啊");
driver.findElement(By.xpath("//*[@id=\"synopsis\"]")).sendKeys("简介");
// 找到下拉框元素
WebElement dropdown = driver.findElement(By.id("@mySelect"));
// 创建 Select 对象
Select select = new Select(dropdown);
select.selectByIndex(1);
//发布文章
driver.findElement(By.xpath("/html/body/div[2]/div[1]/button")).click();
driver.switchTo().alert().dismiss();
//验证是否发布成功
Assertions.assertEquals("自动化测试01",driver.findElement(By.xpath("//*[@id=\"createDiv\"]/div[1]/div[1]")).getText(),"标题不一样错误");
Assertions.assertEquals("简介",driver.findElement(By.xpath("//*[@id=\"createDiv\"]/div[1]/div[3]")).getText(),"简介不一样错误");
}
4.6自动化集成
以上便是所有功能自动化测试,但是很明显我将这些功能写在不同的类,甚至不同的包中当中,难道我还得一个一个类进行测试吗?代码岂不是有冗余?因此我们可以使用JUnit 测试套件。配合@RunWith(JUnitPlatform.class)和@SelectClasses({包名.类名.class})
@Suite
@SelectClasses({Blog_loginreg.class,Blog_List.class, Article.class,Article.class})
//若不同包下的测试类同名的情况:
// @SelectClasses({
// com.example.package1.TestClassA.class,
// com.example.package2.TestClassA.class
//})
//第二种方法-扫描整个包下的测试类:
@SelectPackages(value={"包名", "包名"})
public class MyTestSuite {
}
4.7自动化测试总结
特点:根据个人项目来设计的测试用例,然后根据测试用例使用selenium4自动化测试工具和junit5单元测试框架结合来实现web自动化测试的(功能、步骤、技术一定要明确)
亮点:
-
强大的浏览器控制能力: Selenium 4 提供了更新和改进后的浏览器控制功能,包括支持最新的浏览器版本和更稳定的 WebDriver API。这使得你可以更精确地模拟用户操作和测试各种 web 应用程序。
-
异步测试支持: JUnit 5 支持异步测试方法,这与 Selenium 4 的异步特性(如异步执行命令和等待条件)非常契合。这样可以更有效地处理页面加载、AJAX 请求等异步操作,提高测试的效率和可靠性。
-
丰富的断言和校验功能: JUnit 5 提供了丰富的断言库,可以用来验证页面元素的状态、属性和行为。结合 Selenium 4 的元素定位和操作功能,可以轻松编写详细而清晰的测试断言,确保应用程序的正确性。
-
参数化测试: JUnit 5 支持参数化测试,可以通过不同的参数运行相同的测试方法。这对于测试不同数据集或不同配置的 web 应用程序非常有用,可以通过多次运行相同的测试方法来覆盖更多的场景。
-
并发执行: JUnit 5 具有更好的并发执行支持,可以并行运行测试方法,从而缩短整体测试时间。这与 Selenium 4 的多线程执行能力非常匹配,尤其在大型测试套件或需要快速反馈的场景下,能够显著提升效率。
-
容易集成和扩展: JUnit 5 设计良好,支持插件和扩展,能够与各种 CI/CD 工具和报告生成工具集成。结合 Selenium 4 的稳定的 API 和丰富的第三方库支持,可以实现高度定制化的自动化测试框架,满足特定需求和复杂场景的测试要求。