一、项目介绍

 实战前期准备

搭建测试环境、库的介绍、安装allure

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/getListGET登录态拦截、请求方法校验
博客详情接口/blog/getBlogDetail?blogId=GET登录态拦截、blogId 参数合法性(空 / 不存在 / 非数字 / 过大 / 负数等)、请求方法校验
作者信息接口/user/getAuthorInfo?blogId=GET登录态拦截、blogId 参数合法性(同详情接口)、请求方法校验
博客新增接口/blog/addPOST登录态拦截、标题 / 内容合法性(空 / 过长 / 特殊字符 / 图片 / 链接)、请求方法校验
用户信息接口/user/getUserInfoGET登录态拦截、请求方法校验
修改博客接口/blog/updatePOST登录态拦截、请求方法校验
删除博客接口/blog/deletePOST登录态拦截、请求方法校验

三、设计测试用例

测试用例脑图

接口一:用户登录

请求接口:/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,用于查询当前登录用户的个人基础信息,是用户模块核心查询接口。

测试核心:

  1. 未登录场景:不携带 token 直接请求,校验接口返回 401 未授权。
  2. 错误请求方法:使用 POST 请求调用 GET 接口,验证接口请求方法限制。
  3. 正常查询场景:携带有效 token + 正确 userId,校验返回用户信息结构与字段正确性。
  4. 异常参数场景: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 查询对应博客的作者信息,属于用户与博客关联查询接口。

测试场景:

  1. 未登录场景:无 token 请求,校验 401 未授权。
  2. 错误请求方法:使用 POST 请求调用,验证方法限制。
  3. 正常查询场景:携带有效 token + 合法 blogId,校验作者信息完整结构。
  4. 异常参数场景: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

博客接口自动化测试

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