📖 前言:本文针对个人博客项目进行测试,个人博客主要由四个页面构成:登录页、列表页、详情页和编辑页,主要功能包括:登录、编辑并发布博客、查看详情、删除博客以及注销等功能。对于个人博客的测试就是针对主要功能进行测试,然后按照页面书写测试类。
目录
- 🕒 1. 博客系统页面概览
- 🕒 2. 实施流程
- 🕒 3. 编写思路
- 🕘 3.1 添加相关依赖
- 🕘 3.2 包结构
- 🕒 4. 代码编写
- 🕘 4.1 公共类AutoTestUtils
- 🕘 4.2 BlogLoginTest(登录页测试)
- 🕘 4.3 BlogListTest(列表页测试)
- 🕘 4.4 BlogEditTest(编辑页测试)
- 🕘 4.5 BlogDetailTest(详情页测试)
- 🕘 4.6 BlogDeleteTest(博客删除测试)
- 🕘 4.7 BlogLogoutTest
- 🕘 4.8 runSuite(测试套件)
- 🕒 5. 测试结果
- 🕒 6. 小结
🕒 1. 博客系统页面概览
登录页:
列表页:
详情页:
编辑页:
🕒 2. 实施流程
自动化项目实施的基本流程:
- 熟悉项目
- 设计手工测试用例
- 手工测试用例转换成自动化测试用例
- 部署
🕒 3. 编写思路
- 依据思维导图编写测试用例:为每个页面创建一个测试类,并在各个测试类中编写测试用例。
- 公共属性应单独归类以便代码复用。
- 运用测试套件以简化运行和修改过程。
- 由于启动和现场截图功能会被频繁复用,应单独建立一个类来存放这些功能。
- 添加隐式等待以确保页面能正确完全地加载。
🕘 3.1 添加相关依赖
创建Maven空项目后,在pom.xml里添加需要的依赖
<dependencies>
<!-- 添加selenium依赖 -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.24.0</version>
</dependency>
<!-- 保存屏幕截图需要用到的包 -->
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.16.1</version>
</dependency>
<!-- 添加junit5依赖 -->
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.10.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-suite -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.11.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>
🕘 3.2 包结构
🕒 4. 代码编写
🕘 4.1 公共类AutoTestUtils
功能:创建驱动、保存现场截图
注意:在保存现场截图时,应按日期对文件夹进行分类,并确保图片名称反映出测试类别的名称,以便于问题追踪。同时,要留意文件名的动态生成和时间格式的设定。
public class AutoTestUtils {
public static WebDriver webDriver;
@BeforeAll
static void SetUp() {
if(webDriver == null)
{
webDriver = new ChromeDriver();
}
// 设置隐式等待时间为3秒
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
}
public static void TearDown() {
webDriver.quit();
}
// 保存截图的方法
public static void saveScreenshot(String testName) {
// 获取当前时间并格式化
String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmssSSS").format(new Date());
String dateFolder = new SimpleDateFormat("yyyyMMdd").format(new Date());
String fileName = String.format("%s-%s.png", testName, timestamp);
// 创建文件夹
File folder = new File("screenshots/" + dateFolder);
if (!folder.exists()) {
folder.mkdirs(); // 创建文件夹
}
// 保存截图
File screenshot = ((TakesScreenshot) webDriver).getScreenshotAs(OutputType.FILE);
try {
Files.copy(screenshot.toPath(), Paths.get(folder.getPath(), fileName));
} catch (IOException e) {
e.printStackTrace();
}
}
}
🕘 4.2 BlogLoginTest(登录页测试)
- 创建驱动并打开网页。
- 检查网页是否正常加载。
- 进行正常登录测试:多参数测试,使用Enter键登录。
- 进行异常登录测试:用户名或密码错误,以及为空的情况。
- 使用Order注解确保测试按正确顺序执行,避免因顺序错误导致测试失败。
- 确保在重新输入用户名和密码前清空之前的内容。
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BlogLoginTest extends AutoTestUtils {
// 所有用例都需要先获取登录页面
@BeforeAll
static void baseControl() {
webDriver.get("http://localhost:8080/blog_system/login.html");
}
/**
* 检查登录页面是否正确
* 右上角与左上角的显示、登录框等
*/
@Test
@Order(1)
void loginPageLoadRight() throws IOException {
webDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)"));
webDriver.findElement(By.xpath("/html/body/div[1]/a[2]"));
webDriver.findElement(By.cssSelector("body > div.login-container > form > div"));
saveScreenshot(getClass().getName());
}
/**
* 检查登录正常情况:使用多参数测试
*/
@Order(3)
@ParameterizedTest
@CsvFileSource(resources = "LoginSuccess.csv")
void loginSuccess(String username, String password, String blog_list_url) throws IOException {
// 在每次登录之后都要进行清空,然后才能重新输入
webDriver.findElement(By.cssSelector("#username")).clear();
webDriver.findElement(By.cssSelector("#password")).clear();
webDriver.findElement(By.cssSelector("#username")).sendKeys(username);
webDriver.findElement(By.cssSelector("#password")).sendKeys(password);
webDriver.findElement(By.cssSelector("#login-button")).sendKeys(Keys.ENTER);
saveScreenshot(getClass().getName());
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
// 以上是登录步骤,但是并不能确保就是登录成功的
// 获取到当前页面的URL,如果URL匹配则测试通过
String cur_url = webDriver.getCurrentUrl();
Assertions.assertEquals(blog_list_url, cur_url);
// 列表页展示的用户信息是否是登录账号
String cur_usr = webDriver.findElement(By.cssSelector("body > div.container > div.container-left > div > h3")).getText();
Assertions.assertEquals(username, cur_usr);
// 因为要多参数,所以在执行完一遍执行下一遍的时候需要进行页面的回退,否则找不到登录框
webDriver.navigate().back();
}
/**
* 测试完成后登录以便进行下一阶段测试
*/
@Order(4)
@Test
void loginSuccessAfter() {
webDriver.findElement(By.cssSelector("#login-button")).click();
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
}
/**
* 检查登录异常情况:使用多参数测试
*/
@Order(2)
@ParameterizedTest
@CsvSource({"admin,123", "lisi,12", "'',''"}) // 第三项为空情况
void loginFail(String username, String password) throws IOException {
// 在每次登录之后都要进行清空,然后才能重新输入
webDriver.findElement(By.cssSelector("#username")).clear();
webDriver.findElement(By.cssSelector("#password")).clear();
webDriver.findElement(By.cssSelector("#username")).sendKeys(username);
webDriver.findElement(By.cssSelector("#password")).sendKeys(password);
webDriver.findElement(By.cssSelector("#login-button")).click();
saveScreenshot(getClass().getName());
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
// 登录失败的检测,获取文本进行比对
String expectNotNull = "为空"; // 为空字样
String expectError = "错误"; // 错误字样
String actual = webDriver.findElement(By.cssSelector("body")).getText();
if (actual.contains(expectNotNull) || actual.contains(expectError)) {
System.out.println("登录失败测试通过");
} else {
System.out.println("登录失败测试不通过");
}
// 导航回登录页
webDriver.navigate().back();
}
}
🕘 4.3 BlogListTest(列表页测试)
- 检验博客列表页面是否能够正常打开。
- 验证列表页面的“查看全文”按钮是否能正确跳转。
- 检查未登录时的直接链接是否会重定向到登录页面。
public class BlogListTest extends AutoTestUtils {
@BeforeAll
static void baseControl() {
webDriver.get("http://localhost:8080/blog_system/blog_list.html");
}
/*
* 博客列表页可以正常显示
*/
@Test
void listPageLoadRight() throws IOException {
// 可以多检查几个,确保正确
webDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)"));
webDriver.findElement(By.cssSelector("body > div.container > div.container-left > div > img"));
// 获取页面上所有博客标题对应的元素
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
int title_num = webDriver.findElements(By.cssSelector(".title")).size();
// 如果元素数量不为0,则测试通过
Assertions.assertNotEquals(0,title_num);
saveScreenshot(getClass().getName());
}
}
🕘 4.4 BlogEditTest(编辑页测试)
- 检验编辑页面是否能正确打开
- 检查博客发布是否正常:元素是否完整或部分缺失
- 验证“写博客”按钮的功能性
public class BlogEditTest extends AutoTestUtils {
@BeforeAll
static void baseControl() {
webDriver.get("http://localhost:8080/blog_system/blog_list.html");
}
/*
* 博客编辑页可以正常显示
*/
@Test
@Order(1)
void editPageLoadRight() throws IOException {
// 找到写博客按钮,点击
webDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(5)")).click();
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
// 可以多检查几个,确保正确
webDriver.findElement(By.cssSelector("#submit"));
webDriver.findElement(By.cssSelector("#blog-title"));
saveScreenshot(getClass().getName());
}
/*
* 正确编辑并发布博客测试
*/
@Test
@Order(2)
void editAndSubmitBlog() throws IOException {
// 通过JS输入标题
((JavascriptExecutor) webDriver).executeScript("document.getElementById(\"blog-title\").value=\"自动化测试\"");
// 编辑页的md是第三方插件,所以不可以直接使用sendKeys向编辑模块写入内容,但是可以通过点击上方按钮进行内容的插入
webDriver.findElement(By.cssSelector("#editor > div.editormd-toolbar > div > ul > li:nth-child(30) > a > i")).click();
webDriver.findElement(By.cssSelector("#editor > div.editormd-toolbar > div > ul > li:nth-child(5) > a > i")).click();
saveScreenshot(getClass().getName());
// 点击发布
webDriver.findElement(By.cssSelector("#submit")).click();
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
// 获取当前页面的URL
String cur_url = webDriver.getCurrentUrl();
Assertions.assertEquals("http://localhost:8080/blog_system/blog_list.html", cur_url);
saveScreenshot(getClass().getName());
}
}
🕘 4.5 BlogDetailTest(详情页测试)
- 正确打开测试详情页:分别测试有blogId和无blogId的情况
- 测试删除按钮是否可用,应比较时间而非标题,因为标题可能为空
- 注意操作时要确保能够导航回列表页
public class BlogDetailTest extends AutoTestUtils {
@BeforeAll
static void baseControl() {
webDriver.get("http://localhost:8080/blog_system/blog_list.html");
}
public static Stream<Arguments> Generator() {
return Stream.of(Arguments.arguments("http://localhost:8080/blog_system/blog_detail.html",
"博客详情页","自动化测试"));
}
@ParameterizedTest
@MethodSource("Generator")
void BlogDetail(String expected_url,String expected_title,String expected_blog_title) {
// 找到第一个博客对应的查看全文按钮
webDriver.findElement(By.xpath("/html/body/div[2]/div[2]/div[1]/a")).click();
// 获取当前页面的URL、页面title、博客标题
String cur_url = webDriver.getCurrentUrl();
String cur_title = webDriver.getTitle();
String cur_blog_title = webDriver.findElement(By.cssSelector("body > div.container > div.container-right > div > h3")).getText();
Assertions.assertEquals(expected_title, cur_title);
Assertions.assertEquals(expected_blog_title, cur_blog_title);
if (cur_url.contains(expected_url)) {
System.out.println("测试通过");
} else {
System.out.println("测试不通过");
}
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
saveScreenshot(getClass().getName());
}
}
🕘 4.6 BlogDeleteTest(博客删除测试)
- 测试博客是否真正删除了
- 尝试删除别人的文章
- 删除完返回列表页
public class BlogDeleteTest extends AutoTestUtils {
@BeforeAll
static void baseControl() {
webDriver.get("http://localhost:8080/blog_system/blog_list.html");
}
@Test
void DeleteBlog () {
// 点击查看全文按钮
webDriver.findElement(By.cssSelector("body > div.container > div.container-right > div:nth-child(1) > a")).click();
// 点击删除按钮
webDriver.findElement(By.cssSelector("#delete_button")).click();
// 博客列表页第一篇博客不是“自动化测试”
String first_blog_title = webDriver.findElement(By.cssSelector("body > div.container > div.container-right > div:nth-child(1) > div.title")).getText();
// 校验当前博客标题不等于“自动化测试”
Assertions.assertNotEquals(first_blog_title,"自动化测试");
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
saveScreenshot(getClass().getName());
// 尝试删除别人的文章
// 点击查看全文按钮
webDriver.findElement(By.cssSelector("body > div.container > div.container-right > div:nth-child(1) > a")).click();
// 点击删除按钮
webDriver.findElement(By.cssSelector("#delete_button")).click();
// 删除失败的检测,获取文本进行比对
String expectError = "没有权限"; // 没有权限错误字样
String actual = webDriver.findElement(By.cssSelector("body")).getText();
if (actual.contains(expectError)) {
System.out.println("删除失败测试通过");
} else {
System.out.println("删除失败测试不通过");
}
saveScreenshot(getClass().getName());
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
// 导航回列表页
webDriver.navigate().back();
}
}
🕘 4.7 BlogLogoutTest
- 测试注销后是否返回登录页
- 断开驱动连接
public class BlogLogoutTest extends AutoTestUtils {
@BeforeAll
static void baseControl() {
webDriver.get("http://localhost:8080/blog_system/blog_list.html");
}
@Test
void Logout(){
webDriver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")).click();
// 校验当前页面URL是否是“http://localhost:8080/blog_system/login.html”
String cur_url=webDriver.getCurrentUrl();
Assertions.assertEquals("http://localhost:8080/blog_system/login.html",cur_url);
// 校验提交按钮
WebElement webElement = webDriver.findElement(By.cssSelector("#login-button"));
Assertions.assertNotNull(webElement);
webDriver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
saveScreenshot(getClass().getName());
//webDriver.quit();
TearDown();
}
}
🕘 4.8 runSuite(测试套件)
测试套件按照测试类的顺序执行。
@Suite
@SelectClasses({BlogLoginTest.class, BlogListTest.class, BlogEditTest.class, BlogInfoChecked.class, BlogDetailTest.class, BlogDeleteTest.class, BlogLogoutTest.class})
public class runSuite {
}
🔎 自动化测试完整源码+博客项目
🕒 5. 测试结果
实际效果(无倍速):
🕒 6. 小结
- 关注测试用例的执行顺序!
- 检查页面元素是否存在,以确保正确性。
- 注意多参数测试中的页面导航问题。
- 多参数(多用户登录)可能导致高并发服务器错误,需交由开发处理。
- 登录页面测试的最后一步应为登录成功状态,以便后续测试顺利进行。
- 驱动关闭应在最后一个用例结束后进行。
- 在公共类中定义保存截图方法以保存所有用例的执行结果,便于后续查错。截图保存时使用动态时间戳,按照天或周分类保存。
@SelectClasses
可指定执行类的顺序。- 建议获取固定元素(如时间、标题)时,使用
XPath
定位。因为XPath
可以基于元素的层级关系和属性进行查找,所以它适合用于固定元素的定位,特别是在复杂的 DOM 结构中。 - 可以适当关注用例执行时间,排查是否为性能问题。
- 可创建单独类存放驱动释放方法,放在套件测试的最后。
- 测试用例数量并非越多越好。
- 可以使用无头模式创建驱动。
【面试题】使用Selenium 4 自动化测试工具和JUnit 5 单元测试框架结合,如何实现的,以及有什么亮点?
1)实施:
① 设计测试用例是基于个人项目的需求,通过结合Selenium 4自动化测试工具和JUnit 5单元测试框架来执行Web自动化测试(功能、步骤和技术必须明确)。
② 需要对代码中的每个包提供概要介绍(公共属性[可复用]、测试用例[基于每个页面设计]),并使用测试套件来加载所有测试类。
2)特点:
① 利用JUnit 5提供的注解,避免创建过多对象,节省资源和时间,提升自动化测试的执行效率。
② 只创建一次驱动对象,减少每个测试用例重复创建驱动对象,节约时间和资源。
③ 采用参数化测试,使测试用例保持简洁,提高代码可读性。
④ 通过测试套件,减少了测试人员的工作量,一次性执行所有测试用例。
⑤ 使用隐式等待机制,确保页面加载完成,提升了自动化测试的运行效率和稳定性,减少了误报的可能性。
⑥ 采用屏幕截图功能,便于问题追踪和解决。
⑦ 使用无头模式,专注于测试结果,节省屏幕空间。
编写自动化测试不难,但要提高执行速度、更有效地发现问题并避免误报才是挑战。
OK,以上就是本期知识点“个人博客系统测试”的知识啦~~ ,感谢友友们的阅读。后续还会继续更新,欢迎持续关注哟📌~
💫如果有错误❌,欢迎批评指正呀👀~让我们一起相互进步🚀
🎉如果觉得收获满满,可以点点赞👍支持一下哟~
❗ 转载请注明出处
作者:HinsCoder
博客链接:🔎 作者博客主页