【测试理论与实践】(十)Web 项目自动化测试实战:从 0 到 1 搭建博客系统 UI 自动化框架

【测试理论与实践】(十)Web 项目自动化测试实战:从 0 到 1 搭建博客系统 UI 自动化框架

目录

前言

一、项目背景与测试规划:先明确 "测什么" 和 "怎么测"

1.1 项目介绍

1.2 测试目标

1.3 测试范围与用例设计

​编辑

二、环境搭建:3 步搞定自动化测试前置准备

2.1 安装核心依赖包

2.2 浏览器配置

2.3 项目目录结构设计

三、核心模块开发:封装公共工具,提高代码复用性

3.1 驱动管理与截图工具封装(common/Utils.py)

3.2 代码说明与优化点

四、测试用例开发:逐个模块实现自动化测试

4.1 登录模块测试(cases/BlogLogin.py)

4.2 博客列表模块测试(cases/BlogList.py)

4.3 博客详情模块测试(cases/BlogDetail.py)

4.4 博客编辑模块测试(cases/BlogEdit.py)

五、测试用例执行入口:一键运行所有测试(RunCases.py)

六、测试报告生成:打造专业、清晰的测试成果文档

【测试报告】博客系统 UI 自动化测试报告

1. 报告基本信息

2. 项目背景

2.1 测试目标及测试任务概括

2.2 被测系统及相关信息

2.3 产品需求和设计文档

3. 测试安排

4. 测试结果汇总

4.1 测试用例执行情况

4.2 缺陷统计与分析

4.3 截图示例

5. 测试结论与建议

5.1 测试结论

5.2 后续建议

总结


前言

        在如今的软件开发流程中,"测试" 不再是测试工程师的专属工作。尤其是对于 C++ 方向的 Web 项目开发,重复的手动测试不仅耗时耗力,还容易出现遗漏和误判。想象一下:每次迭代都要反复验证登录功能、博客列表展示、编辑提交等核心流程,稍有疏忽就可能让 bug 流入生产环境。

        而自动化测试的出现,正是为了解决这个痛点 —— 它就像一个不知疲倦的 "测试助手",能自动执行预设用例、精准捕获异常、生成测试报告,让开发者把更多精力放在核心功能开发上。本文将以经典的博客系统为案例,手把手教你用 Python 搭建一套完整的 UI 自动化测试框架,从用例设计到脚本开发,再到测试报告落地,全程实战、代码可直接复用,适合 C++ 开发者、测试工程师及所有想要入门自动化测试的小伙伴。下面就让我们正式开始吧!

一、项目背景与测试规划:先明确 "测什么" 和 "怎么测"

1.1 项目介绍

        本次实战的被测对象是一个基于 Web 的博客系统(后端 C++ 开发),核心功能模块包括:登录模块、博主信息模块、博客列表模块、博客编辑模块、博客详情模块。系统支持用户登录后查看、编辑、提交博客,未登录用户访问受限功能时会触发登录弹窗提示。

1.2 测试目标

验证博客系统核心功能的稳定性和正确性;实现关键流程的自动化执行,减少手动测试成本;快速定位迭代过程中引入的回归 bug;输出清晰、规范的测试报告,支撑项目上线决策。

1.3 测试范围与用例设计

        自动化测试并非 "面面俱到",而是聚焦核心流程和高频场景。结合系统功能,我们设计了以下测试用例(覆盖正常场景与异常场景):

二、环境搭建:3 步搞定自动化测试前置准备

        在开始编码前,我们需要先完成环境配置。请确保你的电脑已安装 Python 环境(3.8 及以上版本),然后按照以下步骤操作:

2.1 安装核心依赖包

        打开命令行终端,执行以下命令安装所需库:

# 安装Selenium(UI自动化核心库) pip install selenium==4.10.0 # 安装webdriver-manager(自动管理浏览器驱动) pip install webdriver-manager==4.0.0 

2.2 浏览器配置

        本文以 Chrome 浏览器为例(推荐版本 110+),无需手动下载 ChromeDriver:webdriver-manager 会自动检测浏览器版本并下载对应驱动,彻底解决 "驱动版本不匹配" 的问题。

        如果需要使用 Firefox 或 Edge 浏览器,只需修改后续代码中的浏览器配置(下文会详细说明)。

2.3 项目目录结构设计

        一个清晰的目录结构是自动化框架可维护的关键。我们采用以下目录结构组织项目:

blog_auto_test/ # 项目根目录 ├── common/ # 公共工具模块 │ └── Utils.py # 驱动管理、截图等公共功能 ├── cases/ # 测试用例模块 │ ├── BlogLogin.py # 登录模块测试用例 │ ├── BlogList.py # 博客列表模块测试用例 │ ├── BlogEdit.py # 博客编辑模块测试用例(后续补充) │ └── BlogDetail.py # 博客详情模块测试用例 ├── images/ # 截图存储目录(自动生成) │ └── 2024-05-20/ # 按日期分类的截图文件夹 ├── reports/ # 测试报告目录 │ └── test_report.md # 测试报告文件 └── RunCases.py # 测试用例执行入口

三、核心模块开发:封装公共工具,提高代码复用性

        在编写具体测试用例前,我们先封装公共工具类 —— 这能避免重复编码,让后续用例开发更高效。核心公共功能包括:驱动对象创建、自动截图、异常处理等。

