一、项目背景
共享博客系统是一个简单的Web网站项目,使用前后端分离技术来实现的。该项目由博客登录页面、博客列表页、博客详情页、博客编辑页来构建而成的。该项目已经部署到了云服务器中,可以在线上模拟实现登录、查看博客列表、查看博客详情、编辑和删除博客、发布博客、注销账号等一系列功能。
二、项目功能
- 登陆页面:在登陆页面是用来实现登录功能,这里的账号和密码已经提前写入到了数据库当中。此页面中可以跳转到博客列表页和博客编辑页,登录成功时就会自动跳转到博客列表页
- 博客列表页:在博客列表页是可以查看所有编辑过的博客记录和发布时间,并可以查看登陆的用户名称,此页面中存在主页,写博客,查看全文等标签,可以跳转到博客详情页,博客编辑页
- 博客详情页:在博客详情页可以查看博客标题和完整的博客内容,此页面中存在主页,写博客,可以跳转到博客列表页,博客编辑页,而且存在编辑和删除标签,实现博客的更新和删除
- 博客编辑页:在博客编辑页可以输入标题和博客内容后可以进行发布,发布后会跳转到博客列表页。此页面中存在主页标签,可以跳转到博客列表页
三、对项目进行测试
对项目进行测试时需要从功能测试,性能测试,界面测试,安全测试,易用性测试,界面测试,网络测试来进行测试,但是在共享博客系统主要是围绕着功能测试来实现的
1. 功能测试
先采用手工测试,对登录功能、查看博客列表功能、查看博客详情功能、编辑和删除博客功能、发布博客功能、注销账号功能来测试,排查已有的问题。
脑图
测试用例:
部分操作步骤
1.1 测试登录操作
登录界面
-
账号+密码 正确
预期结果:正常跳转界面
-
账号密码为空
预期结果:登录失败
-
账号或密码错误
预期结果:登陆失败
1.2 测试发布博客
博客编辑页
- 编辑博客时标题不为空,内容不为空,点击发布博客按钮
预期结果:发布成功,跳转到博客列表页并显示在博客列表页
- 当标题为空,内容不为空,点击发布博客按钮
预期结果:发布失败
实际结果:发布成功
1.3 测试展示博客
- 登录成功下
预期结果:展示成功
- 登录异常情况下
预期结果:展示博客失败 提示登陆失败
1.4 测试注销功能
- 点击注销功能
预期结果:返回到登录页面,尝试展示博客时显示未能登录
2. 使用Selenium进行自动化测试
通过使用Selenium通过定位元素的Selector,和屏幕截图等方式来进行自动化测试操作,实现对项目的进一步完善。主要的测试点围绕着登录功能、查看博客列表功能、查看博客详情功能、编辑和删除博客功能、发布博客功能、注销账号功能来测试,并进行查找和修复已有Bug来完善自己的项目。
脑图
测试用例:
2.1 代码编写步骤
- 添加相关依赖pom.xml
- 创建一个公共类来同意进行创建驱动和屏幕截图,并创建隐式等待确保可以由足够的时间来查找元素、
- 创建一个Run来统一进行代码测试
- 创建一个NotLogin类,专门对没有进行登录时对博客列表页,博客详情页,博客发布页来进行测试
- 使每一个页面为为一个测试类,成功登录情况下在各个测试类中进行测试用例的编写
2.1.1添加相关依赖pom.xml
<dependencies>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies>
2.1.2 创建公共类
- 统一创建驱动对象
- 统一实现屏幕截图功能,注意文件格式,以及命名方式
- 实现隐式等待,对查找元素统一等待2秒
代码
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 int Admin;
public static WebDriver create(){
if(driver == null) {
WebDriverManager.chromedriver().setup();
ChromeOptions options = new ChromeOptions();
//允许访问所有链接
options.addArguments("--remote-allow-origins=*");
driver = new ChromeDriver(options);
//等待-- 查找每个元素时都等待2秒
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
}
return driver;
}
public Utils() {//未登录用
driver = create();
}
public Utils(String url) {//传url
//创建驱动对象
driver = create();
driver.get(url);
}
//屏幕截图
public void getScreen(String filename) throws IOException {
//./src/test/img
// /2024-XX-XX
// /test01/170025.png
// /2024-XX-XX
// /test05/580031.png
SimpleDateFormat s1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat s2 = new SimpleDateFormat("HHmmssSS");
String dirTime = s1.format(System.currentTimeMillis());
String fileTime = s2.format(System.currentTimeMillis());
String file = "./src/test/img" + "" + dirTime + "/" + filename + "/" + fileTime + ".png";
File f = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(f,new File(file));
}
}
2.1.3测试NotLogin类
- 测试:未登录时博客列表页是否提示弹窗,提示没有登陆并返回到登陆页面
- 测试:未登录时博客详情页是否提示弹窗,提示没有登陆并返回到登陆页面
- 测试:未登录时是否可以正常访问博客发布页
运行结果
代码:
public class NotLogin extends Utils {
String listUrl = "http://111.229.29.41:8080/blog_list.html";
String Detail = "http://111.229.29.41:8080/blog_detail.html";
String edit = "http://111.229.29.41:8080/blog_edit.html";
public void notLoginList() throws IOException, InterruptedException {
driver.get(listUrl);
/*出现弹窗错误---不使用
getScreen(Thread.currentThread().getStackTrace()[1].getMethodName());出现弹窗错误
Thread.sleep(2000);*/
Thread.sleep(2000);
//出现弹窗
Alert alert = driver.switchTo().alert();
alert.accept();
}
public void notLoginDetail() throws IOException, InterruptedException {
driver.get(Detail);
/*出现弹窗错误---不使用
getScreen(Thread.currentThread().getStackTrace()[1].getMethodName());出现弹窗错误
Thread.sleep(2000);*/
Thread.sleep(2000);
//出现弹窗
Alert alert = driver.switchTo().alert();
alert.accept();
}
public void notLoginEdit() throws IOException {
getScreen(Thread.currentThread().getStackTrace()[1].getMethodName());
driver.get(edit);
}
}
2.1.4 测试登录页表页
- 创建驱动,并打开页面
- 测试页面是否正常打开
- 正常登录后测试多组元素
- 返回上一级目录并清空账号和密码
- 测试异常登录:用户名/密码错误的情况
代码:
public class Login extends Utils {
public static String url = "http://111.229.29.41:8080/blog_login.html";
public Login() {
super(url);
}
//登录页面正确
public void loginRight() throws InterruptedException, IOException {
//验证主页
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));
//验证登录页面
driver.findElement(By.cssSelector("body > div.container-login > div > h3"));
//验证博客页面
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));
//获取标题
System.out.println(driver.getTitle());
getScreen(Thread.currentThread().getStackTrace()[1].getMethodName());
Thread.sleep(2000);
}
//登录成功
public void loginSuc() throws InterruptedException, IOException {
//输入账号;密码
driver.findElement(By.cssSelector("#username")).sendKeys("lisi");
driver.findElement(By.cssSelector("#password")).sendKeys("123456");
driver.findElement(By.cssSelector("#submit")).click();
//检验是否登录成功
//1.查看标题
driver.findElement(By.cssSelector("body > div.nav > span"));
//2.查看主页标签
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));
//3.查看查看全文标签
driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a"));
Thread.sleep(2000);
//获取标题
System.out.println(driver.getTitle());
//获取url
String url = driver.getCurrentUrl();
//断言
assert url.equals("http://111.229.29.41:8080/blog_list.html");
getScreen(Thread.currentThread().getStackTrace()[1].getMethodName());
//返回上一页
driver.navigate().back();
}
//登录失败
public void loginFail() throws InterruptedException, IOException {
//刷新
driver.navigate().refresh();
driver.findElement(By.cssSelector("#username")).sendKeys("lisi");
driver.findElement(By.cssSelector("#password")).sendKeys("123456");
//检验是否登录成功
//获取url
System.out.println(driver.getCurrentUrl());
getScreen(Thread.currentThread().getStackTrace()[1].getMethodName());
}
}
2.1.5 测试博客列表页
- 测试博客列表页是否可以正常打开
- 正常显示博客列表页:测试多组元素
- 测试列表页是否可以正常跳转到博客详情页
- 测试跳转到的博客详情页内容是否正确
代码:
public class BlogList extends Utils {
public BlogList() {
super("http://111.229.29.41:8080/blog_list.html");
}
//登录成功
//列表页显示成功
public void listSuc() throws InterruptedException, IOException {
//1.查看标题
driver.findElement(By.cssSelector("body > div.nav > span"));
//2.查看主页标签
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));
//3.查看查看全文标签
driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a"));
Thread.sleep(2000);
//4.获取标题
System.out.println(driver.getTitle());
//5.断言url
String url = driver.getCurrentUrl();
assert url.equals("http://111.229.29.41:8080/blog_list.html");
getScreen(Thread.currentThread().getStackTrace()[1].getMethodName());
}
//验证--查看全文是否正确
public void listShowRight(){
//验证是否跳转成功
driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a")).click();
//----跳转成功
//验证是否有标题
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title"));
//验证是否有账号名称
String admin = driver.findElement(By.cssSelector("body > div.container > div.left > div > h3")).getText();
//验证对自己账号可否有编辑,删除
if(admin == "lisi"){
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.operating > button:nth-child(1)"));
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.operating > button:nth-child(2)"));
}
//验证收是否有主页
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));
//验证标题
String url = driver.getTitle();
assert url.equals("博客详情页");
}
}
2.1.6 测试登录页表页
- 测试详情页的正确打开:有blogId和没有blogId两种情况
- 测试有blogId时-测试多组元素
- 没有blogId-是否提示错误信息
public class Detail extends Utils {
//详情页失败
public void detailFail() throws IOException, InterruptedException {
driver.get("http://111.229.29.41:8080/blog_detail.html");
//出现弹窗
Thread.sleep(2000);//执行太快缓俩秒
Alert alert = driver.switchTo().alert();
alert.accept();
}
//详情页成功
public void detailSuc(){
driver.get("http://111.229.29.41:8080/blog_list.html");
driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > a")).click();
//验证是否有标题
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.title"));
//验证是否有账号名称
String admin = driver.findElement(By.cssSelector("body > div.container > div.left > div > h3")).getText();
//验证对自己账号可否有编辑,删除
if(admin == "lisi"){
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.operating > button:nth-child(1)"));
driver.findElement(By.cssSelector("body > div.container > div.right > div > div.operating > button:nth-child(2)"));
}
//验证收是否有主页
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));
//验证标题
String url = driver.getTitle();
assert url.equals("博客详情页");
}
}
2.1.7 测试博客发布页
- 测试编辑页是否可以显示–测试多组元素
- 测试博客是否可以正常发布
- 测试是否显示在博客列表页
- 关闭驱动对象
代码:
public class Edit extends Utils {
public Edit(){
super("http://111.229.29.41:8080/blog_edit.html");
}
public void EditRight(){
driver.findElement(By.cssSelector("#submit"));
driver.findElement(By.cssSelector("body > div.nav > span"));
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)"));
}
public void editSuc() throws InterruptedException {
driver.findElement(By.cssSelector("#title")).sendKeys("测试");
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(1000);
//测试是否发布成功
String title = driver.findElement(By.cssSelector("body > div.container > div.right > div:nth-child(1) > div.title")).getText();
assert title.equals("测试");
}
}
执行代码:
public class Run {
public static void main(String[] args) throws InterruptedException, IOException {
Login login = new Login();
login.loginRight();
login.loginSuc();
login.loginFail();
BlogList blogList = new BlogList();
blogList.listSuc();
blogList.listShowRight();
Detail detail = new Detail();
detail.detailFail();
detail.detailSuc();
Edit edit = new Edit();
edit.EditRight();
edit.editSuc();
Utils.driver.quit();
}
}
测试结果:成功
自动化测试亮点以及难点
难点
在测试过程中出现一些意想不到的问题和Bug,需要花费时间来进行调整和完善,并在跳转和处理弹窗时需要强制等待,以便于代码可以正确识别,避免程序速度过快出现遗漏的请况
亮点:
- 只创建一次驱动,避免了资源浪费
- 使用了隐式等待,提高了自动化测试的稳定性
- 使登录异常和登录成功 分开进行测试,使代码变得更易观
- 使用了屏幕截图:方便问题的追溯以及问题的解决