「萧瑟其中」出品:开源博客系统全链路测试实战(功能+自动化+性能)

目录

前言

[1. 项目简介](#1. 项目简介)

[1.1 注册界面:](#1.1 注册界面:)

[1.2 登陆界面:](#1.2 登陆界面:)

[1.3 博客列表页:](#1.3 博客列表页:)

[1.4 博客详情页:](#1.4 博客详情页:)

[1.5 博客编辑页:](#1.5 博客编辑页:)

[1.6 博客系统信息:](#1.6 博客系统信息:)

[2. 测试用例](#2. 测试用例)

[3. 功能测试](#3. 功能测试)

[4. 自动化测试](#4. 自动化测试)

[4.1 准备工作](#4.1 准备工作)

[4.2 Common目录](#4.2 Common目录)

[4.3 tests目录](#4.3 tests目录)

[4.4 cases目录](#4.4 cases目录)

[5. 性能测试](#5. 性能测试)

[5.1 30用户登录接口并发压测](#5.1 30用户登录接口并发压测)

[5.2 30用户同步并发全链路压测](#5.2 30用户同步并发全链路压测)

[5.3 40并发阶梯式负载测试](#5.3 40并发阶梯式负载测试)

[6. 测试结果](#6. 测试结果)

总结


前言

为了将软件测试的理论知识Web UI 自动化 实战技能深度融合,特意选取了一款开源博客系统作为测试目标。项目初期,首先完成了详尽的测试用例设计与手工执行,以确保系统核心业务逻辑的完整性;在此基础上,进一步搭建了基于 SeleniumPython 的自动化测试框架,最终实现了对博客系统核心业务全流程的自动化回归,有效巩固了自身的测试工具链应用能力


1. 项目简介

本次测试对象是一款采用 Java 后端与 JavaScript 前端构建的开源博客系统。系统核心操作界面共包含五个功能模块:注册页、登录页、博客列表页、博客详情页以及博客编辑页。

1.1 注册界面:

  • 用户需填写系统中不存在的用户名、两次确认密码(需保持一致),并可选择填写个人 GitHub 主页。
  • 提交注册信息后,浏览器会弹出成功提示弹窗。
  • 点击确认按钮后自动跳转至登录页面。

1.2 登陆界面:

  • 用户输入对应的账号的用户名与密码。
  • 若校验通过,系统会跳转至博客列表页;
  • 若账号不存在或密码错误,页面则会弹出相应的错误提示弹窗。

1.3 博客列表页:

  • 支持页面上下滑动浏览,页面默认按时间倒序展示最新发布的五篇博客,底部提供分页功能。
  • 点击某篇博客下方的"查看全文"按钮,即可跳转至该篇博客的详情页面。

1.4 博客详情页:

  • 页面清晰展示了博客的完整内容以及作者的个人信息。
  • 若当前登录用户即是该文章的作者,页面右上角会额外显示"编辑"与"删除"功能按钮。
  • 点击"编辑"将跳转至博客编辑页;
  • 点击"删除"则会弹出二次确认弹窗,确认删除后将返回博客列表页。

1.5 博客编辑页:

  • 在此页面,用户可以对已有博客的标题与正文内容进行修改。
  • 编辑完成后,点击"发布文章"按钮,系统会弹出发布确认弹窗;
  • 点击弹窗中的确认按钮,即可保存新内容并自动返回至博客列表页。

1.6 博客系统信息:

2. 测试用例

3. 功能测试

本视频完整展示了一条覆盖用户注册、登录鉴权、列表浏览、详情查阅 ,直至文章编辑与发布的全链路自动化测试流程。

自动化测试

4. 自动化测试

4.1 准备工作

  • 创建python项目,下载webdriver和selenium依赖。
  • 创建common、cases和tests目录

这是Web UI自动化测试的项目目录。

4.2 Common目录

  • config.py:配置合法且不存在的用户名和密码,以及不可用的用户名和密码
python 复制代码
# ======================== 测试账号配置 ========================

# 注册 & 登录成功 使用的账号
VALID_USER = {
    "username": "xxxxxxx",
    "password": "123456",
}

# 登录失败测试 使用的错误密码
INVALID_USER = {
    "username": "xxxxx",
    "password": "111111111",
}
  • Utils.py:初始化Chrome驱动,实现截图功能函数。
python 复制代码
import datetime
import os
import sys
import traceback

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager


class Dirver:
    driver = ""
    def __init__(self):
        options = webdriver.ChromeOptions()
        options.add_argument("--start-maximized")
        self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()),options=options)

    def getScreenShot(self):
        # 以 Utils.py 所在目录(common/.. → BlogTest/)为基准
        base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        dirname = datetime.datetime.now().strftime('%Y-%m-%d')
        screenshot_dir = os.path.join(base_dir, "images", dirname)
        os.makedirs(screenshot_dir, exist_ok=True)

        caller_name = traceback.extract_stack()[-2].name  # 获取调用者函数名
        filename = (sys._getframe().f_back.f_code.co_name + "-" +
                    datetime.datetime.now().strftime('%Y-%m-%d-%H%M%S') + ".png")
        self.driver.save_screenshot(os.path.join(screenshot_dir, filename))


BlogDriver = Dirver()

4.3 tests目录

  • BlogRegister.py:访问登陆界面,点击注册提示,跳转至注册界面,输入合法的用户名和密码,正常注册用户。
python 复制代码
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from common.Utils import BlogDriver
from common.config import VALID_USER


class BlogRegister:
    url = ""
    driver = ""

    def __init__(self):
        self.url = "http://49.235.61.184:19090/blog_login.html"
        self.driver = BlogDriver.driver
        self.driver.get(self.url)

    def RegisterSuccessTest(self):
        """注册成功测试:从登录页点击注册链接 → 填写注册信息 → 提交 → 处理弹窗 → 回到登录页"""
        time.sleep(2)

        # 1. 点击登录页的注册选项,跳转到注册页面
        self.driver.find_element(
            By.CSS_SELECTOR,
            "body > div.container-login > div > div:nth-child(5) > a"
        ).click()
        time.sleep(2)

        # 2. 填写注册表单
        username = VALID_USER["username"]
        password = VALID_USER["password"]

        self.driver.find_element(By.CSS_SELECTOR, "#username").clear()
        self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys(username)

        self.driver.find_element(By.CSS_SELECTOR, "#password").clear()
        self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys(password)

        self.driver.find_element(By.CSS_SELECTOR, "#confirmPassword").clear()
        self.driver.find_element(By.CSS_SELECTOR, "#confirmPassword").send_keys(password)

        BlogDriver.getScreenShot()

        # 3. 点击注册按钮
        self.driver.find_element(By.CSS_SELECTOR, "#submit").click()
        time.sleep(2)

        # 4. 处理弹窗:注册成功,请登录
        alert = self.driver.switch_to.alert
        alert_text = alert.text
        print(f"弹窗内容: {alert_text}")
        assert "注册成功" in alert_text, f"预期弹窗包含'注册成功',实际: {alert_text}"
        alert.accept()  # 点击确认按钮
        time.sleep(2)

        # 5. 弹窗确认后应回到登录页面
        BlogDriver.getScreenShot()
  • BlogLogin.py:访问登录页面输入合法用户名和密码,点击登录后验证是否成功跳转至博客列表页。访问登录页面输入非法用户名和密码,点击登录后验证页面是否提示"用户名或密码错误"。
python 复制代码
import time
from selenium.webdriver.common.by import By
from common.Utils import BlogDriver
from common.config import VALID_USER, INVALID_USER


class BlogLogin:
    url = ""
    driver = ""

    def __init__(self):
        self.url = "http://49.235.61.184:19090/blog_login.html"
        self.driver = BlogDriver.driver
        self.driver.get(self.url)

    def loginSuccessTest(self):
        time.sleep(2)
        self.driver.find_element(By.CSS_SELECTOR, "#username").clear()
        self.driver.find_element(By.CSS_SELECTOR, "#password").clear()
        self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys(VALID_USER["username"])
        self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys(VALID_USER["password"])
        self.driver.find_element(By.CSS_SELECTOR, "#submit").click()
        time.sleep(2)
        # 对登录结果进⾏检测, 如果跳转到了博客列表⻚才算是登录成功了
        self.driver.find_element(By.CSS_SELECTOR, "body > div.container >div.left > div > img")
        BlogDriver.getScreenShot()
        self.driver.back()


    def loginFailTest(self):
        time.sleep(2)
        self.driver.find_element(By.CSS_SELECTOR, "#username").clear()
        self.driver.find_element(By.CSS_SELECTOR, "#password").clear()
        self.driver.find_element(By.CSS_SELECTOR, "#username").send_keys(INVALID_USER["username"])
        self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys(INVALID_USER["password"])
        self.driver.find_element(By.CSS_SELECTOR, "#submit").click()
        time.sleep(2)
        # 等登录失败的结果进⾏检测, 不能仅仅通过body来校验结果,因为登录成功的结果⻚也有body元素
        expect = "⽤⼾名或密码错误!"
        actual = self.driver.find_element(By.CSS_SELECTOR, "body").text
        print(actual)
        BlogDriver.getScreenShot()
        assert expect == actual
        self.driver.back()
  • BlogList.py:访问博客列表页面,等待文章数据加载完成后,验证左侧用户面板和右侧文章列表是否存在,并点击第一篇文章的"查看全文"跳转至详情页。
python 复制代码
import time
from selenium.webdriver.common.by import By
from common.Utils import BlogDriver


class BlogList:
    url = ""
    driver = ""

    def __init__(self):
        self.driver = BlogDriver.driver
        self.url = "http://49.235.61.184:19090/blog_list.html"
        self.driver.get(self.url)

    def ListTest(self):
        # 等待 AJAX 加载文章列表
        time.sleep(3)

        # 验证左侧用户面板存在(头像图片)
        self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.left > div > h3")

        # 验证文章列表不为空
        articles = self.driver.find_elements(By.CSS_SELECTOR, "body > div.container > div.right > div.blog")
        assert len(articles) > 0, "文章列表为空"

        # 验证第一篇文章标题存在
        self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.right > div.blog:nth-child(1) > div.title")

        # 点击第一篇文章的"查看全文"
        self.driver.find_element(
            By.CSS_SELECTOR,
            "body > div.container > div.right > div.blog:nth-child(1) > a.detail"
        ).click()
        time.sleep(2)

        BlogDriver.getScreenShot()
  • BlogDetail.py:通过 URL 参数访问指定文章的详情页面,验证文章标题、发布日期和用户信息等关键元素是否正常展示。
python 复制代码
from selenium.webdriver.common.by import By
from common.Utils import BlogDriver


class BlogDetail:
    url = " "
    driver = ""
    def __init__(self):
        self.url = "http://49.235.61.184:19090/blog_detail.html?blogId=36528"
        self.driver = BlogDriver.driver
        title = self.driver.title
        # 如果还停在列表页(BlogList 未点击),才需要手动跳转到详情页
        if title == "博客列表页":
            self.driver.get(self.url)

    def DetailCheck(self):
        # body > div.container > div.right > div:nth-child(1) > a
        BlogDriver.getScreenShot()

        self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.right > div > div.title")
        self.driver.find_element(By.CSS_SELECTOR, "body > div.container >div.right > div > div.date")
        self.driver.find_element(By.CSS_SELECTOR, "body > div.container > div.left > div > h3")
  • BlogEdit.py:访问博客编辑页面,填入以时间戳命名的标题,通过 JS 向 CodeMirror 编辑器写入文章正文内容,点击发布按钮后处理发布结果弹窗。
python 复制代码
import datetime
import time
from selenium.webdriver.common.by import By
from common.Utils import BlogDriver


class BlogEdit:
    url = ""
    driver = ""

    def __init__(self):
        self.url = "http://49.235.61.184:19090/blog_edit.html"
        self.driver = BlogDriver.driver
        self.driver.get(self.url)

    def EditTest(self):
        time.sleep(2)

        # 输入标题
        title_name = datetime.datetime.now().strftime('%Y-%m-%d-%H%M%S') + "-测试"
        self.driver.find_element(By.CSS_SELECTOR, "#title").send_keys(title_name)

        # 通过 JS 设置编辑器内容(绕过复杂的 CodeMirror DOM,直接调 API)
        content = "这是一个测试"
        self.driver.execute_script(
            "document.querySelector('#editor .CodeMirror').CodeMirror.setValue(arguments[0]);",
            content
        )

        time.sleep(1)
        BlogDriver.getScreenShot()

        # 点击发布
        self.driver.find_element(By.CSS_SELECTOR, "#submit").click()
        time.sleep(2)

        # 处理发布成功弹窗
        alert = self.driver.switch_to.alert
        print(f"弹窗内容: {alert.text}")
        alert.accept()

4.4 cases目录

  • RunCases.py:作为测试入口脚本,按顺序串联执行整个测试流程:先注册新用户,再用该用户登录,接着浏览博客列表并进入文章详情,最后发布一篇新博客,完成后关闭浏览器。
python 复制代码
import sys
import os

from common.Utils import BlogDriver
from tests import BlogLogin
from tests import BlogList
from tests import BlogDetails
from tests import BlogEdit
from tests import BlogRegister


if __name__ == "__main__":
    BlogRegister.BlogRegister().RegisterSuccessTest()
    BlogLogin.BlogLogin().loginSuccessTest()
    BlogList.BlogList().ListTest()
    BlogDetails.BlogDetail().DetailCheck()
    BlogEdit.BlogEdit().EditTest()
    BlogDriver.driver.quit()

5. 性能测试

本次性能测试使用 Apache JMeter 作为压测工具。

5.1 30用户登录接口并发压测

测试脚本通过读取预先准备的 CSV 文件,利用其中的 10 组有效用户名和密码进行参数化 (避免多线程使用同一账号导致的 Session 冲突)。线程组配置为 30 个并发线程 ,模拟 30 位独立用户,在 2 秒内(Ramp-up 时间)快速启动并持续不间断地向登录接口发送请求。

  • 登录接口压测数据表现极其稳定,1210次请求的异常率严格保持为0%,未出现任何网络或服务端超时报错。
  • 响应速度极快,接口平均响应时间仅为40毫秒,中位数低至30毫秒,且90%的用户请求都能在59毫秒内获得完美响应。
  • 尽管当前12.1次/秒的吞吐量运行良好,但个别最大响应时间出现了802毫秒的瞬时波动。

5.2 30用户同步并发全链路压测

通过参数化读取 CSV 文件中的 10 组用户名与密码,规避了单一账号并发冲突问题。配置 30 个并发线程并启用同步定时器(集合点) ,模拟 30 位用户在同一瞬间同时向服务器发起冲击。测试请求串联了完整的业务链路,包含:用户登录、获取用户信息、新增博客、访问博客列表以及访问两个不同 ID 的博客详情页

  • 压测数据的异常率均严格保持在 0.00%,证明系统在面对 30 个用户的瞬间突发流量时,没有出现任何业务异常或连接超时。
  • 在响应性能方面,系统表现出极度高效的毫秒级处理能力,整体平均响应时间为 61 毫秒,即便是业务逻辑相对较重的"添加博客"接口,其 99% 线的响应时间也仅需 117 毫秒,用户体验极佳。
  • 得益于无差错和毫秒级的响应,系统在瞬间并发压力下总吞吐量达到了 362.9 次/秒。没有出现最大值接近800ms,可能是请求量较小。

5.3 40并发阶梯式负载测试

  • 测试一开始先迅速启动一批线程,然后每过 6 秒 就会新增加 10 个并发用户。
  • 每次增加这 10 个用户时,会使用 2 秒的时间让它们线性启动完毕。
  • 当并发数达到 40 之后,系统会以 40 个并发持续运行 80 秒
  • 80 秒的稳定期结束后,系统每过 2 秒,就会平稳地停止 5 个用户线程

Numbers of transaction 即事务数量,常以‌TPS ‌(每秒事务数)表示吞吐量。Response times 是响应时间。我们可以看到吞吐量响应时间大致成反比。不管是吞吐量和响应时间都是比较稳定的。

  • 从聚合报告看,系统的抗压表现非常卓越。在 4.6 万次高频请求的压力下,错误率仅为 0.05%
  • 整体平均响应时间控制在 86.78 毫秒 ,APDEX(应用性能指数)更是达到了 0.987 的极高水平(通常 0.9 即属优秀)。
  • 同时,系统的整体吞吐量达到了 385.27 次/秒,证明后端容器和数据库连接池在面对阶梯性并发冲击时,具备极强的抗压与服务能力。
  • 错误图表展示了极少数失败的请求(共 25 次)。
  • 其中 20 次报 401 未授权错误,这通常指向压测脚本中 Token 提取、缓存或参数化导致的部分线程认证失效;
  • 另外 5 次为网络连接超时,属于极个别瞬间的网络波动。这类错误未造成系统核心功能崩溃,但也提示我们在脚本中应完善异常处理和 Token 失效时的重试机制。
  • 在响应时间的线性分布和趋势图中,4.5 万次以上的请求全部归拢在 500 毫秒以内的绿色区域,呈现出极高的一致性与稳定性。
  • 尽管"添加博客"和"博客列表"等接口因涉及数据持久化操作,平均耗时略高于查询接口,但在整个阶梯爬坡的过程中,所有接口的响应时间均未出现随并发上升而激增的倾斜,系统平稳度过了压力峰值期。
  • 总的来说, 该博客系统在 40 并发阶梯压力下抗住了核心业务流的冲击,底层业务逻辑和数据库没有发生任何内部异常,系统主体性能优秀且健壮。
  • 不过 也有可能是因为 40 个并发量相对较小,服务器资源并未达到极限,使得这类网络波动显得孤立。
  • 如果有条件,可以将最大线程数增加至 100 甚至 150,再进行压测并比对。

6. 测试结果

测试类型 测试模块/场景 测试内容及核心技术 执行时间 工时预估 测试结论
手工功能测试 注册、登录、主页、编辑、详情页 全链路手工测试,验证系统核心业务逻辑及UI交互 7.25 2.5 d 全部测试完成,业务流转正常。
UI自动化测试 注册、登录、博客列表、详情、编辑 基于 Selenium + Python 搭建自动化框架,解决 CodeMirror 编辑器输入难点 7.25 1.5 d 核心业务流程跑通,具备可复用的自动化回归能力。
接口/性能测试 登录接口 30用户同步并发测试:利用同步定时器,模拟30个用户同一瞬间登录 7.26 0.5 d 成功率 100% ,平均响应仅 40ms,验证了接口的瞬时抗压能力。
接口/性能测试 核心业务链路(登录、增改查) 30用户全链路并发测试:模拟30人同步进行登录、查看、编辑等完整操作 7.26 0.5 d 异常率 0% ,总吞吐量达 362.9 次/秒,系统全链路瞬时并发表现极佳。
接口/性能测试 核心业务链路 40用户阶梯式负载测试:使用阶梯线程组逐步加压,模拟真实流量递增趋势 7.26 0.5 d 整体错误率 0.05% ,APDEX指数 0.987 。网络波动导致极个别 401 和超时,脚本容错机制可后续优化。

总结

先单点验证接口功能,再串联业务全链路。

创作充满挑战,但若我的文章能为你带来一丝启发或帮助,那便是我最大的荣幸。如果你喜欢这篇文章,请不吝点赞、评论和分享,你的支持是我继续创作的最大动力!

相关推荐
阡陌..13 小时前
Ubuntu 22.04 离线环境完全配置指南:从 GCC 到 NVIDIA 驱动再到 Samba 共享
linux·运维·ubuntu
方乐寺村14 小时前
彩笔运维勇闯机器学习--拟合
运维·人工智能·机器学习
画中有画15 小时前
pc浏览器网页如何实现自动化操作?
运维·自动化
重生的黑客16 小时前
Linux 进程程序替换与自定义 Shell:从 exec 函数族到命令行解释器
linux·运维·服务器·shell
北极糊的狐16 小时前
阿里云服务器-命令2-Linux 系统实时资源监视器 top 命令详解(进程级实时资源监控)
linux·运维·服务器
迁移科技16 小时前
周转箱拆垛码垛自动化实战:3D视觉实现 ±2mm 毫米级稳定作业
3d·自动化·视觉检测
三言老师17 小时前
clear与history历史命令管理实操
linux·运维·服务器·网络·centos
味悲17 小时前
Linux 环境下 DNS 服务器搭建
linux·运维·服务器
greenbbLV17 小时前
中小公司积分商城选型:SaaS与私有化优劣对比分析
大数据·运维·人工智能
feasibility.18 小时前
wsl安装Ubuntu方法(含网络不稳定处理)
linux·运维·windows·ubuntu