3.1 驱动管理与截图工具封装(common/Utils.py)

import datetime import os.path import sys from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.common.exceptions import WebDriverException class Driver: """驱动管理类:创建浏览器驱动、截图、关闭驱动等功能封装""" driver = None # 类属性,全局共享驱动对象 def __init__(self): """初始化驱动对象,配置Chrome浏览器选项""" try: # 创建Chrome浏览器选项对象 options = webdriver.ChromeOptions() # 可选配置:无头模式(无界面运行,适合服务器环境) # options.add_argument('--headless=new') # 可选配置:忽略证书错误 options.add_argument('--ignore-certificate-errors') # 可选配置:禁用GPU加速(避免部分环境报错) options.add_argument('--disable-gpu') # 可选配置:设置窗口大小 options.add_argument('window-size=1920,1080') # 自动安装匹配的ChromeDriver并创建驱动对象 self.driver = webdriver.Chrome( service=Service(ChromeDriverManager().install()), options=options ) # 设置页面加载超时时间(10秒) self.driver.set_page_load_timeout(10) # 设置隐式等待时间(5秒,等待元素加载) self.driver.implicitly_wait(5) print(f"驱动初始化成功!ChromeDriver版本:{self.driver.capabilities['chrome']['chromedriverVersion']}") except WebDriverException as e: print(f"驱动初始化失败:{str(e)}") raise # 抛出异常,终止程序运行 except Exception as e: print(f"未知错误:{str(e)}") raise def get_screen_shot(self): """ 截图功能:按日期创建文件夹,截图文件名包含用例名和时间戳 返回值:截图文件路径 """ try: # 按当前日期创建截图文件夹(如:images/2024-05-20) dirname = datetime.datetime.now().strftime('%Y-%m-%d') screenshot_dir = f"../images/{dirname}" # 截图存储路径 if not os.path.exists(screenshot_dir): os.makedirs(screenshot_dir) print(f"创建截图文件夹:{screenshot_dir}") # 获取调用该方法的函数名(即测试用例名) case_name = sys._getframe().f_back.f_code.co_name # 截图文件名:用例名-时间戳.png(如:loginSucTest-2024-05-20-143025.png) filename = f"{case_name}-{datetime.datetime.now().strftime('%Y-%m-%d-%H%M%S')}.png" screenshot_path = os.path.join(screenshot_dir, filename) # 执行截图并保存 self.driver.save_screenshot(screenshot_path) print(f"截图成功!路径:{screenshot_path}") return screenshot_path except Exception as e: print(f"截图失败:{str(e)}") return None def quit_driver(self): """关闭驱动,释放资源""" if self.driver: self.driver.quit() print("🔌 驱动已关闭,资源释放完成") # 创建全局驱动实例(所有用例共享同一个驱动) blog_driver = Driver() 

3.2 代码说明与优化点

驱动全局共享:通过类属性driver和全局实例blog_driver,确保所有测试用例使用同一个浏览器实例,避免重复打开浏览器,提高执行效率;异常处理:捕获WebDriverException等常见异常,并打印详细日志,方便问题定位;灵活配置:预留了无头模式、窗口大小等可选配置,可根据实际测试环境调整;截图优化:按日期分类存储截图,文件名包含用例名和时间戳,便于追溯测试场景。

四、测试用例开发:逐个模块实现自动化测试

        接下来,我们按照测试规划,逐个模块编写测试用例。每个用例都遵循 "前置条件→操作步骤→断言验证" 的逻辑,确保测试结果的准确性。

4.1 登录模块测试(cases/BlogLogin.py)

        登录模块是系统的入口,也是自动化测试的核心场景。我们需要覆盖正常登录和多种异常登录场景:

