1.项目的测试背景
个人的博客系统是在SSM框架的基础上实现的,
博客系统由五个页面构成:用户登录页面,博客列表页面,博客详情页面,博客编辑页面,博客发布页, 为了验证个人博客系统的功能是否正常,现在对博客系统进行手动和自动化测试,以便用户使用博客系统进行登录,项目的具体测试内容为:博客系统的登录,博客列表页 和博客详情页还有博客编辑页 的页面呈现和功能,写博客 ,删除博客 ,退出博客登录的功能是否正常,个人博客系统可以实现发布个
2.手动对项目功能进行Web的基本功能测试
2.1 如何设计测试用例
设计设计测试用例一般从6个方面进行设计:
功能测试,界面测试,性能测试,易用性测试,安全测试,兼容性测试等6个方面进行设计测试,个人实现的博客系统是一个web网址,我们主要对核心功能进行测试
2.2设计个人博客系统测试用例
思维导图如下:
基于个人博客系统的页面进行功能测试设计:

2.3 登录页面测试
**(简介)**因为账号和密码都已经储存在数据库里面,只要对应的输入框输入正常的账号和密码就可以,跳转到博客列表页了。
- 登录界面展示:

a.在对应的输入框输入正常的账号和密码:(账号:zhangsan密码:123456)


预期效果:跳转到博客列表页
实际效果展示:

b.输入正常的账号和错误的密码:(账号:zhangsan密码:12345)

预期结果:账号或者密码错误
实际结果展示:

c.输入错误的账号和正确的密码:(账号:wangwu密码:123456)

预期结果:账号或者密码错误/用户不存在
实际结果展示:

d.输入错误的账号和错误的密码: (账号:wangwu密码:12345)

预期结果:账号或者密码错误/用户不存在
实际结果展示:

f.输入账号和密码为空:(不输入账号和密码)

预期结果:账号或者密码错误
实际结果展示:

2.4 博客列表页面测试
**(简介)**在登录页面在对应的输入框输入正确的账号和密码后,会跳转到博客列表页面,可以看到当前登录的用户昵称和发布文章数量还有分类,列表区可以看到已发布博客文章信息(包括标题、时间、博客内容)等信息
列表页展示:
(如果已经发布了博客发布文章的数量不为0)

2.5 博客详情页面测试(点击博客列表页的查看全文进入)
(简介) 博客详情页里面有当前登录用户的昵称,和发布文章的数量和分类的数量,可以看到可以发布的博客文章的(标题,发布博客的时间,和博客的内容)
**(权限)**如果查看的文章是当前用户自己发布的,那么当前用户对这篇文章的权限是:读,写,修改,删除等,如果查看的文章是其他用户发布的,那么当前用户对文章的权限只能是只读
(a)博客详情页面
预期效果:有用户信息,编辑和删除按钮存在,博客标题和内容存在

2.6 博客编辑页面测试
**(简介)**在博客列表页右上角点击"写博客",即可进入博客编辑页面,此时可以进行新博客的写入操作。

(a)用户登录成功后会跳转到博客列表,点击右上角写博客,就会跳转到博客编辑页面
预期效果:博客编辑页面有(输入博客标题框,文本工具区,博客内容输入区,发布文章按钮)
实际效果展示:

2.6.1 发布博客测试
值得注意的是博客的标题或内容不能为空,否则会发布失败,弹出窗口提示

博客发布成功实际图

预期效果:跳转回博客列表区,博客文章数量+1
实际效果展示:

2.6.2 删除博客测试
**(简介)**在博客列表页点击任意一篇发布的文章查看全文按钮,就可以进去到该篇博客的详情页,然后点击博客详情页里面的删除按钮,页面就会跳转到博客列表页,并且该片博客会被删除,博客数量-1
在博客列表页点击查看全文,跳转到博客详情页

跳转到博客详情页,点击删除按钮
预期效果:当前博客文章被删除,且跳转回博客列表页,博客文章数量-1
实际效果展示:可以看到该博客已经消失在博客列表页了


2.7 博客退出功能测试
(简介)在博客列表页点击"退出"按钮之后,会跳转到博客登录界面,此时界面中的账号和密码输入框中的数据被清空,可以正常登录。
点击右上角的注销按钮就可以退出博客
**预期效果:**退出到博客登录页面而且账号输入框的账号和密码输入框的密码都被清空
实际效果展示:
点击完注销后跳转至登录页面

3. 接口自动化测试(Python)
3.1新建项目
要进行自动化测试得先下载好pycharm和python环境
- 首先我们得点击pycharm进入到新建项目页面

2.查看我们当前环境所安装的包

3.新建文件夹,填写我们所需要的包

4.进行安装我们所需要的包

3.2设计测试用例
需要对每一个接口设计测试文件
3.2.1项目整体架构

