项目背景介绍
背景:当在学习一项技能的时候,我们总会习惯通过博客来记录所学的知识点,方便后期遗忘时随时查看和快速复习。本次开发的Web网站程序便是为了更加轻量和方便地记录自己的学习笔记
概述:一个Web网站程序,可以随时随地查看自己和其他用户发布的博客,也可以自己注册登录后发布属于自己的博客
相关技术栈:SpringBoot SpringMVC MyBatis MySQL Redis HTML CSS JavaScript
博客登录页:
博客注册页:
博客主页:
个人博客列表页:
博客详情页:
博客编辑页:
草稿列表:
写博客页:
测试计划
1. 手工测试
1.1 编写测试用例
1.2 执行测试用例(部分)
由于篇幅及时间受限,仅针对部分用例进行了测试
测试环境
操作系统:
版本 Windows 10 家庭中文版
版本号 22H2
安装日期 2021/6/19
操作系统内部版本 19045.2728
体验 Windows Feature Experience Pack 120.2212.4190.0
浏览器:
Google Chrome 版本 111.0.5563.65(正式版本) (64 位)
网络:
协议: Wi-Fi 5 (802.11ac)
网络频带: 5 GHz
博客登录页:界面能否正常加载,输入正确或错误的账号、密码及验证码是否能得到预期的响应
a)界面
b)输入正确的账号、密码错误的验证码
预期结果:弹窗提示验证码错误,并自动刷新验证码
实际结果如下:
c)输入正确的验证码、错误的账号或密码
预期结果:提示用户名或密码错误。
实际结果如下:
d)输入正确的账号、密码及验证码:
预期结果:跳转到个人博客列表页。
实际结果如下:
e)输入正确的账号和密码,同一用户重复登录:
预期结果:提示用户重复登录,然后返回该用户博客列表页。
实际结果如下:
f)同一用户多次输入错误的密码及正确的验证码:
预期结果:提示用户账户被冻结,一段时间内禁止该用户登录。
实际结果如下:
博客列表页:检测界面是否符合预期,点击“查看全文”按钮是否能跳转到对应的博客详情页,点击注销是否能退出登录
a)界面
b)点击查看详情
预期结果:进入到对应的博客详情页,且能够正确加载文章内容。
实际结果如下:
c)点击删除
预期结果:删除对应文章,弹窗提示删除成功,作者文章计数更新,并自动刷新页面。
实际结果如下:
通过观察我们发现此处作者的文章计数器并未更新,不符合预期结果。由于这里采用了Redis缓存用户信息,而此处采用的是缓存过期自动更新的设置,没有进行主动的数据同步,推测是由于缓存更新不及时导致的结果。
此处我们手动删除缓存后再刷新测试,得到结果如下:
可以确定导致问题的原因就是缓存更新不及时导致
d)点击修改
预期结果:获取对应文章内容,进入对应文章修改页面。
实际结果如下:
e)退出登录
预期结果:删除对应用户token,并返回登录页
实际结果如下:
2. 使用Selenium进行Web端UI自动化测试
2.1 为什么需要自动化测试
由于随着项目版本的迭代,功能的逐渐增多,各种功能回归测试的需要,单纯的手工测试已经难以满足我们对于测试效率的要求,于是我们引入了自动化测试。它能够将人工从重复机械的测试过程中解放,使得人力资源能够投入到更加关键的测试中。
2.2 测试环境
测试环境
操作系统:
版本 Windows 10 家庭中文版
版本号 22H2
安装日期 2021/6/19
操作系统内部版本 19045.2728
体验 Windows Feature Experience Pack 120.2212.4190.0
浏览器:
Microsoft Edge版本 111.0.1661.44 (正式版本) (64 位)
网络:
协议: Wi-Fi 5 (802.11ac)
网络频带: 5 GHz
Selenium:
版本:4.0.0
驱动版本 :111.0.1661.44(x64)
2.3 编写测试用例
2.4 自动化测试代码
2.4.1 实现工具类,增加代码复用
package com.example.blogautotest.common;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.edge.EdgeDriver;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
public class AutotestUtils {
public static EdgeDriver driver;
//创建驱动对象
public static EdgeDriver createDriver(){
//单例模式
if(driver==null){
driver=new EdgeDriver();
//创建隐式等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
}
return driver;
}
//获取当前时间将截图按照时间保存
public List<String> getTime(){
//文件夹以天保存
//截图以毫秒时间戳保存
SimpleDateFormat sim1=new SimpleDateFormat("yyyyMMdd-HHmmssSS");
SimpleDateFormat sim2 = new SimpleDateFormat("yyyyMMdd");
String filename=sim1.format(System.currentTimeMillis());
String dirname=sim2.format(System.currentTimeMillis());
List<String> list = new ArrayList<>();
list.add(dirname);
list.add(filename);
return list;
}
//获取屏幕截图,把所有的用例执行的结果保存下来
public void getScreenShot(String str) throws IOException {
List<String> list=getTime();
// ./指的是当前的项目路径下,也就是BlogAutoTest下
String filename="./src/test/java/com/blogautotest/"+list.get(0)+"/"+str+"_"+list.get(1)+".png";
File srcfile=driver.getScreenshotAs(OutputType.FILE);
//把屏幕截图生成的文件放到指定的路径
FileUtils.copyFile(srcfile,new File(filename));
}
}
2.4.2 登陆页面
package com.example.blogautotest.Tests;
import com.example.blogautotest.common.AutotestUtils;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.By;
import org.openqa.selenium.edge.EdgeDriver;
import java.io.IOException;
//设置优先级
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogLoginTest extends AutotestUtils {
public static final String UNIVERSAL_KAPTCHA_CODE="c8fd27d19b2aa9fa24affd2a4726778c";
public static EdgeDriver driver=createDriver();
@BeforeAll
public static void baseControl(){
driver.get("http://127.0.0.1:8080/login.html");
}
/*
检查登录页面打开是否正确
检查点:登录标题 用户名是否存在
*/
@Test
@Order(1)
public void loginPageLoadRight() throws IOException {
//检验页面是否加载正确(两个检查点)
driver.findElement(By.cssSelector("body > div.login-container > div > h3"));
driver.findElement(By.cssSelector("body > div.login-container > div > div:nth-child(2) > span"));
getScreenShot(getClass().getName());
}
@ParameterizedTest
@CsvSource({"zhangsan,123","admin,admin"})
@Order(2)
public void loginSuc(String name , String password) throws InterruptedException, IOException{
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#rightCode")).clear();
driver.findElement(By.cssSelector("#username")).sendKeys(name);
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#rightCode")).sendKeys(UNIVERSAL_KAPTCHA_CODE);
driver.findElement(By.cssSelector("#submit")).click();
//处理弹窗
Thread.sleep(300);
driver.switchTo().alert().accept();
//对登录结果进行检测,存在草稿页元素代表登录成功
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));
//getScreenShot(getClass().getName());
driver.navigate().back();
}
@ParameterizedTest
@CsvSource({"admin,123","zhangsan,666"})
@Order(3)
public void loginFail(String name,String password) throws IOException, InterruptedException {
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#rightCode")).clear();
driver.findElement(By.cssSelector("#username")).sendKeys(name);
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#rightCode")).sendKeys(UNIVERSAL_KAPTCHA_CODE);
driver.findElement(By.cssSelector("#submit")).click();
//处理弹窗
Thread.sleep(300);
//获取弹窗内容
String text=driver.switchTo().alert().getText();
String except="登陆成功!";
driver.switchTo().alert().accept();
Assertions.assertNotEquals(except,text);
//获取当前页面截屏
//getScreenShot(getClass().getName());
}
}
2.4.3 博客主页
package com.example.blogautotest.Tests;
import com.example.blogautotest.common.AutotestUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.edge.EdgeDriver;
import java.io.IOException;
public class BlogListTest extends AutotestUtils {
public static EdgeDriver driver=createDriver();
@BeforeAll
public static void baseControl(){
driver.get("http://127.0.0.1:8080/blog_list.html");
}
@Test
public void listPageLoadRight(){
//检查博客列表加载是否正常
driver.findElement(By.cssSelector("body > div.container > div > div.blog-pagnation-wrapper > button:nth-child(1)"));
driver.findElement(By.cssSelector("body > div.container > div > div.blog-pagnation-wrapper > button:nth-child(4)"));
}
@Test
public void jumpTest() throws InterruptedException {
//测试分页能否正常跳转
Thread.sleep(500);
driver.findElement(By.cssSelector("body > div.container > div > div.blog-pagnation-wrapper > button:nth-child(3)")).click();
Assertions.assertEquals("http://127.0.0.1:8080/blog_list.html?pindex=2&psize=3",driver.getCurrentUrl());
Thread.sleep(500);
driver.findElement(By.cssSelector("body > div.container > div > div.blog-pagnation-wrapper > button:nth-child(2)")).click();
Assertions.assertEquals("http://127.0.0.1:8080/blog_list.html?pindex=1&psize=3",driver.getCurrentUrl());
Thread.sleep(500);
driver.navigate().back();
}
}
2.4.4 博客详情页
package com.example.blogautotest.Tests;
import com.example.blogautotest.common.AutotestUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.edge.EdgeDriver;
import java.io.IOException;
public class BlogDetailTest extends AutotestUtils {
public static EdgeDriver driver=createDriver();
@BeforeAll
public static void baseControl(){
driver.get("http://127.0.0.1:8080/blog_content.html?id=12869974016131072");
}
@Test
public void blogDeailLoadRight() throws IOException{
driver.findElement(By.cssSelector("#data"));
driver.findElement(By.cssSelector("#title"));
}
}
2.4.5 博客编辑页
package com.example.blogautotest.Tests;
import com.example.blogautotest.common.AutotestUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.edge.EdgeDriver;
import java.io.IOException;
public class BlogEditTest extends AutotestUtils {
public static EdgeDriver driver=createDriver();
@BeforeAll
public static void baseControl(){
driver.get("http://127.0.0.1:8080/blog_edit.html");
}
@Test
public void editAndSubimitBlog() throws IOException, InterruptedException {
driver.findElement(By.cssSelector("#title")).sendKeys("自动化测试");
//博客系统使用到的编辑是第三方软件,所以不能直接使用sendKeys向编辑模块发送文本
driver.findElement(By.cssSelector("#editorDiv > div.editormd-toolbar > div > ul > li:nth-child(30)")).click();
driver.findElement(By.cssSelector("body > div.blog-edit-container > div.title > button:nth-child(2)")).click();
Thread.sleep(300);
String actual=driver.switchTo().alert().getText();
driver.switchTo().alert().accept();
String expect = "恭喜:添加成功!";
Assertions.assertEquals(expect,actual);
}
}
2.4.6 个人列表页
package com.example.blogautotest.Tests;
import com.example.blogautotest.common.AutotestUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.edge.EdgeDriver;
public class MyBlogListTest extends AutotestUtils {
public static EdgeDriver driver=createDriver();
@BeforeAll
public static void baseControl(){
driver.get("http://127.0.0.1:8080/myblog_list.html");
}
@Test
public void myListPageLoadRight(){
//检查博客列表加载是否正常
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(7)"));
driver.findElement(By.cssSelector("body > div.container > div.container-left > div > a"));
driver.findElement(By.cssSelector("body > div.container > div.container-right > div.blog-pagnation-wrapper > button:nth-child(1)"));
driver.findElement(By.cssSelector("body > div.container > div.container-right > div.blog-pagnation-wrapper > button:nth-child(3)"));
}
@Test
public void jumpTest(){
//测试导航栏能否正常跳转
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")).click();
Assertions.assertEquals("http://127.0.0.1:8080/blog_list.html",driver.getCurrentUrl());
driver.navigate().back();
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
Assertions.assertEquals("http://127.0.0.1:8080/blog_edit.html",driver.getCurrentUrl());
driver.navigate().back();
}
}
2.4.7 退出驱动
package com.example.blogautotest.Tests;
import com.example.blogautotest.common.AutotestUtils;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.edge.EdgeDriver;
//用于最后关闭驱动
public class driverQuitTest extends AutotestUtils {
public static EdgeDriver driver=createDriver();
@Test
public void driverQuit(){
driver.quit();
}
}
2.4.8 测试结果
可以看到所有测试用例均通过
2.4.9 自动化测试亮点
1.通过使用junit5中的注解,避免生成过多的测试对象,减少资源和时间上的浪费,提高了自动化执行的效率
2.只创建一次驱动,避免每个用例重复创建驱动造成时间和资源的浪费。
3.使用参数化,保持用例的整洁,提高代码的可读性。
4.使用隐式等待,提高了自动化运行效率,提高了自动化的稳定性。
5.使用屏幕截图,方便问题的溯源以及解决。