import time from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException from common.Utils import blog_driver class BlogLogin: """登录模块测试用例""" def __init__(self): """初始化:设置登录页面URL,打开登录页""" self.login_url = "http://192.168.47.135:8653/blog_system/blog_login.html" self.driver = blog_driver.driver # 获取全局驱动对象 self.driver.get(self.login_url) print(f"打开登录页面:{self.login_url}") def clear_input(self): """清空账号和密码输入框(复用方法)""" username_input = self.driver.find_element(By.CSS_SELECTOR, "#username") password_input = self.driver.find_element(By.CSS_SELECTOR, "#password") username_input.clear() password_input.clear() print("清空账号密码输入框") def login_suc_test(self, username="admin", password="123"): """ 正常登录测试用例 :param username: 测试账号 :param password: 测试密码 """ try: print(f"\n===== 执行正常登录测试(账号:{username},密码:{password})=====") # 清空输入框 self.clear_input() # 输入账号密码 self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys(username) self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys(password) print(f"输入账号:{username},密码:{password}") # 点击登录按钮 self.driver.find_element(By.CSS_SELECTOR, "#submit").click() print("点击登录按钮") # 等待页面跳转(避免页面未加载完成就断言) time.sleep(2) # 断言:登录成功后跳转到博客列表页,且能找到博主头像元素 try: # 博主头像元素选择器(根据实际页面调整) avatar_element = self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.left > div > img") assert avatar_element.is_displayed() # 验证元素是否可见 print(f"正常登录测试通过!{username}账号登录成功") # 截图记录成功场景 blog_driver.get_screen_shot() # 退回登录页,为后续测试做准备 self.driver.back() time.sleep(1) except NoSuchElementException: print(f"正常登录测试失败:未找到博主头像元素,可能未跳转至列表页") blog_driver.get_screen_shot() raise except AssertionError: print(f"正常登录测试失败:博主头像元素不可见") blog_driver.get_screen_shot() raise except Exception as e: print(f"正常登录测试异常:{str(e)}") blog_driver.get_screen_shot() raise def login_fail_test(self, username="admin", password="111", expect_msg="用户名或密码错误!"): """ 异常登录测试用例 :param username: 测试账号 :param password: 测试密码 :param expect_msg: 预期错误提示 """ try: print(f"\n===== 执行异常登录测试(账号:{username},密码:{password})=====") # 清空输入框 self.clear_input() # 输入账号密码 self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys(username) self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys(password) print(f"输入账号:{username},密码:{password}") # 点击登录按钮 self.driver.find_element(By.CSS_SELECTOR, "#submit").click() print("点击登录按钮") # 等待错误提示加载 time.sleep(1) # 断言:页面显示预期的错误提示 actual_msg = self.driver.find_element(By.CSS_SELECTOR, "body").text print(f"预期错误提示:{expect_msg}") print(f"实际错误提示:{actual_msg}") assert expect_msg in actual_msg, f"错误提示不匹配!预期:{expect_msg},实际:{actual_msg}" print(f"异常登录测试通过!错误提示符合预期") # 截图记录失败场景 blog_driver.get_screen_shot() # 退回登录页(若页面未跳转,可省略) self.driver.back() time.sleep(1) except NoSuchElementException: print(f"异常登录测试失败:未找到错误提示元素") blog_driver.get_screen_shot() raise except AssertionError as e: print(f"异常登录测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"异常登录测试异常:{str(e)}") blog_driver.get_screen_shot() raise def login_empty_username_test(self): """账号为空登录测试""" print(f"\n===== 执行账号为空登录测试 =====") self.clear_input() # 仅输入密码 self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys("123") print(f"账号为空,输入密码:123") self.driver.find_element(By.CSS_SELECTOR, "#submit").click() time.sleep(1) # 断言:页面未跳转,且可能有错误提示(根据实际需求调整) assert self.driver.current_url == self.login_url, "账号为空时页面不应跳转" print(f"账号为空登录测试通过!页面未跳转") blog_driver.get_screen_shot() def login_empty_password_test(self): """密码为空登录测试""" print(f"\n===== 执行密码为空登录测试 =====") self.clear_input() # 仅输入账号 self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys("admin") print(f"输入账号:admin,密码为空") self.driver.find_element(By.CSS_SELECTOR, "#submit").click() time.sleep(1) # 断言:页面未跳转 assert self.driver.current_url == self.login_url, "密码为空时页面不应跳转" print(f"密码为空登录测试通过!页面未跳转") blog_driver.get_screen_shot() # 测试代码(单独运行时执行) if __name__ == "__main__": login_test = BlogLogin() # 执行正常登录测试(admin账号) login_test.login_suc_test("admin", "123") # 执行正常登录测试(lisi账号) login_test.login_suc_test("lisi", "123") # 执行密码错误测试 login_test.login_fail_test("admin", "111", "用户名或密码错误!") # 执行账号为空测试 login_test.login_empty_username_test() # 执行密码为空测试 login_test.login_empty_password_test() # 关闭驱动 blog_driver.quit_driver() 

4.2 博客列表模块测试(cases/BlogList.py)

        博客列表模块的核心测试点是 "登录状态下正常访问" 和 "未登录状态下跳转登录页",同时需要验证博客数量和跳转功能:

import time from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException from common.Utils import blog_driver from cases.BlogLogin import BlogLogin class BlogList: """博客列表模块测试用例""" def __init__(self): """初始化:设置列表页URL,获取驱动对象""" self.list_url = "http://192.168.47.135:8653/blog_system/blog_list.html" self.driver = blog_driver.driver print(f"博客列表页URL:{self.list_url}") def list_login_status_test(self): """登录状态下访问博客列表页测试""" try: print(f"\n===== 执行登录状态下博客列表测试 =====") # 前置条件:先登录admin账号 login_test = BlogLogin() login_test.login_suc_test("admin", "123") # 打开博客列表页 self.driver.get(self.list_url) print(f"已登录,访问列表页:{self.list_url}") time.sleep(2) # 断言1:页面显示博主头像(验证登录状态有效) avatar_element = self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.left > div > img") assert avatar_element.is_displayed(), "博主头像未显示,登录状态可能失效" print("断言通过:博主头像正常显示") # 断言2:页面显示第一篇博客链接 first_blog = self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.right > div:nth-child(1) > a") assert first_blog.is_displayed(), "第一篇博客链接未找到" print(f"断言通过:第一篇博客链接正常显示(标题:{first_blog.text})") # 断言3:博客数量大于10篇 all_blogs = self.driver.find_elements(By.CSS_SELECTOR, "body > div.container > div.right > div") blog_count = len(all_blogs) print(f"当前博客总数:{blog_count}") assert blog_count > 10, f"博客数量不足10篇,实际数量:{blog_count}" print("断言通过:博客数量大于10篇") # 操作:点击第一篇博客,跳转至详情页 first_blog.click() time.sleep(2) # 断言4:跳转至博客详情页(通过标题验证) assert self.driver.title == "博客详情页", f"跳转失败,当前页面标题:{self.driver.title}" print("断言通过:成功跳转至博客详情页") # 截图记录 blog_driver.get_screen_shot() print("===== 登录状态下博客列表测试通过 =====") except NoSuchElementException as e: print(f"登录状态下博客列表测试失败:未找到元素 - {str(e)}") blog_driver.get_screen_shot() raise except AssertionError as e: print(f"登录状态下博客列表测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"登录状态下博客列表测试异常:{str(e)}") blog_driver.get_screen_shot() raise def list_unlogin_status_test(self): """未登录状态下访问博客列表页测试""" try: print(f"\n===== 执行未登录状态下博客列表测试 =====") # 前置条件:确保未登录(关闭所有关联页面,重新打开浏览器) blog_driver.quit_driver() # 关闭现有驱动 new_driver = Driver() # 创建新驱动(未登录状态) self.driver = new_driver.driver # 直接访问博客列表页 self.driver.get(self.list_url) print(f"未登录,直接访问列表页:{self.list_url}") time.sleep(2) # 断言:跳转至登录页(通过URL验证) assert self.driver.current_url == "http://192.168.47.135:8653/blog_system/blog_login.html", \ f"未跳转至登录页,当前URL:{self.driver.current_url}" print("断言通过:未登录状态下访问列表页,自动跳转至登录页") # 截图记录 new_driver.get_screen_shot() # 关闭新驱动 new_driver.quit_driver() print("===== 未登录状态下博客列表测试通过 =====") except AssertionError as e: print(f"未登录状态下博客列表测试失败:{str(e)}") new_driver.get_screen_shot() raise except Exception as e: print(f"未登录状态下博客列表测试异常:{str(e)}") new_driver.get_screen_shot() raise # 测试代码(单独运行时执行) if __name__ == "__main__": list_test = BlogList() # 执行登录状态下测试 list_test.list_login_status_test() # 执行未登录状态下测试 list_test.list_unlogin_status_test() 

4.3 博客详情模块测试(cases/BlogDetail.py)

        博客详情模块依赖博客列表模块的跳转,核心测试点是页面元素完整性和未登录访问限制:

import time from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException from common.Utils import blog_driver from cases.BlogLogin import BlogLogin from cases.BlogList import BlogList class BlogDetail: """博客详情模块测试用例""" def __init__(self, blog_id=15): """初始化:设置详情页URL(带博客ID)""" self.detail_url = f"http://192.168.47.135:8653/blog_system/blog_detail.html?blogId={blog_id}" self.driver = blog_driver.driver self.blog_id = blog_id print(f"博客详情页URL(ID:{blog_id}):{self.detail_url}") def detail_login_status_test(self): """登录状态下访问博客详情页测试""" try: print(f"\n===== 执行登录状态下博客详情测试(ID:{self.blog_id})=====") # 前置条件:登录并进入列表页 login_test = BlogLogin() login_test.login_suc_test("admin", "123") list_test = BlogList() list_test.driver.get(list_test.list_url) time.sleep(2) # 从列表页点击博客进入详情页(模拟用户真实操作) first_blog = list_test.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.right > div:nth-child(1) > a") first_blog.click() time.sleep(2) # 断言1:页面标题为"博客详情页" assert self.driver.title == "博客详情页", f"当前页面不是详情页,标题:{self.driver.title}" print("断言通过:页面标题为'博客详情页'") # 断言2:显示博客标题 blog_title = self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.right > div > h3") assert blog_title.is_displayed(), "博客标题未显示" print(f"断言通过:博客标题正常显示(标题:{blog_title.text})") # 断言3:显示发布日期 publish_date = self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.right > div > div.date") assert publish_date.is_displayed(), "发布日期未显示" print(f"断言通过:发布日期正常显示(日期:{publish_date.text})") # 断言4:显示博客正文 blog_content = self.driver.find_element(By.CSS_SELECTOR, "#content") assert blog_content.is_displayed(), "博客正文未显示" print(f"断言通过:博客正文正常显示(前50字:{blog_content.text[:50]}...)") # 截图记录 blog_driver.get_screen_shot() print("===== 登录状态下博客详情测试通过 =====") except NoSuchElementException as e: print(f"登录状态下博客详情测试失败:未找到元素 - {str(e)}") blog_driver.get_screen_shot() raise except AssertionError as e: print(f"登录状态下博客详情测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"登录状态下博客详情测试异常:{str(e)}") blog_driver.get_screen_shot() raise def detail_unlogin_status_test(self): """未登录状态下访问博客详情页测试""" try: print(f"\n===== 执行未登录状态下博客详情测试(ID:{self.blog_id})=====") # 前置条件:未登录(创建新驱动) blog_driver.quit_driver() new_driver = Driver() self.driver = new_driver.driver # 直接访问详情页URL self.driver.get(self.detail_url) print(f"未登录,直接访问详情页:{self.detail_url}") time.sleep(2) # 断言:跳转至登录页 assert self.driver.current_url == "http://192.168.47.135:8653/blog_system/blog_login.html", \ f"未跳转至登录页,当前URL:{self.driver.current_url}" print("断言通过:未登录状态下访问详情页,自动跳转至登录页") # 截图记录 new_driver.get_screen_shot() new_driver.quit_driver() print("===== 未登录状态下博客详情测试通过 =====") except AssertionError as e: print(f"未登录状态下博客详情测试失败:{str(e)}") new_driver.get_screen_shot() raise except Exception as e: print(f"未登录状态下博客详情测试异常:{str(e)}") new_driver.get_screen_shot() raise # 测试代码(单独运行时执行) if __name__ == "__main__": detail_test = BlogDetail(blog_id=15) # 执行登录状态下测试 detail_test.detail_login_status_test() # 执行未登录状态下测试 detail_test.detail_unlogin_status_test() 

