项目功能
- 背景:当你在学习的时候,相信你一定有做笔记的经历,这是一个好习惯,方便你在遗忘或是复习的时候回顾知识,而接下来我要讲的这个程序,就可以方便你去写自己的学习笔记,并且可以发表出来,既方便自己学习,同时也可以分享给别人~
测试计划
功能测试
编写测试用例
使用Selenium+Junit5进行Web自动化测试
创建测试套件类
- 创建一个类(自定义名为runSuite),通过 @Suite 注解标识该类为测试套件类(不是测试类),然后使用 @SelectClasses 注解来声明我们要指定的类(通过这个类来运行测试用例),这样优点如下:
- 相比于一个一个类来实例化,然后调用函数(测试用例)进行测试就大大减少了开销和时间;
- 同时指定了类的测试顺序,即在注解@SelectClasses参数中的类测试顺序为从左向右;
@Suite
@SelectClasses({BlogLoginTest.class, DriveQuitTest.class})
public class runSuite {
}
博客登录页
- 登录页面正常加载(检查元素是否存在)
- 登录成功(检查跳转页面元素是否存在)
- 登录失败
- 使用注解声@TestMethodOrder(MethodOrderer.OrderAnnotation.class)+@Order(1)声明执行顺序
- @BeforeAll 表示当前方法需要在当前类下所有用例执行之前执行一次,并且被该注解修饰的方法必须是静态方法
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogLoginTest extends AutotestUtils{
public static EdgeDriver driver = createDriver();
//如果要测试登录页面,以下所有的用例都有一个共同的步骤
//1.要有浏览器对象 2.访问登录页面的url,因此加一个注解
@BeforeAll
static void baseControl(){
driver.get("http://localhost:8080/login.html");
}
/**
* 检查登录页面正常显示
* 检查点:(主页,注册 )元素是否存在
*/
@Test
@Order(1)
void loginPageLoadRight() throws IOException {
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));//主页
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));//注册
getScreenShot(getClass().getName());
}
/**
* 页面登录成功
* @param username
* @param password
*/
@ParameterizedTest
@CsvSource({"admin,123456"})
@Order(2)
void loginSuc(String username,String password) throws IOException, InterruptedException {
driver.findElement(By.cssSelector("#username")).clear();//再次测试的时候,先将上次的用户名,密码清空
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#username")).sendKeys(username);
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#submit")).click();
//登录成功,页面跳转至博客列表页,然后可以找到博客列表页的特殊元素,说明登录成功
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));//(创作)
getScreenShot(getClass().getName());
// driver.navigate().back();//跳转至列表页需要返回才能再次测试登录页面
}
/**
*1.登录失败
*/
@ParameterizedTest//参数化方法
@CsvSource({"admin,1234","lisi,123456"})//多参数
@Order(3)
void loginFail(String username,String password) throws IOException, InterruptedException {
driver.findElement(By.cssSelector("#username")).clear();//再次测试的时候,先将上次的用户名,密码清空
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#username")).sendKeys(username);
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#submit")).click();
//登录失败,弹窗提示“用户名或密码错误”
Thread.sleep(5000);
Alert alert = driver.switchTo().alert();//切换到弹窗
alert.accept();//确认
getScreenShot(getClass().getName());
driver.navigate().refresh();
Thread.sleep(5000);
}
}
博客列表页(主页)
public class BlogListTest extends AutotestUtils {
public static EdgeDriver driver = createDriver();
@BeforeAll
static void baseControl(){
driver.get("http://127.0.0.1:8080/blog_list.html");
}
@Test
void BlogListLoadRight(){
driver.findElement(By.cssSelector("body > div.container > div > div.blog-pagnation-wrapper > button:nth-child(1)"));//首页
}
}
博客详情页
public class BlogDetailTest extends AutotestUtils {
public static EdgeDriver driver = createDriver();
@BeforeAll
static void baseControl(){
driver.get("http://localhost:8080/blog_content.html?id=2");
}
@Test
void editPageLoadRight() throws IOException {
driver.findElement(By.cssSelector("#title"));
driver.findElement(By.cssSelector("body > div.container > div.container-right > div > div.date"));
driver.findElement(By.xpath("//*[@id=\"title\"]"));
}
}
个人博客列表页
public class BlogListTest extends AutotestUtils {
public static EdgeDriver driver = createDriver();
@BeforeAll
static void baseControl(){
driver.get("http://localhost:8080/myblog_list.html");
}
/**
* 检查列表页面正常显示
*/
@Test
void listPageLoadRight() throws IOException {
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)"));//退出登录
driver.findElement(By.cssSelector("#artlistDiv > div:nth-child(2) > div:nth-child(4) > a:nth-child(2)"));//修改
getScreenShot(getClass().getName());
}
}
修改密码页
- 有2个弹窗,必须添加强制等待(目前没有想到更好的办法)
driver.findElement(By.xpath("/html/body/div[1]/a[4]")).click();//修改密码
driver.findElement(By.cssSelector("#password")).sendKeys("admin");
driver.findElement(By.cssSelector("#password1")).sendKeys("123456");
driver.findElement(By.cssSelector("#password2")).sendKeys("123456");
driver.findElement(By.cssSelector("#submit")).click();
Thread.sleep(5000);
Alert alert = driver.switchTo().alert();
Thread.sleep(5000);
alert.accept();
Thread.sleep(5000);
alert.accept();
Thread.sleep(5000);
String actual = driver.findElement(By.cssSelector("body > div.login-container > div > h3")).getText();
String expect = "登录";
Assertions.assertEquals(expect,actual);
编辑博客页
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogEditTest extends AutotestUtils {
public static EdgeDriver driver = createDriver();
@BeforeAll
static void baseControl(){
driver.get("http://localhost:8080/blog_edit.html");
}
/**
* 检查编辑页面正常显示
*/
@Test
@Order(1)
void editPageLoadRight() throws IOException {
driver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)"));//我的博客
driver.findElement(By.cssSelector("body > div.blog-edit-container > div.title > button"));//发布文章
getScreenShot(getClass().getName());
}
//这个没通过,因为新添加的文章是往后添加的,不是第一篇
@ParameterizedTest
@CsvSource("自动化测试")
@Order(2)
void editAndSubmit(String title,String content) throws IOException, InterruptedException {
String expect = title;
driver.findElement(By.cssSelector("#title")).sendKeys(expect);
driver.findElement(By.cssSelector("#editorDiv > div.editormd-toolbar > div > ul > li:nth-child(17) > a > i")).click();//H6
driver.findElement(By.xpath("/html/body/div[2]/div[1]/button[1]")).click();//发布文章
getScreenShot(getClass().getName());
String actual = driver.findElement(By.cssSelector("#artlistDiv > div:nth-child(1) > div.title")).getText();
Assertions.assertEquals(expect,actual);
}
}
退出驱动
- 测试登录,驱动退出,然后创建驱动,测试编辑页,驱动退出
- 浪费资源和时间,拖慢测试进程
- 编写一个独立的退出类,在所有测试完成后,退出驱动
public class DriveQuitTest extends AutotestUtils {
public static EdgeDriver driver = createDriver();
@Test
void driveQuit(){
driver.quit();
}
}
屏幕截图
- 有的时候测试用例执行出错了,需要查看当时网页出现的情况,那么就需要使用屏幕截图来排查问题
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();
// ./是当前项目路径下
String filename = "./src/test/java/com/blog/test/"+list.get(0)+"/"+ str +"/"+list.get(1)+".png";//路径文件名
File srcfile = driver.getScreenshotAs(OutputType.FILE);//srcfile 是 生成的屏幕截图
FileUtils.copyFile(srcfile,new File(filename));//将 srcfile 放到指定的文件夹下
}
测试有什么亮点
-
使用selinium4自动化测试工具和junit5单元测试框架结合
-
使用了junit5中提供的注解@Test,避免生成过多的对象,造成资源和时间的浪费,提高了自动化的执行效率
-
只创建一次驱动,避免每个用例重复创建驱动对象造成时间的浪费
-
使用注解@ParameterizedTest参数化:保持用例的简洁,提高代码的可读性
-
注解@Suite测试套件:降低了测试人员的工作量,通过套件一次执行所有的测试用例
-
使用等待:提高了自动化运行效率,提高了自动化的稳定性
-
屏幕截图:有的Bug不能复现,方便问题的追溯和解决
自动化测试发现bug的多少
- 就我的一个小项目来说,发现的bug并不多,因为自动化测试做的是一些相对稳定的功能【回归测试】,在前面的项目版本中,已经将bug修复,所以说,发现的bug并不多~
遇到过哪些异常
- NoSuchElementException:没有该元素异常
1.是否 selinium 没有定位到元素
- TimeoutException : 超时异常
- ElementNotVisibleException :元素不可见异常
- 产生弹窗,隐式等待不生效,需添加显示等待