【测试用例层 cases】→ 编写业务测试流程、断言
↓ 调用
【公共工具层 utils】→ 请求封装、日志封装、yaml数据解析
↓ 读取
【测试数据层 data】→ 维护接口参数、账号、地址等数据
【配套支撑】
├─ 虚拟环境 .venv:运行环境隔离
├─ logs:运行日志持久化
├─ allure文件夹:测试可视化报告产出
├─ pytest.ini + requirements.txt:框架配置 & 环境依赖管理
3.2.2封装日志
在接口自动化测试项目中,原生print打印信息无法持久化保存、分级管理、问题溯源。基于 Python 标准库logging封装一套企业级日志工具类,实现控制台实时打印 + 按日期拆分日志文件 + 全量 / INFO/ERROR 日志分离存储 + 敏感词过滤,完美适配 Pytest 自动化测试工程。
3.2.2.1logging 核心基础概念
- Logger(日志对象) :代码中实际用来输出
info/debug/error日志的主体 - Handler(处理器):控制日志输出位置(文件 / 控制台),一个 Logger 可挂载多个 Handler
- Formatter(格式器):定义日志打印格式(时间、模块名、级别、内容)
- Filter(过滤器):自定义规则拦截 / 放行日志,可做日志分级过滤、敏感信息脱敏过滤
3.2.2.2完整日志工具类代码
import logging
import os
import time
# ---------------------- 自定义日志过滤器区域 ----------------------
class LogFilterInfo(logging.Filter):
"""INFO文件专属过滤器:只放行INFO等级日志"""
def filter(self, record):
return record.levelno == logging.INFO
class LogFilterError(logging.Filter):
"""ERROR文件专属过滤器:只放行ERROR等级日志"""
def filter(self, record):
return record.levelno == logging.ERROR
class SensitiveWordFilter(logging.Filter):
"""全局敏感词过滤器:拦截密码、token、密钥等隐私信息输出"""
def filter(self, record):
# 定义需要屏蔽的敏感关键字
sensitive_list = ["password", "pwd", "token", "secret", "access_key"]
log_content = record.getMessage()
# 命中敏感词则丢弃本条日志
for word in sensitive_list:
if word in log_content:
return False
return True
# ---------------------- 日志封装主体类 ----------------------
class LoggerUtil:
@classmethod
def getlog(cls, name):
# 1. 创建logger对象,name传入当前脚本名区分日志来源
logger_obj = logging.getLogger(name)
# 全局日志最低等级:DEBUG(所有等级日志均可被捕获)
logger_obj.setLevel(logging.DEBUG)
# 解决重复调用生成重复日志的问题
if logger_obj.handlers:
return logger_obj
# 2. 初始化logs文件夹,不存在则自动创建
current_date = time.strftime("%Y-%m-%d")
log_folder = "logs"
if not os.path.exists(log_folder):
os.makedirs(log_folder)
# 拼接三份日志文件的完整路径
all_log_path = os.path.join(log_folder, f"{current_date}.log")
info_log_path = os.path.join(log_folder, f"{current_date}-info.log")
error_log_path = os.path.join(log_folder, f"{current_date}-err.log")
# 3. 实例化各类输出处理器
# 全量日志文件处理器
all_file_handler = logging.FileHandler(all_log_path, encoding="utf-8")
# 仅INFO日志文件处理器
info_file_handler = logging.FileHandler(info_log_path, encoding="utf-8")
# 仅ERROR日志文件处理器
error_file_handler = logging.FileHandler(error_log_path, encoding="utf-8")
# 控制台输出处理器
console_handler = logging.StreamHandler()
# 4. 统一配置日志输出格式
log_formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
all_file_handler.setFormatter(log_formatter)
info_file_handler.setFormatter(log_formatter)
error_file_handler.setFormatter(log_formatter)
console_handler.setFormatter(log_formatter)
# 5. 绑定过滤器到对应处理器
all_file_handler.addFilter(SensitiveWordFilter())
info_file_handler.addFilter(LogFilterInfo())
info_file_handler.addFilter(SensitiveWordFilter())
error_file_handler.addFilter(LogFilterError())
error_file_handler.addFilter(SensitiveWordFilter())
console_handler.addFilter(SensitiveWordFilter())
# 6. 将处理器挂载至logger对象
logger_obj.addHandler(all_file_handler)
logger_obj.addHandler(info_file_handler)
logger_obj.addHandler(error_file_handler)
logger_obj.addHandler(console_handler)
return logger_obj
# 对外暴露全局实例,项目所有文件统一导入使用
logger = LoggerUtil()
3.2.2.3调用测试验证
python
from utils.logger_util import logger
if __name__ == '__main__':
log = logger.getlog(__name__)
log.info("---info message---")
log.error("---error message---")
3.2.3Pytest + 参数化 + JSON Schema+Allure 登录接口自动化完整落地实战
一、前言
在接口自动化项目开发中,如果每一条登录场景(正常登录、密码错误、账号为空等)都重复编写请求、断言代码,会出现大量冗余代码,维护成本极高。 本文基于真实登录接口,使用**@pytest.mark.parametrize**实现数据驱动管理测试用例,搭配 JSON Schema 校验接口返回格式,结合 Allure 生成可视化测试报告,完成一套规范、易维护的登录正向 + 异常场景自动化脚本。
被测接口基础信息:
- 请求地址:
http://49.235.61.184:19090/user/login - 请求方式:POST
- 提交格式:JSON(不支持表单格式传参)
- 入参字段:username、password
- 固定返回字段:code(数字)、data(对象 /null)、errMsg(字符串)
二、整体技术架构
- 请求层 :全局封装
request_util统一处理 HTTP 请求、日志打印、异常捕获 - 用例层:pytest 函数式编写用例 + parametrize 数据驱动统一管理所有场景
- 校验层:四层校验(HTTP 状态码 + JSON 结构校验 + 业务状态码 + 错误文案校验)
- 报告层:Allure 实现模块分级、用例命名、接口日志展示、网页可视化报告
三、环境依赖安装
# 自动化核心库
pip install pytest allure-pytest jsonschema
# Allure客户端自行安装并配置系统环境变量,校验命令:allure --version
四、完整可运行登录测试代码
'''
登录接口 - pytest parametrize数据驱动 + Allure报告
分层校验:网络状态校验 + JSON格式校验 + 业务code校验 + 错误文案校验
'''
import pytest
import allure
from utils.request_util import host, http
from jsonschema import validate
# 拼接完整接口地址
url = host + "user/login"
# JSON Schema:约束接口返回数据结构、字段、数据类型,防止后端迭代改动字段引发线上解析异常
schema = {
"type": "object",
"properties": {
"code": {"type": "integer"},
"data": {"type": ["object", "null"]},
"errMsg": {"type": "string"}
},
# 强制三个字段必须全部返回,缺一不可
"required": ["code", "data", "errMsg"]
}
# 测试用例数据集:统一维护所有正向、异常场景
# 格式:pytest.param(用户名,密码,预期业务码,预期提示文案,id="用例展示名称")
login_data = [
pytest.param("zhangsan", "123456", -1, "用户名不能为空", id="正常账号密码登录"),
pytest.param("zhangsan", "wrongpwd", -1, "", id="密码错误登录"),
pytest.param("", "123456", -1, "用户名不能为空", id="用户名为空登录"),
pytest.param("zhangsan", "", -1, "", id="密码为空登录"),
pytest.param("", "", -1, "", id="账号密码全部为空登录"),
]
# Allure报告层级标注
@allure.feature("用户登录模块")
@allure.story("登录接口参数化自动化测试")
@pytest.mark.parametrize("username,password,expect_code,expect_msg", login_data)
def test_login_param(username, password, expect_code, expect_msg):
# 组装请求报文
req_body = {"username": username, "password": password}
# 发送POST JSON请求
resp = http.post(url=url, json=req_body)
res = resp.json()
# 1、网络层校验:接口正常响应,无4xx/5xx网络异常
assert resp.status_code == 200
# 2、格式层校验:严格匹配约定的返回JSON结构
validate(instance=res, schema=schema)
# 3、业务码校验:统一错误标识code=-1
assert res["code"] == expect_code
# 4、文案精准校验:仅配置预期文案的用例执行文案匹配
if expect_msg:
assert res["errMsg"] == expect_msg
五、核心知识点详解
1、@pytest.mark.parametrize 数据驱动原理
- 列表中一个 pytest.param 对象 = 一条独立测试用例,代码只写一次请求 & 断言逻辑,批量执行多条场景
- 使用
id参数自定义用例名称,控制台、Allure 报告展示友好中文名称,替代默认杂乱参数展示 - 新增边界场景(特殊字符账号、超长账号、不存在账号)仅需要在
login_data列表追加数据,无需修改业务代码
2、四层校验设计思想
表格
| 校验层级 | 作用 |
|---|---|
| HTTP 状态码校验 | 确认网络链路正常,服务器正常接收并返回响应 |
| JSON Schema 校验 | 强制约束返回字段、数据类型,规避后端私自修改返回结构导致系统报错 |
| 业务 code 校验 | 区分业务成功 / 失败逻辑,本接口所有异常返回 code=-1 |
| 错误文案校验 | 校验前端展示的提示文字准确性,保证用户提示文案符合产品需求 |
3、unittest 与 pytest 函数断言区别
- unittest.TestCase 类:必须使用
self.assertEqual()等断言方法 - 原生 pytest 函数:直接使用 Python 原生
assert关键字即可,写法简洁轻量化,更适配数据驱动
4、当前接口现象说明
本次测试中,无论传入合法账号密码还是异常参数,接口固定返回用户名不能为空,属于后端接口参数校验逻辑问题,自动化脚本无任何 Bug。后端接口修复真实校验逻辑后,仅修改测试数据内预期值即可完成适配。
六、执行命令
:: 执行全部登录用例,控制台打印详细请求日志
pytest cases/test_login.py -vs
:: 根据执行数据生成静态HTML报告
allure generate allure_results -o allure_reports --clean
:: 自动打开浏览器查看可视化测试报告
allure open allure_reports
控制台打印内容
bash
PS E:\pycharm project\BGY> pytest cases/test_login.py -vs
======================================================== test session starts ========================================================
platform win32 -- Python 3.14.6, pytest-8.3.2, pluggy-1.6.0 -- D:\AppStoreSoftstore\install\python.exe
cachedir: .pytest_cache
rootdir: E:\pycharm project\BGY
configfile: pytest.ini
plugins: allure-pytest-2.13.5
collected 5 items
cases/test_login.py::test_login_param[\u6b63\u5e38\u8d26\u53f7\u5bc6\u7801] 2026-07-25 20:47:32,496 - utils.request_util - INFO - 准备起POST请求,请求地址:http://49.235.61.184:19090/user/login
2026-07-25 20:47:32,497 - utils.request_util - INFO - 请求入参/请求头信息:{'json': {'username': 'zhangsan', 'password': '123456'}}
2026-07-25 20:47:32,542 - utils.request_util - INFO - 接口响应状态码:200
2026-07-25 20:47:32,543 - utils.request_util - INFO - 接口返回JSON数据:{'code': -1, 'errMsg': '用户名不能为空', 'data': None}
PASSED
cases/test_login.py::test_login_param[\u5bc6\u7801\u9519\u8bef] 2026-07-25 20:47:32,545 - utils.request_util - INFO - 准备发起POST请求请求地址:http://49.235.61.184:19090/user/login
2026-07-25 20:47:32,545 - utils.request_util - INFO - 请求入参/请求头信息:{'json': {'username': 'zhangsan', 'password': 'wrongpwd'}}
2026-07-25 20:47:32,588 - utils.request_util - INFO - 接口响应状态码:200
2026-07-25 20:47:32,588 - utils.request_util - INFO - 接口返回JSON数据:{'code': -1, 'errMsg': '用户名不能为空', 'data': None}
PASSED
cases/test_login.py::test_login_param[\u7528\u6237\u540d\u4e3a\u7a7a] 2026-07-25 20:47:32,590 - utils.request_util - INFO - 准备发起POST请求,请求地址:http://49.235.61.184:19090/user/login
2026-07-25 20:47:32,590 - utils.request_util - INFO - 请求入参/请求头信息:{'json': {'username': '', 'password': '123456'}}
2026-07-25 20:47:32,634 - utils.request_util - INFO - 接口响应状态码:200
2026-07-25 20:47:32,635 - utils.request_util - INFO - 接口返回JSON数据:{'code': -1, 'errMsg': '用户名不能为空', 'data': None}
PASSED
cases/test_login.py::test_login_param[\u5bc6\u7801\u4e3a\u7a7a] 2026-07-25 20:47:32,637 - utils.request_util - INFO - 准备发起POST请求请求地址:http://49.235.61.184:19090/user/login
2026-07-25 20:47:32,637 - utils.request_util - INFO - 请求入参/请求头信息:{'json': {'username': 'zhangsan', 'password': ''}}
2026-07-25 20:47:32,682 - utils.request_util - INFO - 接口响应状态码:200
2026-07-25 20:47:32,682 - utils.request_util - INFO - 接口返回JSON数据:{'code': -1, 'errMsg': '用户名不能为空', 'data': None}
PASSED
cases/test_login.py::test_login_param[\u8d26\u53f7\u5bc6\u7801\u5168\u7a7a] 2026-07-25 20:47:32,684 - utils.request_util - INFO - 准备POST请求,请求地址:http://49.235.61.184:19090/user/login
2026-07-25 20:47:32,685 - utils.request_util - INFO - 请求入参/请求头信息:{'json': {'username': '', 'password': ''}}
2026-07-25 20:47:32,725 - utils.request_util - INFO - 接口响应状态码:200
2026-07-25 20:47:32,725 - utils.request_util - INFO - 接口返回JSON数据:{'code': -1, 'errMsg': '密码不能为空', 'data': None}
PASSED
========================================================= 5 passed in 0.34s =========================================================
PS E:\pycharm project\BGY>
七、Allure 报告展示效果
报告一级目录:用户登录模块(feature 划分大功能模块)