4.4 博客编辑模块测试(cases/BlogEdit.py)

        博客编辑模块是核心功能之一,需要覆盖正常提交和异常提交(无标题、无内容)场景:

import time from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException from common.Utils import blog_driver from cases.BlogLogin import BlogLogin class BlogEdit: """博客编辑模块测试用例""" def __init__(self): """初始化:设置编辑页URL,获取驱动对象""" self.edit_url = "http://192.168.47.135:8653/blog_system/blog_edit.html" self.driver = blog_driver.driver print(f"博客编辑页URL:{self.edit_url}") def edit_login_normal_submit_test(self): """登录状态下正常提交博客测试""" try: print(f"\n===== 执行登录状态下正常提交博客测试 =====") # 前置条件:登录admin账号 login_test = BlogLogin() login_test.login_suc_test("admin", "123") # 打开编辑页 self.driver.get(self.edit_url) time.sleep(2) print(f"已登录,访问编辑页:{self.edit_url}") # 输入博客标题和内容 title_input = self.driver.find_element(By.CSS_SELECTOR, "#title") # 假设标题输入框ID为title content_input = self.driver.find_element(By.CSS_SELECTOR, "#content") # 假设内容输入框ID为content submit_btn = self.driver.find_element(By.CSS_SELECTOR, "#submit") # 假设提交按钮ID为submit blog_title = f"自动化测试实战_测试博客_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}" blog_content = "这是一篇通过自动化测试脚本提交的博客,用于验证编辑模块的正常提交功能。\n测试内容:标题+正文完整填写,提交后应跳转至列表页并显示新增博客。" title_input.clear() title_input.send_keys(blog_title) content_input.clear() content_input.send_keys(blog_content) print(f"输入博客标题:{blog_title}") print(f"输入博客内容:{blog_content[:30]}...") # 点击提交按钮 submit_btn.click() time.sleep(3) # 等待提交完成并跳转 print("点击提交按钮") # 断言1:跳转至博客列表页 assert self.driver.title == "博客列表页", f"提交后未跳转至列表页,当前标题:{self.driver.title}" print("断言通过:提交后成功跳转至列表页") # 断言2:列表页显示新增的博客 all_blogs = self.driver.find_elements(By.CSS_SELECTOR, "body > div.container > div.right > div > a") blog_titles = [blog.text for blog in all_blogs] assert blog_title in blog_titles, f"新增博客未在列表中显示(标题:{blog_title})" print(f"断言通过:新增博客已显示在列表页") # 截图记录 blog_driver.get_screen_shot() print("===== 登录状态下正常提交博客测试通过 =====") except NoSuchElementException as e: print(f"正常提交博客测试失败:未找到元素 - {str(e)}") blog_driver.get_screen_shot() raise except AssertionError as e: print(f"正常提交博客测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"正常提交博客测试异常:{str(e)}") blog_driver.get_screen_shot() raise def edit_login_no_title_submit_test(self): """登录状态下无标题提交测试""" try: print(f"\n===== 执行登录状态下无标题提交博客测试 =====") # 前置条件:登录并打开编辑页 login_test = BlogLogin() login_test.login_suc_test("admin", "123") self.driver.get(self.edit_url) time.sleep(2) # 仅输入内容,不输入标题 content_input = self.driver.find_element(By.CSS_SELECTOR, "#content") submit_btn = self.driver.find_element(By.CSS_SELECTOR, "#submit") blog_content = "这是无标题提交测试的内容,预期提交失败。" content_input.clear() content_input.send_keys(blog_content) print(f"未输入标题,输入内容:{blog_content}") # 点击提交按钮 submit_btn.click() time.sleep(1) print("点击提交按钮") # 断言:页面未跳转,仍停留在编辑页 assert self.driver.current_url == self.edit_url, f"无标题提交时页面不应跳转,当前URL:{self.driver.current_url}" print("断言通过:无标题提交时页面未跳转") # (可选)断言:显示"请输入标题"提示 try: error_msg = self.driver.find_element(By.CSS_SELECTOR, "#title-error").text # 假设错误提示元素 assert "请输入标题" in error_msg, f"错误提示不匹配,实际:{error_msg}" print(f"断言通过:显示正确的错误提示:{error_msg}") except NoSuchElementException: print("未找到错误提示元素,但页面未跳转,测试通过") # 截图记录 blog_driver.get_screen_shot() print("===== 登录状态下无标题提交博客测试通过 =====") except AssertionError as e: print(f"无标题提交博客测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"无标题提交博客测试异常:{str(e)}") blog_driver.get_screen_shot() raise def edit_login_no_content_submit_test(self): """登录状态下无内容提交测试""" try: print(f"\n===== 执行登录状态下无内容提交博客测试 =====") # 前置条件:登录并打开编辑页 login_test = BlogLogin() login_test.login_suc_test("admin", "123") self.driver.get(self.edit_url) time.sleep(2) # 仅输入标题,不输入内容 title_input = self.driver.find_element(By.CSS_SELECTOR, "#title") submit_btn = self.driver.find_element(By.CSS_SELECTOR, "#submit") blog_title = "无内容提交测试的标题" title_input.clear() title_input.send_keys(blog_title) print(f"输入标题:{blog_title},未输入内容") # 点击提交按钮 submit_btn.click() time.sleep(1) print("点击提交按钮") # 断言:页面未跳转 assert self.driver.current_url == self.edit_url, f"无内容提交时页面不应跳转,当前URL:{self.driver.current_url}" print("断言通过:无内容提交时页面未跳转") # (可选)断言:显示"请输入内容"提示 try: error_msg = self.driver.find_element(By.CSS_SELECTOR, "#content-error").text assert "请输入内容" in error_msg, f"错误提示不匹配,实际:{error_msg}" print(f"断言通过:显示正确的错误提示:{error_msg}") except NoSuchElementException: print("未找到错误提示元素,但页面未跳转,测试通过") # 截图记录 blog_driver.get_screen_shot() print("===== 登录状态下无内容提交博客测试通过 =====") except AssertionError as e: print(f"无内容提交博客测试失败:{str(e)}") blog_driver.get_screen_shot() raise except Exception as e: print(f"无内容提交博客测试异常:{str(e)}") blog_driver.get_screen_shot() raise def edit_unlogin_submit_test(self): """未登录状态下提交博客测试""" try: print(f"\n===== 执行未登录状态下提交博客测试 =====") # 前置条件:未登录 blog_driver.quit_driver() new_driver = Driver() self.driver = new_driver.driver # 直接访问编辑页并尝试提交 self.driver.get(self.edit_url) time.sleep(2) print(f"未登录,访问编辑页:{self.edit_url}") # 输入标题和内容 title_input = self.driver.find_element(By.CSS_SELECTOR, "#title") content_input = self.driver.find_element(By.CSS_SELECTOR, "#content") submit_btn = self.driver.find_element(By.CSS_SELECTOR, "#submit") title_input.send_keys("未登录提交测试") content_input.send_keys("未登录状态下不应提交成功") print("输入标题和内容,尝试提交") # 点击提交按钮 submit_btn.click() time.sleep(2) print("点击提交按钮") # 断言:跳转至登录页或提交按钮失效(根据实际逻辑调整) assert self.driver.current_url == "http://192.168.47.135:8653/blog_system/blog_login.html", \ f"未登录提交应跳转至登录页,当前URL:{self.driver.current_url}" print("断言通过:未登录状态下提交,跳转至登录页") # 截图记录 new_driver.get_screen_shot() new_driver.quit_driver() print("===== 未登录状态下提交博客测试通过 =====") except AssertionError as e: print(f"未登录提交博客测试失败:{str(e)}") new_driver.get_screen_shot() raise except Exception as e: print(f"未登录提交博客测试异常:{str(e)}") new_driver.get_screen_shot() raise # 测试代码(单独运行时执行) if __name__ == "__main__": edit_test = BlogEdit() # 执行正常提交测试 edit_test.edit_login_normal_submit_test() # 执行无标题提交测试 edit_test.edit_login_no_title_submit_test() # 执行无内容提交测试 edit_test.edit_login_no_content_submit_test() # 执行未登录提交测试 edit_test.edit_unlogin_submit_test() 

