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


目录

前言

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

[1.1 项目介绍](#1.1 项目介绍)

[1.2 测试目标](#1.2 测试目标)

[1.3 测试范围与用例设计](#1.3 测试范围与用例设计)

​编辑

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

[2.1 安装核心依赖包](#2.1 安装核心依赖包)

[2.2 浏览器配置](#2.2 浏览器配置)

[2.3 项目目录结构设计](#2.3 项目目录结构设计)

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

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

[3.2 代码说明与优化点](#3.2 代码说明与优化点)

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

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

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

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

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

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

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

[【测试报告】博客系统 UI 自动化测试报告](#【测试报告】博客系统 UI 自动化测试报告)

[1. 报告基本信息](#1. 报告基本信息)

[2. 项目背景](#2. 项目背景)

[2.1 测试目标及测试任务概括](#2.1 测试目标及测试任务概括)

[2.2 被测系统及相关信息](#2.2 被测系统及相关信息)

[2.3 产品需求和设计文档](#2.3 产品需求和设计文档)

[3. 测试安排](#3. 测试安排)

[4. 测试结果汇总](#4. 测试结果汇总)

[4.1 测试用例执行情况](#4.1 测试用例执行情况)

[4.2 缺陷统计与分析](#4.2 缺陷统计与分析)

[4.3 截图示例](#4.3 截图示例)

[5. 测试结论与建议](#5. 测试结论与建议)

[5.1 测试结论](#5.1 测试结论)

[5.2 后续建议](#5.2 后续建议)

总结


前言

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

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


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

1.1 项目介绍

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

1.2 测试目标

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

1.3 测试范围与用例设计

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

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

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

2.1 安装核心依赖包

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

bash 复制代码
# 安装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)

python 复制代码
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 代码说明与优化点

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

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

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

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

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

python 复制代码
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)

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

python 复制代码
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)

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

python 复制代码
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)

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

python 复制代码
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

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

python 复制代码
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-18 OPCHEN 0.5d 2026-1-10 测试完成 覆盖正常 / 异常登录
博客列表模块 列表展示、跳转 肖战 王一博 2024-05-18 OPCHEN 0.5d 2026-1-10 测试完成 登录 / 未登录状态
博客详情模块 详情展示 肖战 王一博 2024-05-18 OPCHEN 0.5d 2026-1-11 测试完成 登录 / 未登录状态
博客编辑模块 编辑、提交 肖战 王一博 2024-05-18 OPCHEN 0.5d 2026-1-11 测试完成 正常 / 异常提交

4. 测试结果汇总

4.1 测试用例执行情况
模块 用例总数 通过数 失败数 通过率
登录模块 5 5 0 100.00%
博客列表模块 2 2 0 100.00%
博客详情模块 2 2 0 100.00%
博客编辑模块 5 5 0 100.00%
总计 14 14 0 100.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 自动化测试框架,覆盖了用例设计、脚本开发、执行入口、报告生成等全流程,并且实现了代码复用、异常处理、截图追溯等关键功能。

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

相关推荐
pulinzt2 小时前
【python】第四节引入模版+文件的读取
开发语言·python
@zulnger3 小时前
读写Excel
开发语言·python·excel
科士威传动3 小时前
工业基础与高端制造的支撑座选择需求
大数据·科技·机器人·自动化·制造
小二·10 小时前
Python Web 开发进阶实战:性能压测与调优 —— Locust + Prometheus + Grafana 构建高并发可观测系统
前端·python·prometheus
C_心欲无痕10 小时前
Dockerfile:构建 Docker 镜像
运维·docker·容器
zz_nj11 小时前
工作的环境
linux·运维·服务器
七牛云行业应用11 小时前
重构实录:我删了 5 家大模型 SDK,只留了 OpenAI 标准库
python·系统架构·大模型·aigc·deepseek
知乎的哥廷根数学学派11 小时前
基于多模态特征融合和可解释性深度学习的工业压缩机异常分类与预测性维护智能诊断(Python)
网络·人工智能·pytorch·python·深度学习·机器学习·分类
C_心欲无痕12 小时前
nginx - 实现域名跳转的几种方式
运维·前端·nginx