实战:博客系统的 Web 自动化测试
目录
一.引言
二.项目介绍
(一)项目功能
(二)页面展示
1.注册页面
2.登录页面
3.列表页面
4.详情页面
5.编辑页面
三.测试用例设计
四.自动化测试准备
(一)工具选择
(二)环境搭建及项目创建
五.编写测试脚本
(一)实现工具类
(二)登录功能测试
(三)列表页面功能测试
(四)详情页面功能测试
(五)写博客功能测试
(六)驱动退出和测试套件类 实现
六、测试执行与结果分析
七.测试总结及亮点
(一)总结
(二) 亮点
一.引言
项目自动化测试源码:BlogAutoTest: 我的博客系统——Web自动化测试实战
在软件开发的世界里,质量是生命线。对于博客系统这样的应用而言,确保其功能的稳定性和可靠性至关重要。自动化测试作为一种高效的质量保障手段,能够帮助我们快速、准确地发现系统中的问题。本篇文章将围绕 [个人博客系统] 展开,详细介绍如何进行自动化测试。
二.项目介绍
(一)项目功能
本项目围绕博客系统展开,涉及注册、登录、博客列表展示、博客详情查看、博客编写等功能:
- 注册页面:提供用户名、密码及确认密码输入框,用户可在此完成注册操作,点击提交后跳转至登录页面。
- 登录页面:设有用户名和密码输入框,用户输入已在后端数据库存储的账号信息,点击提交后,若验证通过,将跳转至列表页面。
- 博客列表页面:展示已发布博客的标题、发布时间和内容概要,每篇博客旁有查看全文、修改、删除操作按钮。左侧呈现登录用户信息及文章数、分类数。右上角有主页(即当前列表页)、写博客、注销功能,可进行相应页面跳转。
- 博客详情页面:显示单篇博客完整内容,以及发布时间、阅读量等信息。
- 博客编辑页面:具备富文本编辑功能,用户可输入文章标题,在编辑区域撰写博客内容,完成后点击提交可发布博客,发布成功后跳转至列表页面
(二)页面展示
1.注册页面
2.登录页面
3.列表页面
4.详情页面
5.编辑页面
三.测试用例设计
这里使用的软件是Xmind
四.自动化测试准备
(一)工具选择
本次自动化测试选用了 Selenium 作为主要工具。Selenium 是一个强大的 Web 自动化测试框架,能够模拟用户在浏览器中的各种操作,支持多种编程语言,如 Java、Python 等。配合 JUnit 作为测试框架,方便组织和运行测试用例,以及查看测试结果。
(二)环境搭建及项目创建
- 安装浏览器驱动:这里使用的浏览器是Edge。根据所使用的浏览器(如 Chrome、Edge 等),下载并配置对应的驱动程序,确保 Selenium 能够与浏览器进行交互。
- 项目创建:在IDEA中创建Maven项目——BlogAutoTest
- 引入依赖:在 pom.xml 文件中添加如下依赖:
4.0.0 org.example MyTest 1.0-SNAPSHOT 17 17 org.junit.jupiter junit-jupiter 5.8.2 test org.junit.platform junit-platform-suite 1.8.2 test org.seleniumhq.selenium selenium-java 4.0.0 io.github.bonigarcia webdrivermanager 5.9.0
4. 包的模块化设计 :依据系统功能特性,将相关类放置在同一个包中,实现代码的组织和管理。比如在本次项目里,创建 Tests 包放测试用例类,common包放工具类,这样做能提高代码可读性、可维护性和可复用性。结构如下图所示:
五.编写测试脚本
所有测试功能类的代码均已标注注释,方便理解与问题排查
(一)实现工具类
在common包内创建一个AutotestUtils类,用来存放在测试过程中常用的重复代码。主要方法有:
- createDriver():创建 Edge 浏览器驱动(配置跨域选项和隐式等待)
- getTime():生成时间戳字符串(用于截图文件名和目录名)
- getScreenShot():基于当前时间戳保存屏幕截图,支持问题追溯
public class AutotestUtils { public static EdgeDriver driver; /** * 创建驱动对象 */ public static EdgeDriver createDriver() { if (driver == null) { WebDriverManager.edgedriver().setup(); EdgeOptions options = new EdgeOptions(); options.addArguments("--remote-allow-origins=*"); // options.addArguments("-headless"); driver = new EdgeDriver(options); //创建隐式等待 driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); } return driver; } /** * 获取时间戳 */ public List 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 list = new ArrayList(); list.add(dirname); list.add(filename); return list; } /** * 获取屏幕截图 */ public void getScreenShot(String str) throws IOException { List list = this.getTime(); String filename = "./src/test/java/com/blogWebAutoTest/" + list.get(0) + "/" + str + "_" + list.get(1) + ".png"; File srcfile = (File)driver.getScreenshotAs(OutputType.FILE); FileUtils.copyFile(srcfile, new File(filename)); } }
(二)登录功能测试
在Tests包内创建一个BlogLoginTest类。主要测试方法有:
- loginPageLoadRight():验证登录页面加载正常(检查主页、写博客、注册元素)
- loginSuc():参数化测试正常登录场景(验证登录后跳转至博客列表页)
- loginFail():参数化测试异常登录场景(验证错误提示弹窗内容)
@TestMethodOrder(OrderAnnotation.class) class BlogLoginTest extends AutotestUtils { //1.驱动对象 public static EdgeDriver driver = createDriver(); //被BeforeAll修饰的方法必须为静态的 //在当前测试类中所有测试方法执行之前只执行一次 @BeforeAll /** 访问登录页面的URL */ static void baseControl() { driver.get("http://127.0.0.1:7070/login.html"); } @Test @Order(1) /** 检查登录页面是否正常打开 检查点:主页 写博客 注册 元素是否存在 */ void loginPageLoadRight() throws IOException { driver.findElement(By.cssSelector("body > div.nav > a:nth-child(4)")); driver.findElement(By.xpath("/html/body/div[1]/a[2]")); driver.findElement(By.cssSelector("body > div.nav > a:nth-child(6)")); getScreenShot(getClass().getName()); } @ParameterizedTest @CsvSource({"Wukong.Sun,111111","w_o.ogason,123456"}) @Order(2) /** 检查正常登录的情况 */ void loginSuc(String name, String passwd) throws InterruptedException, IOException { //这三步只是登录的步骤,能不能保证登录是成功的呢? //clear 先清空 driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password")).clear(); driver.findElement(By.cssSelector("#username")).sendKeys(name); driver.findElement(By.cssSelector("#password")).sendKeys(passwd); driver.findElement(By.cssSelector("#submit")).click(); //对登录结果进行检验——跳转到博客列表页才算是登录成功 //检查“查看全文”元素是否存在 //driver.findElement(By.cssSelector("#artDiv > div:nth-child(1) > a:nth-child(4)")); //检查“文章”元素是否存在 driver.findElement(By.cssSelector("body > div.container > div.container-left > div > div:nth-child(4) > span:nth-child(1)")); getScreenShot(getClass().getName()); //页面返回——防止执行第二个测试用例时候因为页面跳转而导致元素查找失败 driver.navigate().back(); } @ParameterizedTest @CsvSource({"Wukong.Sun,11111"}) @Order(3) /** 检查异常登录的情况 */ void loginFail(String name, String passwd) throws IOException { driver.findElement(By.cssSelector("#username")).clear(); driver.findElement(By.cssSelector("#password")).clear(); driver.findElement(By.cssSelector("#username")).sendKeys(name); driver.findElement(By.cssSelector("#password")).sendKeys(passwd); driver.findElement(By.cssSelector("#submit")).click(); //对登录失败的结果进行检验 WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3)); wait.until(ExpectedConditions.alertIsPresent()); Alert alert = driver.switchTo().alert(); String res = alert.getText(); alert.accept(); getScreenShot(getClass().getName()); // System.out.println(res); assert res.equals("抱歉登录失败,用户名或密码输入错误,请重试!"); } }
(三)列表页面功能测试
在common包内创建BlogListTest类,主要测试方法有:
- listPageLoadRight():验证登录状态下列表页加载正常(检查 "文章" 和 "分类" 元素是否存在)
public class BlogListTest extends AutotestUtils { public static EdgeDriver driver = createDriver(); @BeforeAll static void baseControl() { driver.get("http://127.0.0.1:7070/myblog_list.html"); } /** * 博客列表页正常显示-登录状态下 */ @Test void listPageLoadRight() throws IOException { //文章 分类元素是否存在 driver.findElement(By.cssSelector("body > div.container > div.container-left > div > div:nth-child(4) > span:nth-child(1)")); driver.findElement(By.cssSelector("body > div.container > div.container-left > div > div:nth-child(4) > span:nth-child(2)")); getScreenShot(getClass().getName()); } }
(四)详情页面功能测试
在Tests包内创建BlogDeatailTest类,主要测试方法有:
- blogDeailLoadRight():验证登录状态下博客详情页加载正常(检查标题和时间元素是否存在)
public class BlogDetailTest extends AutotestUtils { public static EdgeDriver driver = createDriver(); @BeforeAll static void baseControl() { driver.get("http://127.0.0.1:7070/blog_content.html?id=26"); } @Test /** * 检查博客详情页是否正常打开-登录状态下 */ void blogDeailLoadRight() throws IOException { //标题元素 driver.findElement(By.cssSelector("#title")); //时间元素 driver.findElement(By.cssSelector("#updatetime")); getScreenShot(getClass().getName()); } }
(五)写博客功能测试
在common包内创建BlogEditTest类,主要测试方法有:
- editPageLoadRight():验证博客编辑页面是否能正常加载(检查标题框和提交按钮是否存在)
- editAndSubmitBlog():验证博客文章是否可以正常发布
@TestMethodOrder(OrderAnnotation.class) public class BlogEditTest extends AutotestUtils { public static EdgeDriver driver = createDriver(); @BeforeAll static void baseControl() { driver.get("http://127.0.0.1:7070/blog_add.html"); } @Test @Order(1) /** * 检查博客编辑页可以正常打开 */ void editPageLoadRight() throws IOException { //检查标题框和提交按钮是否存在 driver.findElement(By.cssSelector("#title")); driver.findElement(By.cssSelector("#submit")); getScreenShot(getClass().getName()); } @Test @Order(2) /** * 检查博客可以正常发布文章 */ void editAndSubimitBlog() throws IOException, InterruptedException { String expect = "java104&105 Autotest"; driver.findElement(By.cssSelector("#title")).sendKeys(expect); //因博客系统使用到的编辑是第三方软件,所以不能直接使用sendkeys向编辑模块发送文本 //横线 Thread.sleep(10); driver.findElement(By.cssSelector("#editorDiv > div.editormd-toolbar > div > ul > li:nth-child(21) > a > i")).click(); //删除线 Thread.sleep(10); driver.findElement(By.cssSelector("#editorDiv > div.editormd-toolbar > div > ul > li:nth-child(5) > a > i")).click(); Thread.sleep(10); driver.findElement(By.cssSelector("#submit")).click(); //处理第一个弹窗——确认发布文章吗? Thread.sleep(10); WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3)); wait.until(ExpectedConditions.alertIsPresent()); Alert alert = driver.switchTo().alert(); alert.accept(); //处理第二个弹窗——是否继续发布文章 Thread.sleep(10); WebDriverWait wait2 = new WebDriverWait(driver, Duration.ofSeconds(3)); wait2.until(ExpectedConditions.alertIsPresent()); Alert alert2 = driver.switchTo().alert(); alert2.dismiss(); Thread.sleep(10); //获取列表页博客标题文本,检查是否跟预期相符 getScreenShot(getClass().getName()); String actual = driver.findElement(By.cssSelector("#artDiv > div > div.title")).getText(); Assertions.assertEquals(expect, actual); } }
(六)驱动退出和测试套件类 实现
- 在Tests包内创建 driverQuitTest类:使浏览器驱动退出
- 在Tests包内创建 runSuite类:组合多个测试类为一个执行单元,提升测试效率和可维护性。通过注解@SelectClasses可指定测试类的执行顺序。
- 两者配合实现了从功能测试到资源释放的全流程自动化管理。
public class driverQuitTest extends AutotestUtils { public static EdgeDriver driver = createDriver(); @Test void driverQuit() { driver.quit(); } }
@Suite//创建测试套件 // 指定要执行的测试类 @SelectClasses({BlogLoginTest.class,BlogListTest.class,BlogEditTest.class,BlogDetailTest.class,driverQuitTest.class}) public class runSuite { }
六、测试执行与结果分析
可以看到所有测试用例均执行通过。
说明我们的博客系统在当前测试场景下功能正常,能够满足基本的业务需求。但这并不意味着系统完全没有问题,后续还需要不断扩展测试用例,覆盖更多的边界情况和复杂场景。
七.测试总结及亮点
(一)总结
通过对博客系统进行自动化测试,我们能够在开发过程中及时发现并解决问题,有效提高系统的质量和稳定性。在未来,我们可以进一步扩展测试用例,增加对不同浏览器兼容性的测试,以及性能测试等。同时,将自动化测试集成到持续集成 / 持续交付(CI/CD)流程中,实现每次代码提交都自动触发测试,确保新的代码变更不会引入新的问题,为博客系统的持续迭代和优化提供有力保障。
(二) 亮点
- 使用了 junit5 中提供的注解:避免生成过多的对象,造成资源和时间的浪费,提高了自动化的执行效率。
- 只创建一次驱动对象,避免每个用例重复创建驱动对象造成时间和资源的浪费。
- 使用参数化:保持用例的简洁,提高代码的可读性。
- 测试套件:降低了测试人员的工作量,通过套件一次执行所有要运行的测试用例。
- 使用了等待:提高了自动化运行效率,提高了自动化的稳定性。
- 屏幕截图:方便问题的追溯以及问题的解决。
- blogDeailLoadRight():验证登录状态下博客详情页加载正常(检查标题和时间元素是否存在)
- listPageLoadRight():验证登录状态下列表页加载正常(检查 "文章" 和 "分类" 元素是否存在)