五、测试用例执行入口:一键运行所有测试(RunCases.py)

        为了方便执行所有测试用例,我们创建一个统一的执行入口文件,按模块顺序执行测试:

import sys import time from common.Utils import blog_driver from cases.BlogLogin import BlogLogin from cases.BlogList import BlogList from cases.BlogDetail import BlogDetail from cases.BlogEdit import BlogEdit def run_all_cases(): """执行所有自动化测试用例""" print("="*50) print("开始执行博客系统自动化测试用例") print(f"执行时间:{time.strftime('%Y-%m-%d %H:%M:%S')}") print("="*50) # 存储测试结果 test_results = { "passed": 0, "failed": 0, "total": 0 } try: # 1. 执行登录模块测试 print("\n" + "="*30) print("开始执行登录模块测试") print("="*30) login_test = BlogLogin() # 正常登录(admin) login_test.login_suc_test("admin", "123") test_results["passed"] += 1 # 正常登录(lisi) login_test.login_suc_test("lisi", "123") test_results["passed"] += 1 # 密码错误 login_test.login_fail_test("admin", "111", "用户名或密码错误!") test_results["passed"] += 1 # 账号为空 login_test.login_empty_username_test() test_results["passed"] += 1 # 密码为空 login_test.login_empty_password_test() test_results["passed"] += 1 # 2. 执行博客列表模块测试 print("\n" + "="*30) print("开始执行博客列表模块测试") print("="*30) list_test = BlogList() # 登录状态下测试 list_test.list_login_status_test() test_results["passed"] += 1 # 未登录状态下测试 list_test.list_unlogin_status_test() test_results["passed"] += 1 # 3. 执行博客详情模块测试 print("\n" + "="*30) print("开始执行博客详情模块测试") print("="*30) detail_test = BlogDetail(blog_id=15) # 登录状态下测试 detail_test.detail_login_status_test() test_results["passed"] += 1 # 未登录状态下测试 detail_test.detail_unlogin_status_test() test_results["passed"] += 1 # 4. 执行博客编辑模块测试 print("\n" + "="*30) print("开始执行博客编辑模块测试") print("="*30) edit_test = BlogEdit() # 正常提交 edit_test.edit_login_normal_submit_test() test_results["passed"] += 1 # 无标题提交 edit_test.edit_login_no_title_submit_test() test_results["passed"] += 1 # 无内容提交 edit_test.edit_login_no_content_submit_test() test_results["passed"] += 1 # 未登录提交 edit_test.edit_unlogin_submit_test() test_results["passed"] += 1 except Exception as e: print(f"\n测试执行过程中出现异常:{str(e)}") test_results["failed"] += 1 finally: # 计算总用例数 test_results["total"] = test_results["passed"] + test_results["failed"] # 关闭驱动 blog_driver.quit_driver() # 输出测试总结 print("\n" + "="*50) print("博客系统自动化测试执行完成") print("="*50) print(f"总用例数:{test_results['total']}") print(f"通过用例数:{test_results['passed']}") print(f"失败用例数:{test_results['failed']}") pass_rate = (test_results['passed'] / test_results['total']) * 100 if test_results['total'] > 0 else 100 print(f"测试通过率:{pass_rate:.2f}%") print("="*50) # 若有失败用例,退出码设为1(便于CI/CD集成) if test_results["failed"] > 0: sys.exit(1) else: sys.exit(0) if __name__ == "__main__": run_all_cases() 