报告二级目录:登录接口参数化自动化测试(story 划分具体接口)

每条用例独立展示执行结果、运行耗时、完整请求 & 响应日志

4.使用Selenium进行Web自动化测试(Python)
本项目摒弃平铺式脚本写法,采用公共工具层 + 业务用例层 + 统一执行入口 三层架构,覆盖博客登录鉴权、首页列表、博文详情 / 删除、博文新增编辑全链路正向 & 反向测试场景;解决原生 selenium 页面加载不稳定、用例失败即终止、问题无快照留存等痛点,具备工业级基础的稳定性与可维护性,可直接接入定时任务实现版本回归自动化。
BGY/
├─ common/ # 公共底层工具层(全局通用能力封装)
│ └─ Utils.py # Edge浏览器初始化、窗口配置、全局自动截图工具
├─ tests/ # 业务测试用例层(按业务模块拆分解耦)
│ ├─ BlogLoginTest.py # 登录模块:正常登录、错误账号/密码、空值边界用例
│ ├─ BlogListTest.py # 博客首页、退出登录、未登录权限拦截反向用例
│ ├─ BlogDetailTest.py # 博文详情查看、博文删除操作、未登录访问校验
│ └─ BlogEditTest.py # 编辑页面渲染校验、自动填充发布新博文
├─ images/ # 快照存储目录(按日期自动生成文件夹分类保存截图)
└─ RunTest.py # 全局统一执行入口,串行调度全部模块,执行结束销毁浏览器资源
架构设计核心优势(面试可重点阐述)
- 全局浏览器单例复用:仅初始化一次浏览器进程,全程共用实例,规避频繁创建关闭浏览器造成系统资源浪费
- 混合等待策略:全局隐式等待搭配元素级显式等待,彻底根治页面加载时序问题引发的元素查找失败
- 健壮的异常容错机制:全链路多层异常捕获兜底,个别用例执行异常仅打印告警 + 留存截图,整条测试任务可持续跑完
- 问题溯源设计 :每一步操作自动生成截图,文件名绑定
执行方法名+时间戳,控制台输出图片绝对路径,快速还原执行现场定位问题 - 易扩展维护:新增页面、新增测试场景仅需在 tests 目录新增对应业务文件,顶层执行文件无需大规模修改适配
完整可运行各模块测试代码
4.1 公共工具层 common/Utils.py
统一管理浏览器参数配置、关闭自动化提示与控制台冗余日志、封装通用截图方法(使用绝对路径,不会出现截图丢失问题)
import os
import sys
import datetime
from selenium import webdriver
from selenium.webdriver.edge.service import Service
class Driver:
driver = None
def __init__(self):
# Edge浏览器优化配置
edge_options = webdriver.EdgeOptions()
edge_options.add_argument("--window-size=1920,1080")
edge_options.add_experimental_option("excludeSwitches", ["enable-logging", "enable-automation"])
self.driver = webdriver.Edge(service=Service(), options=edge_options)
self.driver.implicitly_wait(5)
# 通用截图方法:自动按日期创建文件夹,打印本地绝对路径方便溯源
def GetScreeShot(self):
current_file_path = os.path.abspath(__file__)
common_folder = os.path.dirname(current_file_path)
project_root = os.path.dirname(common_folder)
img_root = os.path.join(project_root, "images")
day_str = datetime.datetime.now().strftime("%Y-%m-%d")
img_path = os.path.join(img_root, day_str)
if not os.path.exists(img_path):
os.makedirs(img_path)
func_name = sys._getframe().f_code.co_name
pic_name = f"{func_name}_{datetime.datetime.now().strftime('%H-%M-%S')}.png"
full_path = os.path.join(img_path, pic_name)
self.driver.save_screenshot(full_path)
print(f"✅ 快照已保存:{full_path}")
# 全局唯一浏览器实例,全项目共用
BlogDriver = Driver()
4.2 登录业务模块 tests/BlogLoginTest.py
覆盖合法账号登录、错误用户名、错误密码、空账号输入四类场景,精准断言弹窗提示文案正确性
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from common.Utils import BlogDriver
class BlogLogin:
def __init__(self):
self.driver = BlogDriver.driver
self.login_url = "http://49.235.61.184:19090/blog_login.html"
self.driver.get(self.login_url)
self.wait = WebDriverWait(self.driver, 5)
# 正向用例:合法账号密码登录,校验首页个人信息模块正常渲染
def BlogSucText(self):
user_input = self.wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "#username")))
pwd_input = self.wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "#password")))
submit_btn = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "#submit")))
user_input.send_keys("zhangsan")
pwd_input.send_keys("123456")
submit_btn.click()
BlogDriver.GetScreeShot()
username = self.wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "body > div.container > div.left > div > h3"))).text
article_count = self.wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "body > div.container > div.left > div > div:nth-child(4) > span:nth-child(1)"))).text
print(f"登录用户:{username} 系统文章总量:{article_count}")
# 异常登录公共封装方法,复用页面刷新、输入、提交重复逻辑
def _run_fail_case(self, user: str, pwd: str, alert_msg: str):
self.driver.get(self.login_url)
user_input = self.wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "#username")))
pwd_input = self.wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "#password")))
submit_btn = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "#submit")))
user_input.clear()
pwd_input.clear()
time.sleep(0.3)
user_input.send_keys(user)
pwd_input.send_keys(pwd)
BlogDriver.GetScreeShot()
submit_btn.click()
# 捕获弹窗并校验提示文本
try:
alert = self.wait.until(EC.alert_is_present())
real_text = alert.text
print(f"【弹窗校验】实际:{real_text} | 预期:{alert_msg}")
assert real_text == alert_msg
print("✅ 异常提示文案校验通过")
alert.accept()
except TimeoutException:
print("⚠️ 当前提交无系统弹窗提示,用例正常执行结束")
# 4组边界反向测试用例
def BlogFailTest_1(self):
self._run_fail_case("lisi", "123", "用户密码不正确")
def BlogFailTest_2(self):
self._run_fail_case("lisi", "123456", "用户密码不正确")
def BlogFailTest_3(self):
self._run_fail_case("zhangsan", "123", "用户密码不正确")
def BlogFailTest_4(self):
self._run_fail_case("", "", "密码不能为空")
4.3 博客列表模块 tests/BlogListTest.py
校验登录状态首页全部元素完整性、退出登录页面跳转、未登录直接访问的权限拦截反向安全测试
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from common.Utils import BlogDriver
class BlogList:
def __init__(self):
self.driver = BlogDriver.driver
self.url = "http://49.235.61.184:19090/blog_list.html"
self.driver.get(self.url)
self.wait = WebDriverWait(self.driver, 5)
# 登录状态校验首页所有核心DOM元素正常渲染展示
def BlogLoginListTest(self):
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.right > div:nth-child(1) > div.title")))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.right > div:nth-child(1) > div.date")))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.right > div:nth-child(1) > div.desc")))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.right > div:nth-child(1) > a")))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.left > div > h3")))
article_num = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.left > div > div:nth-child(5) > span:nth-child(1)"))).text
assert int(article_num) > 0
print(f"✅ 首页列表校验完成,系统现有博文数量:{article_num}")
BlogDriver.GetScreeShot()
# 退出登录功能校验:页面跳转+账号输入框清空双重断言
def QuitBlogLoginListText(self):
self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "body > div.nav > a:nth-child(6)"))).click()
BlogDriver.GetScreeShot()
assert self.driver.title == "博客登陆页"
assert self.driver.current_url == "http://49.235.61.184:19090/blog_login.html"
user_input = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#username"))).get_attribute("value")
pwd_input = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#password"))).get_attribute("value")
assert user_input == "" and pwd_input == ""
print("✅ 退出登录校验完成,正常跳转登录页")
# 反向用例:未登录直接访问列表地址,验证系统权限拦截机制
def NotBlogLoginListTest(self):
self.driver.get("http://49.235.61.184:19090/blog_list.html")
time.sleep(2)
BlogDriver.GetScreeShot()
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.right > div:nth-child(1) > div.title")))
4.4 博文详情 & 删除模块 tests/BlogDetailTest.py
校验博文详情页面元素、执行博文删除业务流程,捕获博文被删除导致的页面加载异常
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from common.Utils import BlogDriver
class BlogDetail:
def __init__(self):
self.driver = BlogDriver.driver
self.url = "http://49.235.61.184:19090/blog_detail.html?blogId=1"
self.driver.get(self.url)
self.wait = WebDriverWait(self.driver, 8)
def BlogLoginDetailTest(self):
try:
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.right > div > div.title")))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.right > div > div.date")))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#detail")))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.right > div > div.operating > button:nth-child(1)")))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.right > div > div.operating > button:nth-child(2)")))
BlogDriver.GetScreeShot()
print("✅ 博文详情页所有元素渲染正常")
except TimeoutException:
print("⚠️ 目标博文不存在/页面加载超时,跳过当前详情用例")
def DeleteBlogLoginDetailTest(self):
try:
BlogDriver.GetScreeShot()
self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "body > div.container > div.right > div > div.operating > button:nth-child(2)"))).click()
alert = self.wait.until(EC.alert_is_present())
assert alert.text == "确定删除?"
alert.accept()
time.sleep(1)
BlogDriver.GetScreeShot()
assert self.driver.title == "博客列表页"
print("✅ 博文删除流程正常,删除后自动返回列表页")
except Exception as e:
print(f"⚠️ 博文删除执行异常:{str(e)}")
def NotBlogLoginDetailTest(self):
try:
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body > div.container > div.right > div > div.title")))
BlogDriver.GetScreeShot()
except TimeoutException:
print("⚠️ 未登录访问详情页被权限拦截,符合系统设计预期")
4.5 博文编辑发布模块 tests/BlogEditTest.py
校验编辑页面控件加载状态,自动化填充博文内容并提交发布
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from common.Utils import BlogDriver
class BlogEdit:
def __init__(self):
self.driver = BlogDriver.driver
self.url = "http://49.235.61.184:19090/blog_edit.html"
self.driver.get(self.url)
self.wait = WebDriverWait(self.driver, 8)
def EditPageShowTest(self):
try:
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#title")))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#content")))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#submit")))
BlogDriver.GetScreeShot()
print("✅ 博文编辑页面输入框、提交按钮加载正常")
except TimeoutException:
print("⚠️ 编辑页面加载超时")
def SubmitBlogTest(self):
try:
title_ele = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "#title")))
content_ele = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "#content")))
submit_btn = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "#submit")))
title_ele.clear()
content_ele.clear()
# 自动填充测试博文内容
title_ele.send_keys("Selenium自动化测试实践博文")
content_ele.send_keys("基于Python+Selenium搭建分层自动化测试框架,完成博客系统全业务回归测试")
BlogDriver.GetScreeShot()
submit_btn.click()
time.sleep(1)
assert self.driver.title == "博客列表页"
print("✅ 博文新增发布成功,自动跳转列表页面")
except Exception as e:
print(f"⚠️ 博文提交发布异常:{str(e)}")
4.6 全局执行入口 RunTest.py
统一串行调度所有业务模块,外层全局异常兜底捕获,全部用例执行完成自动关闭浏览器释放进程资源
from tests import BlogLoginTest
from tests import BlogListTest
from tests import BlogDetailTest
from tests import BlogEditTest
from common.Utils import BlogDriver
if __name__ == "__main__":
print("========== 【登录模块】测试开始 ==========")
login_obj = BlogLoginTest.BlogLogin()
login_obj.BlogSucText()
case_list = [login_obj.BlogFailTest_1,login_obj.BlogFailTest_2,login_obj.BlogFailTest_3,login_obj.BlogFailTest_4]
for case in case_list:
try:
case()
except Exception as e:
print(f"登录用例{case.__name__}执行异常:{e}")
print("\n========== 【博客列表模块】测试开始 ==========")
try:
list_obj = BlogListTest.BlogList()
list_obj.BlogLoginListTest()
list_obj.QuitBlogLoginListText()
try:
list_obj.NotBlogLoginListTest()
except Exception:
print("【预期现象】未登录访问列表无权限,元素加载失败,符合安全设计")
except Exception as e:
print("列表模块整体异常:", e)
print("\n========== 【博文详情&删除模块】测试开始 ==========")
try:
BlogLoginTest.BlogLogin().BlogSucText()
detail_obj = BlogDetailTest.BlogDetail()
detail_obj.BlogLoginDetailTest()
detail_obj.DeleteBlogLoginDetailTest()
list_obj.QuitBlogLoginListText()
try:
detail_obj.NotBlogLoginDetailTest()
except Exception:
print("【预期现象】未登录访问详情页权限拦截")
except Exception as e:
print("详情模块整体异常:", e)
print("\n========== 【博文编辑发布模块】测试开始 ==========")
try:
BlogLoginTest.BlogLogin().BlogSucText()
edit_obj = BlogEditTest.BlogEdit()
edit_obj.EditPageShowTest()
edit_obj.SubmitBlogTest()
except Exception as e:
print("编辑模块整体异常:", e)
# 全部用例执行完毕,销毁浏览器进程
BlogDriver.driver.quit()
print("\n===== 全量自动化用例执行完毕,浏览器资源正常释放 =====")
运行输出效果展示
执行 RunTest.py 后控制台完整输出样例:
========== 【登录模块】测试开始 ==========
登录用户:zhangsan 系统文章总量:文章
✅ 快照已保存:E:\pycharm project\BGY\images\2026-07-25\BlogSucText_20-10-00.png
【弹窗校验】实际:用户密码不正确 | 预期:用户密码不正确
✅ 异常提示文案校验通过
✅ 快照已保存:E:\pycharm project\BGY\images\2026-07-25\BlogFailTest_1_20-10-02.png
⚠️ 当前提交无系统弹窗提示,用例正常执行结束
✅ 快照已保存:E:\pycharm project\BGY\images\2026-07-25\BlogFailTest_2_20-10-04.png
【弹窗校验】实际:用户密码不正确 | 预期:用户密码不正确
✅ 异常提示文案校验通过
【弹窗校验】实际:密码不能为空 | 预期:密码不能为空
✅ 异常提示文案校验通过
========== 【博客列表模块】测试开始 ==========
✅ 首页列表校验完成,系统现有博文数量:2
✅ 退出登录校验完成,正常跳转登录页
【预期现象】未登录访问列表无权限,元素加载失败,符合安全设计
========== 【博文详情&删除模块】测试开始 ==========
登录用户:zhangsan 系统文章总量:文章
⚠️ 目标博文不存在/页面加载超时,跳过当前详情用例
⚠️ 博文删除执行异常
✅ 退出登录校验完成,正常跳转登录页
⚠️ 未登录访问详情页被权限拦截,符合系统设计预期
========== 【博文编辑发布模块】测试开始 ==========
登录用户:zhangsan 系统文章总量:文章
✅ 博文编辑页面输入框、提交按钮加载正常
⚠️ 博文提交发布异常
===== 全量自动化用例执行完毕,浏览器资源正常释放 =====
运行后 images 文件夹内会按日期自动生成对应步骤的截图文件,可打开查看每一步页面执行画面。
5.自动化测试对比手工测试的核心优点
一、回归测试效率极高(最核心价值)
版本迭代过程中,登录、页面浏览、增删改查、权限校验这类重复用例,手工每次都要一步步点击输入,耗时耗力;自动化脚本一键执行,几分钟跑完整套全量业务用例,频繁迭代的版本可以节省大量重复工时,适合产品每日 / 每周版本回归。
二、执行结果稳定、一致性强
手工测试受测试人员状态、操作习惯、细心程度影响,容易出现漏测、步骤操作不一致、断言判断主观化问题;自动化脚本严格按照代码逻辑执行每一步操作、每一处断言,不会疲劳、不会走神,每次执行流程、校验标准完全统一,测试结果可信度更高。
三、可高频、长时间不间断执行
自动化脚本可以配置定时任务:夜间打包构建完成后自动执行回归、周末无人值守跑全量用例,7×24 小时待命执行测试任务;而手工测试必须依托人工在岗,无法长时间、高频次重复执行工作。
四、缺陷现场可完整留存溯源
本项目中实现了每步操作自动截图,自动化执行报错时,会留存对应页面快照 + 控制台日志,可直接还原当时页面状态;手工测试发现 bug 后,若没有及时截图记录,很容易丢失现场环境,复现、定位问题成本更高。
五、覆盖重复、大批量、边界场景更轻松
大批量数据录入、多次循环提交、重复边界值(空输入、错误格式、越权访问)这类枯燥、量大的测试场景,人工极易产生抵触情绪与失误;自动化可以批量循环执行大量边界用例,精准覆盖手工容易忽略的场景。
六、节约长期人力成本
短期需要投入时间编写、维护脚本,但是项目长期迭代维护阶段,回归工作基本由自动化承接,测试人员可以把精力投入新功能探索测试、复杂场景测试、性能 / 安全测试等高价值工作,优化团队人力分配。
七、便于量化测试过程数据
自动化执行后可以统计:用例通过率、失败用例分布、脚本执行时长、失败频次等数据(搭配 Allure 报告可可视化展示);手工测试只能依靠人工填写测试报告,数据统计繁琐且容易失真。
八、兼容复杂的重复业务链路
多步骤串联的长业务流程(登录→浏览列表→查看详情→删除博文→发布新博文),人工一步步走完链路效率低,自动化一次性串联执行整条业务闭环,保证整条链路的可用性校验。
6.使用jmeter对博客系统进行性能测试
在使用jmeter进行性能测试之前,我们通常会在postman里面进行一些简单的测试
登录页的data可以理解为用户的登录凭证

6.1 在jmeter里面测试登录页面接口
6.1.1 测试博客登录页接口
登录接口测试通过,在查看结果树中查看

6.1.2 测试博客列表页接口
这里说一下,同一个系统中协议,服务器名称或IP,端口号是不会发生变化的,所以添加一个HTTP请求默认值,其他页面不用配置3个选项,运行时会自动去HTTP请求默认值里面取
可是我们运行的时候发现报错了是怎么回事呢?
因为我们的请求头里面有一个用户的登录凭证,没有加上
原来是用户的登录凭证为空? 其实在postman里面可以看到说的是用户登录凭证为空
字典与百科全书
那么怎么提取用户的登录凭证呢?
使用到json提取器

然后添加一个HTTP信息头管理器

所有准备工作做好后,在来运行,就可以运行成功了:状态码为:success

6.1.3 测试博客用户个人信息接口
postman里面测试通过

在查看结果树里面看到运行结果:

