目录
设计测试用例
功能测试
登录功能测试
正常登录
我们以用户名: xiaobai 密码: 123456来进行登录
输入用户名和密码,点击注册按钮后,弹出弹窗登录成功, 处理完弹窗之后,成功进入博客列表页面
异常登录
用户名为空,密码正确
用户名错误,密码错误
用户名正确,密码为空
用户名正确,密码错误
主要的处理逻辑:
客户端把用户的输入的数据通过ajax请求传给服务端
服务端对数据进行逻辑判断,然后把处理结果返回给客户端
客户端再对返回结果进行判断,然后展示给用户
博客列表功能测试
登录情况
显示用户基本信息
1> 用户头像
2> 用户名称
3> 博客总数量
4> 文章分类
显示所有博客信息
1> 博客名称
2> 博客发布时间
3> 博客内容
4> 查看全文按钮
显示其他信息
左上角
1> 博客系统图标
2> 博客系统名称
右上角
1> 主页按钮
2> 写博客按钮
3> 注销按钮
非登录情况
因为没有登录,直接访问博客列表页,直接跳转到登录页面
非登录情况下访问博客列表页跳转到登录页面的逻辑
编写拦截器
除了登录, html, css等页面,访问其他的页面都要进行拦截
前端接受到参数进行校验
因为没有登录,直接访问博客列表页,获取不到token,因此进行拦截
博客详情功能测试
登录情况
显示博客作者信息
1> 博客作者名称
2> 博客作者头像
3> 博客总数量
4> 文章分类
注意: 如果shi
显示博客详细信息
1> 博客标题
2> 博客发布时间
3> 博客具体内容(显示用户编辑博客的时候使用的样式)
4> 编辑按钮
5> 删除按钮
显示其他信息
左上角
1> 博客系统图标
2> 博客系统名称
右上角
1> 主页按钮
2> 写博客按钮
3> 注销按钮
注意如果登录的用户不是博客的作者是不会显示编辑和删除按钮的,并且作者信息的名字会显示作者名称.
非登录情况
因为没有登录,后端没有收到token,直接跳转到登录页面
博客编辑功能测试
登录情况
我们在登录用户就是博客作者的条件下,点击编辑按钮
编辑博客
显示博客编辑页面
1> 博客标题输入框
2> 博客内容输入框
3> 样式
4> 更新文章按钮
显示其他信息
左上角
1> 博客系统图标
2> 博客系统名称
右上角
1> 主页按钮
2> 写博客按钮
3> 注销按钮
写博客
点击右上角写博客按钮
显示写博客页面
1> 博客标题输入框
2> 博客内容输入框
3> 样式
4> 发布文章按钮
显示其他信息
左上角
1> 博客系统图标
2> 博客系统名称
右上角
1> 主页按钮
2> 写博客按钮
3> 注销按钮
非登录情况
能够编辑博客页面,但是点击发布文章会直接跳转到登录页面
其他功能测试
更新博客
注: 只能在用户是文章作者的情况下
点击编辑按钮,进入编辑页面,修改博客的标题/博客的内容/不修改
点击更新博客,回到博客列表页,显示我更新的博客
发布博客
在登录状态下,点击写博客,进入写博客页面
填入标题和内容,点击发布文章
跳转到博客列表页,显示刚刚发布的博客
删除博客
点击删除按钮,跳转到博客列表页,博客被删除
注销
点击注销,页面返回到登录页面
主页
注: 登录情况下
点击主页,回到博客列表页
在非登录页面点击主页按钮, 会直接跳转到登录页面
编辑博客的实现逻辑
客户端填写标题和内容,然后发送给服务端
客户端接收到序列化数据,并且通过一个博客对象进行接收,判断标题是否为空,然后加入到数据库,最后把添加成功的信息响应给前端
客户端接收到响应数据,并且跳转到博客列表页
然后博客列表页,把刚刚发布的博客从服务端获取信息,进行拼接,并且显示给用户
删除博客的逻辑
我们所做的删除,并不是把博客本身进行删除,而是一种逻辑删除.
我们此时可以观察到我们的博客列表只要一篇博客
但是我们的数据库数据不止一条
这时因为我们使用了delete_flag字段来进行标记,1表示删除,0表示没删除
我们服务端的代码在查询数据库的时候会根据这个作为条件,进行条件查询,排除掉delete_flag为1的数据,只返回为1的数据.
自动化测试
测试用例
我们使用selenium4自动化测试工具和junit4单元测试框架结合来对博客系统的登录页面、博客列表页面、博客编辑页、博客详情页进行自动化测试
引入依赖
<!-- 屏幕截图-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- 驱动管理-->
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.9.0</version>
</dependency>
<!-- selenium-->
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
测试目录结构
common里面是公共处理的类,test里面是测试类
Utils
创建驱动、保存现场截图、处理弹窗、登录成功进入列表页、
注意:在保存现场截图的时候命名是按时间来进行文件夹的划分,然后图片的名称要体现出测试类的类名,方便进行问题的追溯.
注意文件名的动态获取,注意时间格式的设置.
注意:可以在创建驱动的时候修改默认的有头模式or无头模式
package test_blog_system.common;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.SessionId;
import org.openqa.selenium.support.ui.ExpectedConditions;
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;
private static SessionId sessionId;
//创建驱动对象
public static WebDriver createDriver() {
if (driver == null || sessionId == null) {
//增加浏览器配置对象,创建驱动对象的时候要强制运行访问所有的链接
ChromeOptions options = new ChromeOptions();
//表示运行所有的链接
options.addArguments("--remote-allow-origins=*");
//设置无头模式
options.addArguments("-headless");
//添加浏览器策略p
// options.setPageLoadStrategy(PageLoadStrategy.NONE);//等待所有页面加载完成
//创建浏览器驱动对象,把配置放进驱动对象
driver = new ChromeDriver(options);
//添加隐式等待,全局元素等待2s
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
}
return driver;
}
//统一创建驱动对象
public Utils() throws InterruptedException {
//调用driver对象
driver = createDriver();
//访问url
driver.get("http://120.26.236.46:8080/blog_login.html");
}
//屏幕截图
public void getScreenShot(String str) throws InterruptedException, IOException {
//保存的图片路径: ./src/test/image/
// /2024-08-17/
// /test01-1743211.png
// /test02-1743222.png
// /test03-1743245.png
// /2024-08-18/
// /test01-1743222.png
// /test02-1743442.png
// /test03-1743332.png
//
// createDriver();
//屏幕截图
//设计文件日期格式
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/2024-08-17/test01-17432.png
String filename = "./src/test/image/" + dirTime + "/" + str + "-" + fileTime + ".png";//方法名-时间.png
File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
//把srcFile放到指定位置
FileUtils.copyFile(srcFile, new File(filename));
}
//统一用户登录成功
public static void loginSuccess() throws InterruptedException {
WebElement element = driver.findElement(By.cssSelector("#username"));
//输入用户名
element.sendKeys("lisi");
// Thread.sleep(1000);
//定位密码框
element = driver.findElement(By.id("password"));
element.sendKeys("123456");
// Thread.sleep(1000);
//点击登录按钮
element = driver.findElement(By.xpath("//*[@id=\"submit\"]"));
//点击操作
element.click();
//处理弹窗
// handleAlert();
//直接进入列表页面测试
Thread.sleep(1000);
}
//统一处理弹窗
public static void handleAlert() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(1));
wait.until(ExpectedConditions.alertIsPresent());
//处理弹窗
Alert alert = driver.switchTo().alert();
alert.accept();
}
}
登录测试
非登录页面统一处理PageByNoLogin
1. 创建驱动,并打开页面.
2. 测试未登录,直接进入博客列表是否会跳转到登录页面.
3. 测试未登录,直接进入博客详情页是否会跳转到登录页面.
4. 测试未登录, 直接进入博客编辑页是否会跳转到登录页面.
5. 关闭驱动.
package test_blog_system.test;
import org.springframework.stereotype.Component;
import test_blog_system.common.Utils;
import java.io.IOException;
//测试所有未登录状态各个页面的各个用例
@Component
public class PageByNoLogin extends Utils {
// public static String loginPageUrl = "http://120.26.236.46:8080/blog_login.html";
public static String editPageUrl = "http://120.26.236.46:8080/blog_update.html";
public static String listPageUrl = "http://120.26.236.46:8080/blog_list.html";
public static String detailPageUrl = "http://120.26.236.46:8080/blog_detail.html";
public PageByNoLogin() throws InterruptedException {
super();//调用父类,直接进入登录界面
}
public void ListPageByNoLogin() throws InterruptedException, IOException {
//进入博客列表页
driver.get(listPageUrl);
}
public void EditPageByNoLogin() throws InterruptedException {
driver.get(editPageUrl);
}
public void DetailPageByNoLogin() throws InterruptedException {
driver.get(detailPageUrl);
driver.quit();//这时测试的最后一个方法,因此要关闭驱动
// driver = null;//手动置为空
}
}
登录页面测试LoginPage
1. 创建驱动,并打开页面.
2. 输入账号未空, 密码不为空是否登录失败, 并且有弹窗提示.
3. 输入账号不为空, 密码为空是否登录失败, 并且有弹窗提示.
4. 输入账号为空, 密码为空是否登录失败, 并且有弹窗提示.
5. 输入账号错误, 密码正确是否登录失败, 并且有弹窗提示.
6. 输入账号正确, 密码错误是否登录失败, 并且有弹窗提示.
7. 输入账号正确, 密码正确是否登录成功.
8. 关闭驱动
package test_blog_system.test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.springframework.stereotype.Component;
import test_blog_system.common.Utils;
@Component
public class LoginPage extends Utils {
public static String loginPageUrl = "http://120.26.236.46:8080/blog_login.html";
public LoginPage() throws InterruptedException {
super();
}
private static WebElement element = null;
public void LoginRight() throws InterruptedException {
//进入了登录页面
loginSuccess();
//进入列表页
driver.quit();
}
public void LoginFail() throws InterruptedException {
//返回登录界面
driver.get(loginPageUrl);
//TODO 账号未空,密码不为空
LocationAndSendKeysAndClick("", "123456");
//TODO 账号不为空,密码为空
LocationAndSendKeysAndClick("lisi", "");
//TODO 账号未空,密码为空
LocationAndSendKeysAndClick("", "");
//TODO 账号正确,密码不正确
LocationAndSendKeysAndClick("lisi", "123");
//TODO 账号错误,密码正确
LocationAndSendKeysAndClick("zhangsan", "123456");
//TODO 账号密码都错误
LocationAndSendKeysAndClick("zhangsan", "123");
Thread.sleep(1000);
}
//进行定位+点击的+清空文本框的封装操作
private void LocationAndSendKeysAndClick(String userName, String passWord) throws InterruptedException {
//TODO 账号未空,密码不为空
//进入了登录页面
// Thread.sleep(1000);
//定位用户框
element = driver.findElement(By.cssSelector("#username"));
//输入用户名
element.sendKeys(userName);
// Thread.sleep(1000);
//定位密码框
element = driver.findElement(By.id("password"));
element.sendKeys(passWord);
// Thread.sleep(1000);
//点击登录按钮
element = driver.findElement(By.xpath("//*[@id=\"submit\"]"));
//点击操作
element.click();
// Thread.sleep(1000);//强制等待后,查看结果
//处理弹窗
handleAlert();
//清空操作
element = driver.findElement(By.cssSelector("#username"));
element.clear();
;
element = driver.findElement(By.id("password"));
element.clear();
}
}
博客列表测试
博客列表页ListPage
1. 创建驱动, 并打开页面.
2. 检查页面是否能够正常打开: 通过获取当前链接是否和博客列表页链接一致.
3. 检查个人信息模块: 通过能否定位文章标签span, 并且比较标签值是否为"文章".
4. 检查博客列表模块: 通过定位一篇博客的查看全文按钮, 并且进行点击操作来判断是否存在博客.
5. 检查右上角三个模块: 分别取定位右上角的三个标签: 主页、写博客、注销 并且进行点击操作, 看是否有相应页面展示出来.
6. 关闭驱动.
package test_blog_system.test;
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 org.springframework.stereotype.Component;
import test_blog_system.common.Utils;
import java.io.IOException;
import java.time.Duration;
@Component
//测试博客列表页
public class ListPage extends Utils {
private static WebElement element = null;
public ListPage() throws InterruptedException {
super();
}
public void testListPage() throws InterruptedException, IOException {
//登录操作
loginSuccess();
//进入博客列表页
//我们通过获取链接的方式来测试
String url = driver.getCurrentUrl();
assert url.equals("http://120.26.236.46:8080/blog_list.html");//比对链接的方式来确认是否跳转到了博客列表页
//TODO 测试个人信息模块
// System.out.println("我再这");
element = driver.findElement(By.cssSelector("body > div.container > div.left > div > div:nth-child(4) > span:nth-child(1)"));
String context = element.getText();
assert context.equals("文章");
Thread.sleep(1000);
getScreenShot(getClass().getName());
//TODO 测试博客列表模块
//定位查看全文
element = driver.findElement(By.xpath("/html/body/div[2]/div[2]/div[2]/a"));
//点击查看全文
element.click();
//处理弹窗
//防止太快了弹窗还没有出现
// WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(1));
// wait.until(ExpectedConditions.alertIsPresent());
// handleAlert();
//获取当前标题,验证是否跳转到博客详情页
String text = driver.getTitle();
assert text.equals("博客详情页");
Thread.sleep(1000);
getScreenShot(getClass().getName());
//回退到博客列表页
//使用浏览器导航操作
driver.navigate().back();
//测试右上角三个元素
//主页
element = driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));
element.click();
//处理弹窗
//防止弹窗没有刷新出来
// wait = new WebDriverWait(driver, Duration.ofSeconds(1));
// wait.until(ExpectedConditions.alertIsPresent());
// alert = driver.switchTo().alert();
// alert.accept();
//写博客
element = driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));
element.click();
//回到博客列表页
driver.navigate().back();
//注销
element = driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)"));
element.click();
//结束
driver.quit();
}
}
博客详情测试
博客详情页DetailPage
1. 创建驱动,并且打开页面.
2. 进入博客列表页.
3. 定位并点击查看全文按钮,并处理弹窗.
4. 点击编辑按钮, 查看博客信息.
5. 关闭驱动.
package test_blog_system.test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.springframework.stereotype.Component;
import test_blog_system.common.Utils;
@Component
public class DetailPage extends Utils {
private static WebElement element= null;
public DetailPage() throws InterruptedException {
super();
}
public void testDetailPage() throws InterruptedException {
//进入博客列表页
loginSuccess();
//进入博客详情页
//定位查看全文
element = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a"));
//点击查看全文
element.click();
Thread.sleep(1000);
//处理弹窗
// handleAlert();
//查看文章信息
driver.quit();
}
}
博客编辑测试
博客编辑页EditPage
1. 创建驱动, 并且打开页面.
2. 进入博客列表页.
3. 定位并点击写博客.
4. 输入标题,不输入内容, 点击发布博客, 是否成功.
5. 输入标题, 输入内容, 点击发布博客, 是否成功.
6. 不输入标题,输入内容, 点击发布博客, 是否失败.
7. 不输入标题, 不输入内容, 点击发布博客, 是否失败.
8. 关闭驱动
package test_blog_system.test;
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 org.springframework.stereotype.Component;
import test_blog_system.common.Utils;
import java.time.Duration;
@Component
public class EditPage extends Utils {
public EditPage() throws InterruptedException {
super();
}
private static WebElement element = null;
public void testEditPage() throws InterruptedException {
//进入博客列表页
loginSuccess();
element = driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));
element.click();
//TODO 编辑操作
//修改标题
element = driver.findElement(By.cssSelector("#title"));
element.sendKeys("我正在修改博客");
//点击更新操作
Thread.sleep(1000);
element = driver.findElement(By.cssSelector("#submit"));
element.click();
//处理弹窗
// handleAlert();
//TODO 写博客
element = driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));
element.click();
//不写标题
Thread.sleep(1000);
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(1));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("submit")));
element = driver.findElement(By.cssSelector("#submit"));
element.click();
//处理弹窗
handleAlert();
//写标题,写内容
element = driver.findElement(By.cssSelector("#title"));
element.sendKeys("测试博客");
wait = new WebDriverWait(driver, Duration.ofSeconds(1));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("submit")));
element = driver.findElement(By.cssSelector("#submit"));
element.click();
//处理弹窗
// handleAlert();
driver.quit();
driver = null;
}
}
测试结果
但是会发现测试耗时有些长,说明性能还有优化的空间
性能测试
使用JMeter对博客系统进行性能测试,我们测试登录操作
创建梯度压测线程组 :jp@gc - Stepping Thread Group
创建csv文件, 存放用户名和密码
导入 csv 文件
设置信息头管理器:
创建http取样器
查看聚合报告
我们可以看出95%的登录请求响应时间在3秒内,最大响应时间达到了21秒,说明在高并发情况下,系统的处理请求的速度明显下降.
测试过程中发生了较少的异常情况,我们通过查看结果树来看单个接口的响应信息.
每秒处理事务数/吞吐量(TPS)
事务在测试的时候逐渐增加, 并且在一段时间内保持稳定. 在在22秒左右, 系统吞吐量达到了最大, 每秒可以处理257个请求.
响应时间
响应时间比较的稳定,说明登录接口的性能还不错
界面测试
我们此时把项目放在线上环境进行测试: 博客登陆页
我们对登录页面, 博客列表页面, 博客详情页面, 博客编辑页面, 博客发布页面进行界面测试
登录页面正确显示:
博客列表页, 博客的数量没有正确显示
我们之前的客户端页面,文章标签下面是一个固定的内容, 我们期待的是这个数字显示的是所有博客的数量.
我们在后端新增加一个count接口
然后把数据传给客户端,把固定的标签内容清楚,然后凭借上从服务端传来的数据
注意: 我们的博客详情页也要修改
然后我们重新打包, 把项目重新部署到xshell上
最终修改结果: 博客列表页正确显示
博客详情页正确显示
博客编辑页正确显示
博客发布页正确显示
总结
功能测试:
1. 博客系统基本功能正常运行, 正常流程可以正确执行
2. 异常情况处理有缺陷, 比如非登录情况下点击写博客, 可以进入写博客页面
自动化测试:
1.测试用例130+全部都通过
2. 使用了显示等待, 隐式等待, 设定元素等待时间, 能够保证元素正确被定位
性能测试:
1. 测试过程中, 发生了较少的处理超时异常
2. 99%的响应时间达到15秒,后续应该进行改进到3秒内
界面测试:
1. 所有按钮点击都能够即时响应, 页面显示良好
2. 博客总数被设定为固定值, 影响用户体验, 增加了count接口
后续改进
1. 博客列表页的响应时间比较长, 可以进行分页处理
2. 增加注册功能
4. 博客列表页的每一篇博客显示的是全部内容, 后续进行提取关键词和部分内容进行显示