六、测试报告生成:打造专业、清晰的测试成果文档

        测试执行完成后,需要生成一份规范的测试报告,用于向团队展示测试结果、进度和问题。以下为大家提供基于本次实战的测试报告模板(可根据实际项目调整):

【测试报告】博客系统 UI 自动化测试报告

1. 报告基本信息

项目名称博客系统(C++ 后端 Web 项目)版本号V1.0
发布类型分级发布测试负责人OPCHEN
测试完成日期2026-1-14联系方式(根据实际填写)
评审人王一博、肖战、马绵、李产品、王交互批准人李产品
评审日期2026-1-14批准日期2026-1-14

2. 项目背景

2.1 测试目标及测试任务概括
测试目标:验证博客系统核心功能(登录、列表、详情、编辑)的正确性和稳定性,实现关键流程自动化,减少手动测试成本,支撑项目上线;测试任务:设计并执行 14 条 UI 自动化测试用例,覆盖正常场景与异常场景,生成测试报告并跟踪问题修复。
2.2 被测系统及相关信息
被测系统:博客系统(Web 端);系统地址:http://xxxxxxxxxxxxxxxxxxxxxx;代码包及文档:diff 链接(根据实际填写)、接口文档(根据实际填写)、《测试计划》;依赖环境:Chrome 浏览器 110+、Python 3.8+、Selenium 4.10.0。
2.3 产品需求和设计文档
《博客系统需求文档》;《博客系统技术设计文档》;《博客系统 UI 设计图》。

3. 测试安排

模块子模块前端开发后端开发提测时间测试负责人工时排期进度备注
登录模块登录功能肖战王一博2024-05-18OPCHEN0.5d2026-1-10测试完成覆盖正常 / 异常登录
博客列表模块列表展示、跳转肖战王一博2024-05-18OPCHEN0.5d2026-1-10测试完成登录 / 未登录状态
博客详情模块详情展示肖战王一博2024-05-18OPCHEN0.5d2026-1-11测试完成登录 / 未登录状态
博客编辑模块编辑、提交肖战王一博2024-05-18OPCHEN0.5d2026-1-11测试完成正常 / 异常提交

4. 测试结果汇总

4.1 测试用例执行情况
模块用例总数通过数失败数通过率
登录模块550100.00%
博客列表模块220100.00%
博客详情模块220100.00%
博客编辑模块550100.00%
总计14140100.00%
4.2 缺陷统计与分析
本次自动化测试未发现新增缺陷;历史缺陷修复验证:所有已提交的缺陷均已修复,且在本次自动化测试中无回归。
4.3 截图示例
测试场景截图路径说明
admin 账号正常登录成功images/2026-1-10/loginSucTest-20260110143025.png跳转至列表页,显示博主头像
密码错误异常登录images/2026-1-10/loginFailTest-20260110143210.png显示 "用户名或密码错误!"
博客列表页正常展示images/2026-1-11/listLoginStatusTest-20260111143508.png博客数量大于 10 篇
编辑博客正常提交images/2026-1-11/editLoginNormalSubmitTest-20260111144015.png新增博客显示在列表页

