博客接口自动化测试--实战
一、项目介绍
实战前期准备
1、项目概述
该博客是一款基于 Spring Boot 框架开发的轻量级系统,采用分层架构设计,实现了用户登录、博客发布 / 修改 / 删除 / 查询、用户信息获取等核心功能,具备完善的请求校验、异常处理、统一响应封装及登录拦截能力。
2、技术栈
核心框架与组件
- 后端框架:Spring Boot
- 数据持久层:MyBatis-Plus
- 数据校验:Jakarta Validatuion(请求参数合法性校验)
- 令牌认证:JWT(用户登录态校验,基于Token机制)
- 工具类:Lombok(简化实体类代码),BeanUtils(对象属性拷贝)
- 异常处理:全局异常拦截,自定义异常体系
3、核心功能模块
用户模块
- 用户登录:校验用户名、密码,验证通过后生成 JWT Token 返回给前端
- 用户信息查询:根据用户ID查询用户基本信息
- 作者信息查询:根据博客ID关联查询该博客的作者信息
博客模块
- 博客列表查询:查询未删除的博客列表,按更新时间倒叙排列
- 博客详情查询:根据博客ID查询单篇博客详情
- 博客发布:校验标题、内容、作者信息,新增博客数据
- 博客修改:根据博客ID更新标题、内容
- 博客删除:逻辑删除(更新删除标识为1),非物理删除
二、接口信息
覆盖系统中 8 个核心接口,重点验证登录态控制、参数合法性、请求方法合规性、异常场景处理四大维度,确保接口功能符合预期。
| 接口名称 | 接口路径 | 请求方法(预期) | 核心测试维度 |
|---|---|---|---|
| 登录接口 | /user/login | POST | 账号密码合法性、登录成功 / 失败场景、请求方法校验 |
| 博客列表接口 | /blog/getList | GET | 登录态拦截、请求方法校验 |
| 博客详情接口 | /blog/getBlogDetail?blogId= | GET | 登录态拦截、blogId 参数合法性(空 / 不存在 / 非数字 / 过大 / 负数等)、请求方法校验 |
| 作者信息接口 | /user/getAuthorInfo?blogId= | GET | 登录态拦截、blogId 参数合法性(同详情接口)、请求方法校验 |
| 博客新增接口 | /blog/add | POST | 登录态拦截、标题 / 内容合法性(空 / 过长 / 特殊字符 / 图片 / 链接)、请求方法校验 |
| 用户信息接口 | /user/getUserInfo | GET | 登录态拦截、请求方法校验 |
| 修改博客接口 | /blog/update | POST | 登录态拦截、请求方法校验 |
| 删除博客接口 | /blog/delete | POST | 登录态拦截、请求方法校验 |
三、设计测试用例
测试用例脑图








