目录
[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 自动化 实战技能深度融合,特意选取了一款开源博客系统作为测试目标。项目初期,首先完成了详尽的测试用例设计与手工执行,以确保系统核心业务逻辑的完整性;在此基础上,进一步搭建了基于 Selenium 与 Python 的自动化测试框架,最终实现了对博客系统核心业务全流程的自动化回归,有效巩固了自身的测试工具链应用能力
1. 项目简介
本次测试对象是一款采用 Java 后端与 JavaScript 前端构建的开源博客系统。系统核心操作界面共包含五个功能模块:注册页、登录页、博客列表页、博客详情页以及博客编辑页。
1.1 注册界面:
- 用户需填写系统中不存在的用户名、两次确认密码(需保持一致),并可选择填写个人 GitHub 主页。
- 提交注册信息后,浏览器会弹出成功提示弹窗。
- 点击确认按钮后自动跳转至登录页面。
1.2 登陆界面:
- 用户输入对应的账号的用户名与密码。
- 若校验通过,系统会跳转至博客列表页;
- 若账号不存在或密码错误,页面则会弹出相应的错误提示弹窗。
1.3 博客列表页:
- 支持页面上下滑动浏览,页面默认按时间倒序展示最新发布的五篇博客,底部提供分页功能。
- 点击某篇博客下方的"查看全文"按钮,即可跳转至该篇博客的详情页面。
1.4 博客详情页:
- 页面清晰展示了博客的完整内容以及作者的个人信息。
- 若当前登录用户即是该文章的作者,页面右上角会额外显示"编辑"与"删除"功能按钮。
- 点击"编辑"将跳转至博客编辑页;
- 点击"删除"则会弹出二次确认弹窗,确认删除后将返回博客列表页。
1.5 博客编辑页:
- 在此页面,用户可以对已有博客的标题与正文内容进行修改。
- 编辑完成后,点击"发布文章"按钮,系统会弹出发布确认弹窗;
- 点击弹窗中的确认按钮,即可保存新内容并自动返回至博客列表页。
1.6 博客系统信息:
- 博客系统入口链接:http://49.235.61.184:19090/blog_login.html
- 自动化测试项目源码:https://gitee.com/Greg_0518/auto-test
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 和超时,脚本容错机制可后续优化。 |
总结
先单点验证接口功能,再串联业务全链路。
创作充满挑战,但若我的文章能为你带来一丝启发或帮助,那便是我最大的荣幸。如果你喜欢这篇文章,请不吝点赞、评论和分享,你的支持是我继续创作的最大动力!