5. 测试结论与建议

5.1 测试结论
博客系统核心功能(登录、列表、详情、编辑)的 UI 自动化测试已全部完成,14 条用例全部通过,通过率 100%;系统在测试场景下运行稳定,未出现功能异常、崩溃等问题;自动化测试框架运行正常,能够准确执行测试用例、捕获异常并生成截图,可支撑后续迭代的回归测试。
5.2 后续建议
扩展用例覆盖范围:新增博客删除、修改密码、评论等功能的自动化用例;集成 CI/CD 流程:将自动化测试脚本集成到 Jenkins 等工具,实现每次代码提交后自动执行测试;优化框架稳定性:增加重试机制(如元素未找到时自动重试)、日志详细程度优化;定期维护用例:当系统 UI 发生变更时,及时更新用例中的元素选择器,确保自动化用例的有效性。

总结

        通过本次博客系统的自动化测试实战,我们从 0 到 1 搭建了一套完整的 UI 自动化测试框架,覆盖了用例设计、脚本开发、执行入口、报告生成等全流程,并且实现了代码复用、异常处理、截图追溯等关键功能。

        最后,自动化测试是一个 "实践出真知" 的领域,只有多动手、多踩坑,才能不断提升自己的技术水平。希望本文的实战案例能为你提供一些帮助,祝你在自动化测试的道路上越走越远!

Read more

Flutter for OpenHarmony:async 异步编程的强力补丁,流处理与集合操作的扩展库(Dart 官方出品) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:async 异步编程的强力补丁,流处理与集合操作的扩展库(Dart 官方出品) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 Dart 语言天生支持异步编程(Future, Stream, async/await),这使得它非常适合 UI 开发。然而,标准库 dart:async 提供的是最基础的原语。当你面对复杂的异步场景时,比如: * “我需要合并三个 Stream,无论谁来了数据都处理。” * “我要把一个 Stream 切分成块,但不想手动写 transformer。” * “我想缓存 Future 的结果,防止重复网络请求。” 这时候,async package 就登场了。它是由 Dart 团队维护的官方扩展库,提供了大量实用的工具类、集合操作符和 Stream 辅助函数,填补了标准库在复杂业务场景下的空白。 对于 OpenHarmony 开发,由于鸿蒙应用的界面更新高度依赖异步事件驱动(

By Ne0inhk
WSL,Ubuntu-24.04如何添加图形桌面环境(Xfce 4)

WSL,Ubuntu-24.04如何添加图形桌面环境(Xfce 4)

一、Xfce介绍          Xfce 是用于类 UNIX 操作系统的轻量级桌面环境。它的目标是速度快、占用系统资源少,同时具有视觉吸引力和用户友好性。它由许多组件组成,这些组件提供了现代桌面环境的全部功能。它们是单独打包的,您可以从可用的软件包中进行选择,以创建最佳的个人工作环境。         Xfce 可以安装在多个 UNIX 平台上。众所周知,它可以在 Linux、NetBSD、FreeBSD、OpenBSD、Solaris、Cygwin 和 MacOS X、x86、PPC、Sparc、Alpha ...上编译。 二、安装步骤 下面是WSL里。Ubuntu-24.04添加Xfce图形桌面环境的具体步骤 1. 更新软件包列表 sudo apt update 2. 安装可用的软件包更新 sudo apt upgrade 3.

By Ne0inhk

Flutter 三方库 super_dates 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、强类型、更优雅的 DateTime 增强与时间逻辑审计引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 super_dates 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、强类型、更优雅的 DateTime 增强与时间逻辑审计引擎 在鸿蒙(OpenHarmony)系统的日程管理、精密任务调度(如鸿蒙版闹钟/日历)、理财工具或带有复杂时间区间(Periods)计算的应用中,如何摆脱标准 DateTime 库中那些模糊的整数偏移,转而使用语义明确、强类型保障的现代日期 API?super_dates 为开发者提供了一套工业级的、基于 Extension 的 DateTime 深度增强方案。本文将深入实战其在鸿蒙时间维度逻辑层中的应用。 前言 什么是 SuperDates?它不是一个替代 DateTime 的庞大框架,而是对 Dart 原生时间类的一次“极致外科手术级”

By Ne0inhk
华三(H3C)交换机基本运维命令及配置案例说明

华三(H3C)交换机基本运维命令及配置案例说明

华三(H3C)交换机基本运维命令及配置案例说明 一、核心运维命令(高频必备) 本文整理运维日常最常用的命令,覆盖登录管理、状态查询、配置操作、故障排查四大核心场景,命令均经过实操验证,适配华三(H3C)主流交换机型号(如S5120、S5560、MSR系列等)。 实操案例设备为:MSR56-60 (一)登录与视图管理(运维入口基础) 操作场景命令运维说明Console口本地登录(终端软件连接,波特率9600,无校验)设备初始化、远程登录故障时必备,物理连接Console线后直接进入SSH远程登录(推荐)PC端:ssh 用户名@交换机IP交换机端启用:ssh server enable加密传输,规避Telnet明文风险,运维远程管理首选视图切换(用户→系统)system-view(缩写:sys)所有配置操作需进入系统视图,提示符从变为[H3C]端口视图切换interface GigabitEthernet

By Ne0inhk