接口一:用户登录
请求接口:/user/login
请求方法:POST
| 用例ID | 用例名称 | 预期结果 | 优先级 |
|---|---|---|---|
| UC001 | 登陆成功-正确账号密码 | 响应码 200,返回 token、userId,msg="登录成功" | 高 |
| UC002 | 登录失败 - 错误账号 | 响应码 400,msg="账号不存在 / 密码错误" | 高 |
| UC003 | 登录失败 - 错误密码 | 响应码 400,msg="账号不存在 / 密码错误" | 高 |
| UC004 | 登录失败 - 账号为空 | 响应码 400,msg="账号不能为空" | 中 |
| UC005 | 登录失败 - 密码为空 | 响应码 400,msg="密码不能为空" | 中 |
| UC006 | 登录失败 - 账号过长(超过 20 位) | 响应码 400,msg="账号长度超出限制" | 中 |
| UC007 | 登录失败 - 密码过长(超过 20 位) | 响应码 400,msg="密码长度超出限制" | 中 |
| UC008 | 登录失败 - 请求方法错误(GET) | 响应码 405,msg="请求方法不支持" | 低 |
接口二、获取用户信息
请求接口:/user/getUserInfo
请求方法:GET
| 用例ID | 用例名称 | 请求头(token) | 预测结果 | 优先级 |
|---|---|---|---|---|
| UC009 | 获取成功-登录 | 有效 token | 响应码 200,返回 username、github 等用户信息 | 高 |
| UC010 | 获取失败-未登录 | 无 token | 响应码 401,msg="未登录,请先登录" | 高 |
| UC011 | 获取失败-请求方法错误(POST) | 有效 token | 响应码 405,msg="请求方法不支持" | 低 |
接口三、获取博客作者信息
请求接口:/user/getAuthorInfo
请求方法:GET
| 用例ID | 用例名称 | 请求头 | 预期结果 | 优先级 |
|---|---|---|---|---|
| UC012 | 获取成功 - 已登录 + 正确 blogId | 有效 token | 响应码 200,返回该博客作者的信息 | 高 |
| UC013 | 获取失败 - 未登录 | 无 token | 响应码 401,msg="未登录,请先登录" | 高 |
| UC014 | 获取失败 - blogId 为空 | 有效 token | 响应码 400,msg="blogId 不能为空" | 中 |
| UC015 | 获取失败 - blogId 不存在 | 有效 token | 响应码 404,msg="博客不存在" | 中 |
| UC016 | 获取失败 - blogId 为字符串 | 有效 token | 响应码 400,msg="blogId 必须为数字" | 中 |
| UC017 | 获取失败 - blogId 为负数 | 有效 token | 响应码 400,msg="blogId 不能为负数" | 中 |
| UC018 | 获取失败 - 请求方法错误(POST) | 有效 token | 响应码 405,msg="请求方法不支持" | 低 |
接口四、获取博客列表
请求接口:/blog/getList
请求方法:GET
| 用例ID | 用例名称 | 请求头 | 预期结果 | 优先级 |
|---|---|---|---|---|
| BC001 | 获取成功 - 已登录 | 有效 token | 响应码 200,返回未删除的博客列表(按更新时间倒序) | 高 |
| BC002 | 获取失败 - 未登录 | 无 token | 响应码 401,msg="未登录,请先登录" | 高 |
| BC003 | 获取失败 - 请求方法错误(POST) | 有效 token | 响应码 405,msg="请求方法不支持" | 低 |
接口五、获取博客详情
请求接口:/blog/getBlogDetail
请求方法:GET
| 用例ID | 用例名称 | 请求头 | 预期结果 | 优先级 |
|---|---|---|---|---|
| BC004 | 获取成功 - 已登录 + 正确 blogId | 有效 token | 响应码 200,返回该博客的标题、内容、作者等详情 | 高 |
| BC005 | 获取失败 - 未登录 | 无 token | 响应码 401,msg="未登录,请先登录" | 高 |
| BC006 | 获取失败 - blogId 为空 | 有效 token | 响应码 400,msg="blogId 不能为空" | 中 |
| BC007 | 获取失败 - blogId 不存在 | 有效 token | 响应码 404,msg="博客不存在" | 中 |
| BC008 | 获取失败 - blogId 为浮点数 | 有效 token | 响应码 400,msg="blogId 必须为整数" | 中 |
| BC009 | 获取失败 - blogId 为特殊字符 | 有效 token | 响应码 400,msg="blogId 必须为数字" | 中 |
| BC010 | 获取失败 - blogId 数字过大 | 有效 token | 响应码 400,msg="blogId 超出范围" | 低 |
| BC011 | 获取失败 - 请求方法错误(POST) | 有效 token | 响应码 405,msg="请求方法不支持" | 低 |
接口六、发布博客
请求接口:/blog/add
请求方法:POST
| 用例ID | 用例名称 | 请求头 | 预期结果 | 优先级 |
|---|---|---|---|---|
| BC012 | 发布成功 - 已登录 + 合法内容 | 有效 token | 响应码 200,msg="发布成功",返回 blogId | 高 |
| BC013 | 发布失败 - 未登录 | 无 token | 响应码 401,msg="未登录,请先登录" | 高 |
| BC014 | 发布失败 - 标题为空 | 有效 token | 响应码 400,msg="标题不能为空" | 中 |
| BC015 | 发布失败 - 内容为空 | 有效 token | 响应码 400,msg="内容不能为空" | 中 |
| BC016 | 发布失败 - 标题 + 内容都为空 | 有效 token | 响应码 400,msg="标题和内容不能为空" | 中 |
| BC017 | 发布失败 - 标题过长(超过 50 位) | 有效 token | 响应码 400,msg="标题长度超出限制" | 中 |
| BC018 | 发布失败 - 内容过长(超过 1000 位) | 有效 token | 响应码 400,msg="内容长度超出限制" | 中 |
| BC019 | 发布失败 - 标题含特殊字符 | 有效 token | 响应码 400,msg="标题含非法字符" | 低 |
| BC020 | 发布失败 - 请求方法错误(GET) | 有效 token | 响应码 405,msg="请求方法不支持" | 低 |
接口七、修改博客
请求接口:/blog/update
请求方法:POST
| 用例ID | 用例名称 | 请求头 | 预期结果 | 优先级 |
|---|---|---|---|---|
| BC021 | 修改成功 - 已登录 + 合法参数 | 有效 token | 响应码 200,msg="修改成功" | 高 |
| BC022 | 修改失败 - 未登录 | 无 token | 响应码 401,msg="未登录,请先登录" | 高 |
| BC023 | 修改失败 - blogId 为空 | 有效 token | 响应码 400,msg="blogId 不能为空" | 中 |
| BC024 | 修改失败 - blogId 不存在 | 有效 token | 响应码 404,msg="博客不存在" | 中 |
| BC025 | 修改失败 - blogId 为字符串 | 有效 token | 响应码 400,msg="blogId 必须为数字" | 中 |
| BC026 | 修改失败 - 标题为空 | 有效 token | 响应码 400,msg="标题不能为空" | 中 |
| BC027 | 修改失败 - 请求方法错误(GET) | 有效 token | 响应码 405,msg="请求方法不支持" | 低 |
接口八、删除博客
请求接口:/blog/delete
请求方法:POST
| 用例ID | 用例名称 | 请求头 | 预期结果 | 优先级 |
|---|---|---|---|---|
| BC028 | 删除成功 - 已登录 + 正确 blogId | 有效 token | 响应码 200,msg="删除成功"(逻辑删除,列表中不可见) | 高 |
| BC029 | 删除失败 - 未登录 | 无 token | 响应码 401,msg="未登录,请先登录" | 高 |
| BC030 | 删除失败 - blogId 为空 | 有效 token | 响应码 400,msg="blogId 不能为空" | 中 |
| BC031 | 删除失败 - blogId 不存在 | 有效 token | 响应码 404,msg="博客不存在" | 中 |
| BC032 | 删除失败 - blogId 为负数 | 有效 token | 响应码 400,msg="blogId 不能为负数" | 中 |
| BC033 | 删除失败 - 请求方法错误(GET) | 有效 token | 响应码 405,msg="请求方法不支持" | 低 |
四、项目架构
BlogApiAutoTest/
.venv/ # Python 虚拟环境
allure-reports/ # Allure 测试报告输出目录
allure-results/ # Allure 测试结果暂存目录
cases/ # 测试用例存放目录
__init__.py # 包初始化文件
test_add.py # 新增博客接口测试
test_blogdelete.py # 删除博客接口测试
test_blogupdate.py # 更新博客接口测试
test_detail.py # 博客详情接口测试
test_getAuthorInfo.py # 获取作者信息接口测试
test_getUserInfo.py # 获取用户信息接口测试
test_list.py # 博客列表接口测试
test_login.py # 用户登录接口测试
data/ # 测试数据存储目录
data.yml # 测试数据配置文件
logs/ # 日志文件存储目录
utils/ # 公共工具模块目录
logger_util.py # 日志记录工具
request_util.py # HTTP 请求封装工具
yaml_util.py # YAML 数据读写工具
pytest.ini # pytest 运行配置文件
requirements.txt # 项目依赖包列表
run.py # 测试执行启动脚本
博客系统接口测试用例.xmind # 接口测试用例设计文档
项目目录结构分析
cases/ - 测试用例目录
作为测试用例的集中管理目录,按照接口功能模块进行划分,每个测试文件对应一个独立的业务接口,涵盖正常场景、异常场景等。采用模块化组织方式,支持 pytest 框架自动发现和执行测试用例,提高测试执行效率。
data/ - 测试数据目录
专门存放测试数据文件,采用 YAML 格式管理测试数据,实现数据与脚本的完全分离。包含接口请求参数、预期结果、用户凭证等信息,支持多环境配置切换。当测试数据需要调整时,只需修改数据文件无需改动测试代码,降低维护成本,提升测试用例的可复用性。
utils/ - 工具模块目录
封装项目通用的基础功能模块,包括 HTTP 客户端封装、日志记录器、YAML 文件解析器等功能。通过统一封装实现请求头管理、响应解析、异常处理等通用逻辑,避免各测试用例重复编写相同代码,提高代码复用率和可维护性。
logs/ - 日志存储目录
记录测试执行过程中的详细信息,按照时间或测试用例级别生成日志文件。包含请求 URL、请求参数、请求头、响应状态码、响应数据、等关键信息。分级记录(INFO/DEBUG/ERROR)便于快速定位问题,为问题排查和测试报告分析提供数据支撑。
allure-results/ - 测试结果暂存目录
Allure 框架在执行测试用例时自动生成 JSON 格式的原始测试数据,包括用例执行状态、执行时间、步骤描述、附件信息等。该目录作为中间数据存储区,为后续生成可视化报告提供数据源,执行完成后可清理或保留用于调试。
allure-reports/ - 测试报告目录
存放 Allure 框架生成的最终测试报告,以 HTML 网页形式展示测试结果。包含测试用例通过率、失败用例详情、执行时间统计、环境信息、日志附件等内容。通过美观的可视化图表直观展示项目质量状况,支持按模块、按优先级等多维度筛选查看。
pytest.ini - pytest 配置文件
定义 pytest 运行时的全局配置参数,包括测试用例发现规则(文件命名规范、类命名规范)、命令行默认参数、日志输出格式等。通过配置文件统一测试执行标准,避免每次运行都输入冗长的命令行参数。
requirements.txt - 依赖管理文件
记录项目所需的所有 Python 第三方库及版本号,包括 pytest 测试框架、requests 请求库、pyyaml 数据解析、allure-pytest 报告插件等。通过该文件可快速搭建一致的开发测试环境,确保环境统一,避免因依赖版本差异导致的问题。
run.py - 测试执行入口
项目的主启动脚本,封装测试执行命令,支持选择执行特定模块、特定测试类或特定测试方法。可集成 pytest 执行参数、Allure 报告生成、环境初始化等操作,简化测试执行流程,降低使用门槛,支持一键运行全部或部分测试用例。
博客系统接口测试用例.xmind - 测试设计文档
采用思维导图形式梳理接口测试覆盖范围,包含各接口的测试点、测试场景、测试数据、预期结果等内容。作为测试设计的可视化文档,指导测试用例编写,确保测试覆盖全面性,同时便于评审和知识传承。
工具类封装
Request封装:集成日志、封装 host 地址和 requests 的 GET/POST 方法,统一请求入口,简化用例中的请求代码。
import requests
from utils.logger_util import logger
host = "http://127.0.0.1:8080/"
class Request:
log = logger.getlog()
def get(self,url,**kwargs):
self.log.info("准备发起get请求,url:"+url)
self.log.info("接口信息:{}".format(kwargs))
r = requests.get(url=url,**kwargs)
self.log.info("接口响应状态码:{}".format(r.status_code))
self.log.info("接口响应内容:{}".format(r.text))
return r
def post(self,url,**kwargs):
self.log.info("准备发起get请求,url:"+url)
self.log.info("接口信息:{}".format(kwargs))
r = requests.post(url=url,**kwargs)
self.log.info("接口响应状态码:{}".format(r.status_code))
self.log.info("接口响应内容:{}".format(r.text))
return r
YAML封装:封装 read_yaml/write_yaml 方法,实现 data.yml 文件的键值对读写,用于存储登录 token、用户 ID、博客 ID 等跨接口依赖数据。
import os
import yaml
#往yaml文件中写入数据
def write_yaml(filename,data):
with open(os.getcwd()+"/data"+filename,mode="a+",encoding="utf-8") as f:
yaml.safe_dump(data,stream=f)
#读取yaml文件中的数据
def read_yaml(filename,key):
with open(os.getcwd()+"/data"+filename,mode="r",encoding="utf-8") as f:
data=yaml.safe_load(f)
return data[key]
#清空
def clear_yaml(filename):
with open(os.getcwd()+"/data"+filename,mode="r",encoding="utf-8") as f:
f.truncate()
Logger封装:设置日志文件名称,支持按级别输出日志到不同文件以及控制台
import logging
import os.path
import time
class infoFilter(logging.Filter): #继承logging里面的Filter类
def filter(self,record):
return record.levelno == logging.INFO
class errorFilter(logging.Filter):
def filter(self,record):
return record.levelno == logging.ERROR
class logger:
#获取日志对象---定义类方法@classmethod
@classmethod
def getlog(cls):
#创建日志对象
cls.logger = logging.getLogger(__name__)
cls.logger.setLevel(logging.DEBUG)
#保证logs文件夹已经创建好了
LOG_PATH = "./logs/"
if not os.path.exists(LOG_PATH):
os.makedirs(LOG_PATH)
#将日志输出到日志文件中
'''
logs
2025-12-12.log
2025-12-12-info.log
2025-12-12-error.log
'''
now = time.strftime("%Y-%m-%d")
log_name = LOG_PATH + now + ".log"
info_log_name = LOG_PATH + now + "-info.log"
error_log_name = LOG_PATH + now + "-error.log"
#创建文件处理器
all_handler = logging.FileHandler(log_name , encoding='utf-8')
info_handler = logging.FileHandler(info_log_name , encoding='utf-8')
error_handler = logging.FileHandler(error_log_name , encoding='utf-8')
#创建处理器,将日志输出到控制台
streamHandler = logging.StreamHandler()
#设置日志内容格式
formatter = logging.Formatter(
"%(asctime)s %(levelname)s [%(name)s] [%(filename)s %(funcName)s:%(lineno)d] - %(message)s"
)
all_handler.setFormatter(formatter)
info_handler.setFormatter(formatter)
error_handler.setFormatter(formatter)
#控制台日志格式
streamHandler.setFormatter(formatter)
#添加过滤器(到处理器)
info_handler.addFilter(infoFilter())
error_handler.addFilter(errorFilter())
#将处理器添加到日志器
cls.logger.addHandler(all_handler)
cls.logger.addHandler(info_handler)
cls.logger.addHandler(error_handler)
#添加到控制台
# cls.logger.addHandler(streamHandler)
return cls.logger
五、编码
测试框架:pytest(支持用例参数化、执行顺序控制、断言灵活化)
请求库:requests(封装为自定义 Request 工具类,统一请求方法)
数据校验:jsonschema(校验接口返回数据的结构合法性,避免字段缺失 / 类型错误)
数据管理:PyYAML(实现测试数据的读写,完成 token、blogId 等关键数据的跨用例传递)
用例顺序:pytest-order(控制用例执行顺序,贴合业务操作链路)
1、测试执行顺序设计
博客系统的接口存在强业务依赖(如操作博客需先登录、查询博客详情需先获取博客 ID),因此通过 @pytest.mark.order(n) 装饰器严格控制用例执行顺序,贴合实际用户操作链路。
1. 登录接口(test_login.py)→ 2. 博客列表接口(test_list.py)→ 3. 新增博客接口(test_add.py)→ 4. 博客详情接口(test_detail.py)→ 5. 用户信息接口(test_getUserInfo.py)→ 6. 作者信息接口(test_getAuthorInfo.py)→ 7. 编辑博客接口(test_blogupdate.py)→ 8. 删除博客接口(test_blogdelete.py)
2、核心接口测试实现
本次测试覆盖博客系统8 大核心业务接口,每个接口单独建包为一个测试文件,采用类式用例设计,每个测试类对应一个接口,类内按【未登录场景→错误请求方法场景→登录成功场景→登录失败场景】设计测试方法,实现用例的模块化管理。以下为各核心接口的测试设计与核心代码实现。
①登录接口
接口基础信息:POST 请求,入参为用户名 / 密码,成功返回 token 和 userId,是所有后续接口的基础依赖。
测试场景:
- 登录失败:账号错误 / 密码错误 / 账号密码为空 / 参数过长等异常场景。
- 登录成功:多账号(zhangsan/lisi)正常登录,提取 token 和 userId 写入 data.yml 。
- 错误请求方法:使用 GET 请求调用 POST 接口,验证接口方法限制。核心设计亮点。
测试设计:
- 分 success_schema/fail_schema 分别校验成功 / 失败的返回结构,保证数据结构合法性。
- 用正则校验 token 格式(100 位以上非空字符串),提升校验精准度。
- 登录成功后自动将 User_token 和 User_id 写入 yaml,供后续接口直接读取。
部分代码演示:
'''
登录-接口自动化测试
url:http://127.0.0.1:8080/user/login
请求方法:POST
请求参数:username":"zhangsan","password":"123456"
'''
import re
import pytest
from jsonschema import validate
from utils.request_util import host, Request
from utils.yaml_util import write_yaml
@pytest.mark.order(1)
class TestLogin:
url = host + "user/login"
#登录成功schema
success_schema = {
"type":"object",
"required": ["code","errMsg","data"],
"additionalProperties":False,
"properties": {
"code":{
"type":"number"
},
"errMsg":{
"type":["string","null"]
},
"data":{
"type":"object",
"required": ["userId", "token"],
"additionalProperties": False,
"properties": {
"userId": {
"type": "number"
},
"token": {
"type": "string"
}
}
}
}
}
'''
登录成功测试
'''
#参数化
@pytest.mark.parametrize("login",[
{
"userName":"zhangsan",
"password":"123456"
},
{
"userName":"lisi",
"password":"123456"
}
])
def test_login_success(self,login):
data = {
"userName":login["userName"],
"password":login["password"]
}
r = Request().post(url=self.url,json=data)
validate(instance=r.json() , schema=self.success_schema)
#断言接口返回的值
assert r.json()["code"] == 200
assert r.json()["errMsg"] is None
assert re.match(r'\S{100,}',r.json()["data"]["token"])
#将token和userid写入data.yml文件
token_userId = {
"User_token" : r.json()["data"]["token"],
"User_id": r.json()["data"]["userId"]
}
write_yaml("/data.yml",token_userId)
②博客列表接口
接口基础信息:GET 请求,需携带登录 token,成功返回博客列表数据,提取首个博客 ID 写入 yaml 供后续详情 / 编辑 / 删除接口使用。
测试场景:
- 未登录场景:无 token 请求,验证 401 未授权。
- 错误请求方法:POST 请求调用 GET 接口,验证方法限制。
- 过期 token 场景:使用无效 token,验证 401 未授权。
测试设计:
- 登录成功场景:校验列表数据结构,提取有效 blogId 写入 yaml。
- 用 jsonschema 的 minItems:1 校验列表最少返回 1 条数据,保证业务基本可用性。
- 提取列表中首个博客的 id 作为 blogId 写入 yaml,实现与后续接口的数椐联动。
部分代码演示:
'''
列表-接口自动化测试
url:http://127.0.0.1:8080/blog/getList
请求方法:GET
请求头(Token)
'''
import pytest
from jsonschema.validators import validate
from utils.request_util import host, Request
from utils.yaml_util import read_yaml, write_yaml
@pytest.mark.order(2)
class TestList:
url = host + "blog/getList"
schema = {
"type": "object",
"required": ["code","errMsg","data"],
"additionalProperties": False,
"properties": {
"code": {
"type": "number"
},
"errMsg": {
"type": "null"
},
"data": {
"type": "array",
#要求返回的博客数据最少要有一个
"minItems": 1,
"items": {
"type": "object",
"required": ["id","title","content","userId","createTime"],
"additionalProperties": False,
"properties": {
"id": {
"type": "number"
},
"title": {
"type": "string"
},
"content": {
"type": "string"
},
"userId": {
"type": "number"
},
"createTime": {
"type": "string"
}
}
}
}
}
}
#登录状态下请求列表页
def test_list_login(self):
#获取token
token = read_yaml("/data.yml","User_token")
header = {
"User_token" : token
}
r = Request().get(url=self.url, headers=header)
validate(instance=r.json(),schema=self.schema)
#关键字段值校验
assert r.json()["code"] == 200
assert r.json()["errMsg"] is None
blogId = {
"blogId" : r.json()["data"][0]["id"]
}
write_yaml("/data.yml",blogId)
③新增博客接口
接口基础信息:POST 请求,需携带 token、userId,入参为标题 / 内容,是博客系统的核心业务接口。
测试场景:
- 未登录场景:无 token 请求,验证 401 未授权。
- 登录成功场景:正常入参添加博客,校验返回结果。
- 登录失败场景:标题为空 / 内容为空 / 标题过长 / 标题内容均过长等异常场景。
测试设计:
- 兼容 errMsg 的字符串 / 列表 / None三种返回格式,避免断言失败。
- 严格校验标题长度(不超过 20 字符),贴合业务规则。
- 采用参数化批量管理多异常场景,减少代码冗余。
部分代码演示:
'''
添加博客页-接口自动化测试
url:http://127.0.0.1:8080/blog/add
请求方法:POST
'''
import pytest
from jsonschema.validators import validate
from utils.request_util import host, Request
from utils.yaml_util import read_yaml
@pytest.mark.order(3)
class TestAdd:
url = host + "blog/add"
schema = {
"type": "object",
"required": ["code","errMsg","data"],
"additionalProperties": False,
"properties": {
"code": {
"type": "number"
},
"errMsg": {
"type": [ "null","string" ],
},
"data": {
"type": ["boolean","null"],
}
}
}
#未登录状态下添加博客
def test_add_nologin(self):
r = Request().post(url=self.url)
assert r.status_code == 401
④博客详情接口
接口基础信息:GET 请求,需携带 token+blogId,成功返回单篇博客的完整信息(id/title/content/createTime 等)。
测试场景:
- 未登录场景:无 token 请求,验证 401 未授权。
- 错误请求方法:POST 请求调用 GET 接口,验证方法限制。
- 登录成功场景:有效 blogId 请求,校验返回数据的完整结构。
- 登录失败场景:blogId 为空 / 不存在 / 为字符串 / 特殊字符 / 负数等异常场景。
部分代码演示:
'''
详情页-接口自动化测试
url:http://127.0.0.1:8080/blog/getBlogDetail?
请求方法:GET
请求参数:blogId=
'''
import pytest
from jsonschema.validators import validate
from utils.request_util import host, Request
from utils.yaml_util import read_yaml
@pytest.mark.order(4)
class TestDetail:
url = host + "blog/getBlogDetail"
schema = {
"type": "object",
"required": ["code", "errMsg", "data"],
"additionalProperties": False,
"properties": {
"code": {
"type": "number"
},
"errMsg": {
"type": "null"
},
"data": {
"type": "object",
"required": ["id", "title", "content", "userId", "createTime"],
"additionalProperties": False,
"properties": {
"id": {
"type": "number"
},
"title": {
"type": "string"
},
"content": {
"type": "string"
},
"userId": {
"type": "number"
},
"createTime": {
"type": "string"
}
}
}
}
}
#未登录状态下访问
def test_detail_nologin(self):
url = self.url + "?blogId=" + str(read_yaml("/data.yml", "blogId"))
r = Request().get(url=url)
assert r.status_code == 401
#登录状态下访问(正确的blogID)
def test_detail_login(self):
url = self.url + "?blogId=" + str(read_yaml("/data.yml", "blogId"))
token = read_yaml("/data.yml", "User_token")
header = {
"User_token" : token
}
r = Request().get(url=url,headers=header)
validate(instance=r.json(), schema=self.schema)
⑤用户信息接口
接口基础信息:GET 请求,请求头需携带登录 token,入参为userId,用于查询当前登录用户的个人基础信息,是用户模块核心查询接口。
测试核心:
- 未登录场景:不携带 token 直接请求,校验接口返回 401 未授权。
- 错误请求方法:使用 POST 请求调用 GET 接口,验证接口请求方法限制。
- 正常查询场景:携带有效 token + 正确 userId,校验返回用户信息结构与字段正确性。
- 异常参数场景:userId 为空、userId 为字符串 / 特殊字符 / 负数 / 不存在的 ID 等,验证接口参数容错能力。
测试设计:
- 独立定义成功 Schema / 失败 Schema,分别校验正常返回与异常返回的数据结构。
- 严格校验返回数据中的 userId 的与登录时写入 yaml 的 User_id 完全一致,保证数据真实性。
- 全覆盖参数异常场景,确保接口对非法 ID 能正确拦截并返回标准错误格式。
部分代码演示:
'''
用户信息-接口自动化测试
url:http://127.0.0.1:8080/user/getUserInfo?
请求方法:GET
请求参数:userId=
'''
import pytest
from jsonschema.validators import validate
from utils.request_util import Request, host
from utils.yaml_util import read_yaml
@pytest.mark.order(5)
class TestGetUserInfo:
url = host + "user/getUserInfo"
S_schema = {
"type": "object",
"required": ["code","errMsg","data"],
"additionalProperties": False,
"properties": {
"code": {
"type": "number"
},
"errMsg": {
"type": "null"
},
"data": {
"type": "object",
"required": ["id", "userName","githubUrl"],
"additionalProperties": False,
"properties": {
"id": {
"type": "number"
},
"userName": {
"type": "string"
},
"githubUrl": {
"type": "string"
}
}
}
}
}
#未登录状态下请求接口
def test_getUserInfo_unlogin(self):
url = self.url + "?blogId=" + str(read_yaml("/data.yml","blogId"))
r = Request().get(url=url)
r.status_code = 401
#用户信息请求成功
@pytest.mark.parametrize("userId",["1","2"])
def test_getUserInfo_Suc(self,userId):
token = read_yaml("/data.yml","User_token")
header = {
"User_token":token
}
param = {
"userId":userId
}
r = Request().get(self.url,headers=header,params=param)
validate(instance=r.json(),schema=self.S_schema)
⑥作者信息接口
接口基础信息:GET 请求,请求头需携带登录 token,入参为blogId,用于根据博客 ID 查询对应博客的作者信息,属于用户与博客关联查询接口。
测试场景:
- 未登录场景:无 token 请求,校验 401 未授权。
- 错误请求方法:使用 POST 请求调用,验证方法限制。
- 正常查询场景:携带有效 token + 合法 blogId,校验作者信息完整结构。
- 异常参数场景:blogId 为空、字符串、负数、不存在 ID、特殊字符等,验证接口异常处理能力。
测试设计:
- 分成功 Schema / 失败 Schema,严格校验不同场景返回结构。
- 强数据关联校验:校验接口返回的 userId 与登录用户的 User_id 一致,保证作者与博客归属关系正确。
- 覆盖全维度参数异常,验证接口对非法 blogId 的识别与拦截能力。
部分代码演示:
'''
用户认证-接口自动化测试
url:http://127.0.0.1:8080/user/getAuthorInfo?
请求方法:GET
请求参数:blogId=
'''
import pytest
from jsonschema.validators import validate
from utils.request_util import host, Request
from utils.yaml_util import read_yaml
@pytest.mark.order(6)
class TestgetAuthorInfo:
url = host + "user/getAuthorInfo"
Suc_schema = {
"type": "object",
"required": ["code","errMsg","data"],
"additionalProperties": False,
"properties": {
"code": {
"type": "number"
},
"errMsg": {
"type": "null"
},
"data": {
"type": "object",
"required": ["id","userName","githubUrl"],
"additionalProperties": False,
"properties": {
"id": {
"type": "number"
},
"userName": {
"type": "string"
},
"githubUrl": {
"type": "string"
}
}
}
}
}
Failed_schema = {
"type": "object",
"required": ["code","errMsg","data"],
"additionalProperties": False,
"properties": {
"code": {
"type": "number"
},
"errMsg": {
"type": "string"
},
"data": {
"type": "null"
}
}
}
#未登录状态访问用户认证接口
def test_getAuthorInfo_nologin(self):
url = self.url + "?blogId=" + str(read_yaml("/data.yml","blogId"))
r = Request().get(url=url)
assert r.status_code == 401
#post请求方法
def test_getAuthorInfo_post(self):
token = read_yaml("/data.yml","User_token")
url = self.url + "?blogId=" + str(read_yaml("/data.yml","blogId"))
user_id = read_yaml("/data.yml","User_id")
header = {
"User_token":token
}
r = Request().post(url=url,headers=header)
validate(instance=r.json(),schema=self.Failed_schema)
#断言
assert r.json()["code"] == -2
assert r.json()["errMsg"] == "Request method 'POST' is not supported"
assert r.json()["data"] is None
⑦编辑博客接口
接口基础信息:POST 请求,需携带 token,入参为 blogId / 标题 / 内容,支持博客信息修改。
测试亮点:
- 采用双参数化 (data,expected) ,将入参与预期返回结果一一对应,提升用例可读性。
- 校验 blogId 的各种非法格式(字符串 / 特殊字符 / 负数 / 过大数字),验证接口的参数解析能力。
- 单独测试【只传 id 不传标题 / 内容】的场景,贴合实际业务的误操作情况。
部分代码演示:
'''
博客更新-接口自动化测试
url:http://127.0.0.1:8080/blog/update?
请求方法:POST
请求参数:"id", "title", "content"
'''
import pytest
from jsonschema.validators import validate
from utils.request_util import host, Request
from utils.yaml_util import read_yaml
@pytest.mark.order(7)
class TestblogUpdate:
url = host + 'blog/update'
schema = {
"type": "object",
"required": ["code","errMsg","data"],
"additionalProperties": False,
"properties": {
"code": {
"type": "number"
},
"errMsg": {
"type": ["null","string"]
},
"data": {
"type": ["boolean","null"]
}
}
}
@pytest.mark.parametrize("data,expected", [
# 用例1:blogid为空
(
{"id": "", "title": "33333333", "content": "33333333333"},
{"code": -2, "errMsg": "博客Id不能为空", "data": None}
),
# 用例2:blogid不存在
(
{"id": 699, "title": "新标题326", "content": "新内容326"},
{"code": 200, "errMsg": None, "data": False}
),
# 用例3:blogid数字过大
(
{"id": 699999, "title": "新标题326", "content": "新内容326"},
{"code": 200, "errMsg": None, "data": False}
),
# 用例4:blogid为字符串
(
{"id": "bhvjhi", "title": "新标题bhvjhi", "content": "新内容bhvjhi"},
{"code": -2, "errMsg": "Cannot deserialize value", "data": None}
),
# 用例5:blogid为特殊字符
(
{"id": "%^&$#&", "title": "新标题%^&$#&", "content": "新内容%^&$#&"},
{"code": -2, "errMsg": "Cannot deserialize value", "data": None}
),
# 用例6:blogid为负数
(
{"id": -169, "title": "新标题-169", "content": "新内容-169"},
{"code": 200, "errMsg": None, "data": False}
),
# 用例7:只传id,不传title和content
(
{"id": 69},
{"code": -2, "errMsg": "博客更新失败", "data": None}
)
])
def test_update_fail(self, data, expected):
token = read_yaml("/data.yml", "User_token")
header = {"User_token": token}
r = Request().post(url=self.url, json=data, headers=header)
response = r.json()
validate(instance=response, schema=self.schema)
assert response["code"] == expected["code"]
if expected["errMsg"] is None:
assert response["errMsg"] is None
else:
assert expected["errMsg"] in response.get("errMsg", "")
assert response["data"] == expected["data"]
⑧删除博客接口
接口基础信息:POST 请求,需携带 token+blogId,为博客操作的收尾接口。
测试场景:
- 未登录场景:无 token 请求,验证 401 未授权。
- 错误请求方法:GET 请求调用 POST 接口,验证方法限制。
- 登录成功场景:使用有效 blogId 删除,校验删除成功结果。
- 登录失败场景:blogId 为空 / 不存在 / 字符串 / 特殊字符 / 浮点数 / 负数 / 0 等 8 种异常场景。
设计亮点:
- 复用 yaml 中存储的有效 blogId,保证删除操作的有效性。
- 全覆盖 ID 类参数的所有非法格式,验证接口的参数校验能力。
部分代码演示:
'''
删除博客-接口自动化测试
url:http://127.0.0.1:8080/blog/delete
请求方法:POST
请求参数:param:"blogId="
'''
from unittest import TestCase
import pytest
import requests
from jsonschema.validators import validate
from utils.request_util import host, Request
from utils.yaml_util import read_yaml
@pytest.mark.order(8)
class TestblogDelete():
url = host + "blog/delete"
schema = {
"type": "object",
"required": ["code","errMsg","data"],
"additionalProperties": False,
"properties": {
"code": {
"type": "number"
},
"errMsg": {
"type": ["string","null"]
},
"data": {
"type": ["boolean","null"]
}
}
}
#未登录状态下删除博客
def test_blog_delete(self):
blogId = str(read_yaml("/data.yml", 'blogId'))
url = self.url + blogId
r = Request().post(url = url)
assert r.status_code == 401
#登录状态下删除博客成功
def test_blog_deleteSuc(self):
blogId = str(read_yaml("/data.yml", 'blogId'))
url = self.url
param = {"blogId": blogId}
token = read_yaml("/data.yml", "User_token")
header = {
"User_token": token
}
r = Request().post(url = url, headers = header, params = param)
validate(instance = r.json(), schema = self.schema)
assert r.json()["code"] == 200
assert r.json()["errMsg"] is None
assert r.json()["data"] == True
3、总结
用例设计三层校验原则
所有接口的用例均实现三层精准校验,避免漏测和误判,保证接口返回结果的合法性和正确性:
- 状态码校验:校验 HTTP 响应状态码(如未登录 401、成功 200)。
- 结构校验:通过 jsonschema 校验返回数据的字段存在性、字段类型、必传字段,避免接口返回结构异常。
- 值校验:校验核心业务字段的值(如错误码 code、错误信息 errMsg、业务数据 data),保证业务逻辑正确性。
异常场景全覆盖原则
针对每个接口的入参和请求方式,实现全维度异常场景覆盖,包括:
- 请求方式异常:用非接口指定的请求方法调用(如 POST 接口用 GET 请求)。
- 认证异常:未登录、token 过期、无效 token。
- 参数异常:参数为空 / 不存在 / 类型错误(字符串 / 浮点数 / 特殊字符)/ 数值超限(过大 / 过小 / 负数)/ 必传参数缺失。
- 业务规则异常:如新增博客的标题过长、删除不存在的博客等。
数据跨用例复用原则
通过data.yml 文件实现关键数据的跨用例、跨文件传递,避免硬编码,保证用例的灵活性:
- 写入:登录成功的 token/userId、博客列表的 blogId 等核心数据,在对应用例中自动写入 yaml。
- 读取:后续所有接口直接从 yaml 中读取 token/userId/blogId,无需手动定义,若基础数据变化,只需修改一次即可。
用例模块化与解耦原则
- 文件解耦:一个接口对应一个测试文件,单独维护,修改某个接口的用例不影响其他接口。
- 类式设计:一个测试文件对应一个测试类,类内按场景拆分测试方法,逻辑清晰。
- 工具解耦:所有通用操作(请求、yaml 读写)封装为工具类,用例仅关注业务逻辑,减少代码冗余。
六、执行与生成报告
在项目根目录执行 pytest 命令,即可按预设顺序执行所有接口用例:
pytest -vs

用例执行结果判定
- 通过:三层校验均通过,无断言失败。
- 失败:任意一层校验不通过(如状态码错误、结构缺失字段、业务值不符),pytest 会直接抛出断言错误,定位到具体用例和代码行。
- 跳过 / 报错:若工具类、yaml 文件、接口地址存在问题,会直接抛出运行时错误。
生成静态报告:
allure generate allure-results -o allure-reports --clean
报告生成位置:

测试结果:


七、总结
该博客系统全接口自动化测试,基于 pytest 实现了8 大核心接口、数十个测试场景的自动化覆盖,通过工具类封装、数据跨用例复用、三层精准校验、异常场景全覆盖,是一套轻量、高效、可维护的自动化测试体系。
该体系替代人工重复的接口测试工作,提升测试效率,保证接口功能的稳定性,减少回归测试的漏测,贴合实际业务链路,验证接口的业务逻辑正确性。
整个项目完整代码已上传gitee
更多推荐
所有评论(0)