阶段 08:后端测试与接口文档
合辑:FastAPI × Vue3 JoyFit 系统阶段开发计划 |端:后端 |阶段:8 / 12 |前置阶段:阶段 07:后端活动首页模块
大家好,我是小马不起床。
目录
- 一、阶段目标
- 二、本阶段开发顺序
- 三、本阶段测试策略说明
- 四、本阶段文件清单
- 五、步骤 1:创建 OpenAPI 路径测试
- 六、步骤 2:创建权限种子测试
- 七、步骤 3:创建 Schema 校验测试
- 八、步骤 4:创建统一响应结构测试
- 九、步骤 5:创建 OpenAPI 导出脚本
- 十、步骤 7:运行测试与导出文档
- 十一、本阶段验收清单
- 十二、常见问题
- 十三、进入下一阶段的条件
- 十四、问题记录与修复方案
一、阶段目标
本阶段目标是在后端主要业务模块已经完成的基础上,补充稳定的自动化测试、检查 OpenAPI 接口文档,并整理后端交付给前端前的接口验收清单。
完成本阶段后,后端应具备以下能力:
- 自动检查核心接口是否出现在 OpenAPI 文档中。
- 自动检查初始化角色和权限是否完整。
- 自动检查核心 Schema 字段校验是否生效。
- 自动检查统一响应结构是否稳定。
- 可以导出
openapi.json供前端、Apifox 或 Postman 使用。 - 形成后端接口验收清单,供进入前端阶段前逐项确认。
二、本阶段开发顺序
1. 创建 OpenAPI 路径测试 2. 创建权限种子测试 3. 创建 Schema 校验测试 4. 创建统一响应结构测试 5. 创建 OpenAPI 导出脚本 6. 创建接口验收清单 7. 运行 pytest 和导出 OpenAPI 文档三、本阶段测试策略说明
本阶段先补充不依赖真实数据库写入的自动化测试。
原因:
- 当前项目使用 MySQL,直接在自动化测试中写真实开发库,容易污染手动测试的数据。
- SQLite 内存库和 MySQL 在
BigInteger自增、日期时间、外键行为上并不完全一致,直接替换数据库容易出现假通过或假失败。 - 当前阶段的重点是确认接口注册、文档生成、权限种子、字段校验和响应格式稳定。
因此本阶段自动化测试重点是:
- OpenAPI 路由完整性。
- 权限种子完整性。
- Schema 字段校验。
- 响应结构契约。
真实 CRUD 接口通过 Swagger 或 Apifox 按验收清单进行人工联调。后续引入更完整测试体系时,可单独配置 MySQL 测试库。
四、本阶段文件清单
| 序号 | 相对路径 | 类型 | 说明 |
|---|---|---|---|
| 1 | backend/tests/test_openapi.py | 新增 | 检查 OpenAPI 核心路径和认证配置 |
| 2 | backend/tests/test_seed_permissions.py | 新增 | 检查角色与权限种子 |
| 3 | backend/tests/test_schema_validation.py | 新增 | 检查核心 Schema 校验 |
| 4 | backend/tests/test_response_contract.py | 新增 | 检查统一响应结构 |
| 5 | backend/scripts/export_openapi.py | 新增 | 导出 OpenAPI JSON 文件 |
| 6 | backend/docs/api_checklist.md | 新增 | 后端接口验收清单 |
五、步骤 1:创建 OpenAPI 路径测试
相对路径
backend/tests/test_openapi.py完整代码
fromapp.mainimportappdeftest_openapi_contains_core_paths()->None:schema=app.openapi()paths=set(schema["paths"].keys())expected_paths={"/api/health","/api/auth/token","/api/auth/login","/api/auth/me","/api/auth/permissions","/api/auth/menus","/api/system/roles","/api/system/permissions","/api/users","/api/stores","/api/course-categories","/api/courses","/api/action-categories","/api/actions","/api/content/slogans","/api/content/events","/api/home",}missing_paths=expected_paths-pathsassertnotmissing_paths,f"missing openapi paths:{sorted(missing_paths)}"deftest_openapi_has_security_scheme()->None:schema=app.openapi()components=schema.get("components",{})security_schemes=components.get("securitySchemes",{})assertsecurity_schemesassertany(scheme.get("type")=="oauth2"forschemeinsecurity_schemes.values())注意事项
- 该测试不连接数据库,仅检查 FastAPI 已注册的接口文档。
- 若新增业务模块后遗漏路由注册,该测试可较早发现问题。
- 若后续接口路径调整,需同步修改
expected_paths。
六、步骤 2:创建权限种子测试
相对路径
backend/tests/test_seed_permissions.py完整代码
fromapp.db.seedimportDEFAULT_PERMISSIONS,DEFAULT_ROLES REQUIRED_ROLES={"admin","store_admin","coach","member",}REQUIRED_PERMISSIONS={"user:list","user:create","user:update","user:delete","role:list","role:create","role:update","role:delete","permission:list","store:list","store:create","store:update","store:delete","course:list","course:create","course:update","course:delete","action:list","action:create","action:update","action:delete","event:list","event:create","event:update","event:delete","slogan:list","slogan:create","slogan:update","slogan:delete",}deftest_default_roles_are_complete()->None:role_codes={role_codeforrole_code,role_nameinDEFAULT_ROLES}assertREQUIRED_ROLES.issubset(role_codes)deftest_default_permissions_are_complete()->None:permission_codes={permission_codeforpermission_code,permission_name,menu_pathinDEFAULT_PERMISSIONS}assertREQUIRED_PERMISSIONS.issubset(permission_codes)deftest_permission_codes_are_unique()->None:permission_codes=[permission_codeforpermission_code,permission_name,menu_pathinDEFAULT_PERMISSIONS]assertlen(permission_codes)==len(set(permission_codes))deftest_menu_permissions_have_expected_paths()->None:permission_paths={permission_code:menu_pathforpermission_code,permission_name,menu_pathinDEFAULT_PERMISSIONS}assertpermission_paths["user:list"]=="/users"assertpermission_paths["role:list"]=="/system/roles"assertpermission_paths["permission:list"]=="/system/permissions"assertpermission_paths["store:list"]=="/stores"assertpermission_paths["course:list"]=="/courses"assertpermission_paths["action:list"]=="/actions"assertpermission_paths["event:list"]=="/content/events"assertpermission_paths["slogan:list"]=="/content/slogans"注意事项
- 该测试可防止后续修改
seed.py时遗漏权限。 - 新增模块后,需将模块权限加入
REQUIRED_PERMISSIONS。 - 菜单路径仅检查具有前端菜单含义的权限。
七、步骤 3:创建 Schema 校验测试
相对路径
backend/tests/test_schema_validation.py完整代码
importpytestfrompydanticimportValidationErrorfromapp.schemas.actionimportActionCreatefromapp.schemas.contentimportActivityEventCreate,SloganCreatefromapp.schemas.userimportUserCreatedeftest_action_create_rejects_invalid_difficulty()->None:withpytest.raises(ValidationError):ActionCreate(action_name="杠铃卧推",category_id=1,action_difficulty=6,applicable_store_type=1,)deftest_action_create_rejects_invalid_store_type()->None:withpytest.raises(ValidationError):ActionCreate(action_name="杠铃卧推",category_id=1,action_difficulty=3,applicable_store_type=3,)deftest_slogan_create_rejects_invalid_status()->None:withpytest.raises(ValidationError):SloganCreate(slogan_name="首页主标语",slogan_content="用每一次训练,靠近更强的自己",status=2,)deftest_activity_event_create_accepts_date_string()->None:event=ActivityEventCreate(title="JoyFit 夏季力量挑战赛",event_date="2026-07-18",location="上海 JoyFit 徐汇店",status="报名中",description="面向会员开放的力量挑战赛。",)assertevent.event_date.isoformat()=="2026-07-18"deftest_user_create_rejects_bcrypt_unsafe_password_length()->None:withpytest.raises(ValidationError):UserCreate(role_id=1,username="too_long_password_user",password="x"*73,)注意事项
bcrypt对密码存在 72 字节限制,因此用户密码 Schema 限制最大长度为 72。- 动作难度限定为 1 到 5。
- 动作适用门店类型限定为 1 到 2。
- Slogan 状态限定为 0 或 1。
八、步骤 4:创建统一响应结构测试
相对路径
backend/tests/test_response_contract.py完整代码
fromdatetimeimportdate,datetimefromapp.utils.responseimportfail,successdeftest_success_response_contract()->None:body=success(data={"name":"JoyFit"})assertbody["code"]==200assertbody["message"]=="success"assertbody["data"]=={"name":"JoyFit"}deftest_fail_response_contract()->None:body=fail(message="permission denied",code=403)assertbody["code"]==403assertbody["message"]=="permission denied"assertbody["data"]isNonedeftest_response_json_encodes_date_and_datetime()->None:body=success(data={"event_date":date(2026,7,18),"created_at":datetime(2026,7,18,9,30,0),})assertbody["data"]["event_date"]=="2026-07-18"assertbody["data"]["created_at"]=="2026-07-18T09:30:00"注意事项
- 所有接口响应应统一为
code、message、data。 - 日期和时间由
jsonable_encoder转换为字符串,便于前端消费。 - 若后续修改统一响应结构,需同步调整本测试。
九、步骤 5:创建 OpenAPI 导出脚本
相对路径
backend/scripts/export_openapi.py完整代码
importjsonfrompathlibimportPathfromapp.mainimportapp OUTPUT_PATH=Path("docs/openapi.json")defmain()->None:schema=app.openapi()OUTPUT_PATH.parent.mkdir(parents=True,exist_ok=True)OUTPUT_PATH.write_text(json.dumps(schema,ensure_ascii=False,indent=2),encoding="utf-8",)print(f"OpenAPI schema exported to{OUTPUT_PATH}")if__name__=="__main__":main()运行命令
确保当前目录是:
FastAPI_Vue3_JoyFit/backend执行:
python scripts/export_openapi.py运行后生成:
backend/docs/openapi.json注意事项
- 运行脚本前无需启动 Uvicorn。
- 该脚本仅读取 FastAPI 路由和 Schema,不会写入业务数据库。
- 前端可将
openapi.json导入 Apifox 或 Postman 查看接口。
十、步骤 7:运行测试与导出文档
(开发顺序中的步骤 6「创建接口验收清单」与阶段验收内容已合并至「十一、本阶段验收清单」,验收清单文件同步沉淀为backend/docs/api_checklist.md。)
运行测试
确保当前目录是:
FastAPI_Vue3_JoyFit/backend执行:
pytest预期结果:
所有测试通过导出 OpenAPI
执行:
python scripts/export_openapi.py预期生成:
backend/docs/openapi.json查看 Swagger
启动服务:
uvicorn app.main:app--reload--host0.0.0.0--port8000访问:
http://127.0.0.1:8000/docs十一、本阶段验收清单
本节由原有的「接口验收清单」与「本阶段验收清单」两处章节合并而成,条目取两者并集并已去重。清单内容同步保存为backend/docs/api_checklist.md(文件清单第 6 项),供前端联调阶段逐项确认。
自动化测试与交付物
- OpenAPI 路径测试通过,核心路径全部存在于 OpenAPI 文档
- 权限种子测试通过,初始化角色和权限完整
- Schema 校验测试通过,非法字段能被拒绝
- 统一响应结构测试通过,
code/message/data稳定 - 成功导出
docs/openapi.json - 接口验收清单文件
docs/api_checklist.md已创建 pytest全部测试通过- Swagger 可以访问:
http://127.0.0.1:8000/docs
启动检查
- 已进入
backend目录 - 已激活
.venv .env数据库配置正确- MySQL 服务正常运行
- 后端启动成功
登录认证
/api/auth/token可以获取 Swagger 授权 Token/api/auth/login可以返回统一响应结构/api/auth/me可以返回当前用户/api/auth/permissions可以返回当前用户权限/api/auth/menus可以返回当前用户菜单
系统权限
/api/system/roles可以查询角色/api/system/permissions可以查询权限- 管理员账号拥有业务管理权限
- 无权限账号访问管理接口返回 403
用户与门店
- 用户列表可以分页查询
- 用户可以新增
- 用户可以修改
- 用户状态可以修改
- 用户档案可以维护
- 省份列表可以查询
- 门店可以新增、修改、删除
- 用户可以绑定门店
课程模块
- 课程分类可以新增、修改、删除
- 课程可以新增、修改、删除
- 课程列表支持分页查询
- 登录用户可以收藏课程
- 登录用户可以取消收藏课程
- 登录用户可以查看课程收藏
动作库模块
- 动作分类可以新增、修改、删除
- 动作可以新增、修改、删除
- 动作列表支持分页查询
- 登录用户可以收藏动作
- 登录用户可以取消收藏动作
- 登录用户可以查看动作收藏
活动首页模块
- Slogan 可以新增、修改、删除
- Slogan 可以按状态查询
- 活动可以新增、修改、删除
- 活动列表支持分页查询
- 活动列表支持状态和日期筛选
/api/home不登录也可以访问/api/home返回启用状态的 Slogan/api/home返回活动列表摘要
统一响应与错误
- 成功响应包含
code - 成功响应包含
message - 成功响应包含
data - 参数错误返回 422
- 未登录返回 401
- 无权限返回 403
- 业务错误返回统一 JSON 结构
后端交付前检查
- Swagger 中接口分组清晰
- 所有接口路径与前端约定一致
- 管理接口均有权限控制
- 用户端接口不误用后台权限
使用说明
- 人工验收条目不参与自动化测试,需通过 Swagger 或 Apifox 逐项确认。
- 每完成一次后端阶段,需重新勾选一遍。
- 进入前端阶段前,至少完成登录、权限、课程、动作、活动首页的验收。
十二、常见问题
1.test_openapi_contains_core_paths失败
原因:
通常为新增接口未注册到backend/app/api/router.py,或测试中的路径与实际接口路径不一致。
修复:
- 打开 Swagger 查看真实路径。
- 若路由遗漏注册,补充
api_router.include_router(...)。 - 若接口路径已调整,同步修改
expected_paths。
2.test_default_permissions_are_complete失败
原因:
backend/app/db/seed.py中缺少某个业务权限。
修复:
- 对照失败信息中的权限代码。
- 将缺失权限补充到
DEFAULT_PERMISSIONS。 - 重启服务,使种子数据自动补齐到数据库。
3.test_schema_validation失败
原因:
Schema 字段范围被改动,或测试用例与当前业务规则不一致。
修复:
- 若业务规则未变,修复 Schema。
- 若业务规则已调整,修改测试期望。
4.python scripts/export_openapi.py提示找不到app
原因:
命令未在backend目录下执行。
修复:
先进入:
cdbackend再执行:
python scripts/export_openapi.py5. 导出的 OpenAPI 没有最新接口
原因:
路由未注册,或导出前代码未保存。
修复:
- 确认对应路由文件已被
backend/app/api/router.py引入。 - 重新执行导出脚本。
- 打开
docs/openapi.json搜索接口路径。
十三、进入下一阶段的条件
本阶段为后端开发线的终点。满足以下全部条件后,进入前端线,即阶段 09:前端基础工程构建:
pytest全部通过。docs/openapi.json可以生成。docs/api_checklist.md已创建。- Swagger 能正常访问。
- 后端核心接口已完成人工验收。
- 后端接口路径和响应结构已稳定。
十四、问题记录与修复方案
本阶段实践中遇到问题时,完整记录报错原文,便于检索与定位。记录后按以下流程处理:
- 判断错误属于测试用例、权限种子、Schema 校验、OpenAPI 导出还是接口验收问题。
- 将修复方案按下方模板追加至本节底部。
问题追加格式
### 问题 N:问题标题 #### 报错现象 按下方模板记录报错信息。 #### 原因分析 说明报错产生的原因。 #### 修复办法 给出具体修复步骤。 #### 需要修改的文件 - 相对路径:xxx - 修改说明:xxx - 完整代码或补丁:xxx问题 1:执行python3 scripts/export_openapi.py提示找不到app
报错现象
在backend目录下执行:
python3 scripts/export_openapi.py报错:
ModuleNotFoundError: No module named 'app'原因分析
直接以下列方式运行脚本时:
python3 scripts/export_openapi.pyPython 会优先将脚本所在目录backend/scripts加入模块搜索路径,而不会稳定地将backend根目录加入其中。
因此脚本中的:
fromapp.mainimportapp无法找到backend/app这个包。
修复办法
在脚本开头将backend根目录加入sys.path。
需要修改的文件
- 相对路径:
backend/scripts/export_openapi.py - 修改说明:导入
app.main前,先将项目后端根目录加入 Python 模块搜索路径 - 完整代码:
importjsonimportsysfrompathlibimportPath BACKEND_DIR=Path(__file__).resolve().parents[1]ifstr(BACKEND_DIR)notinsys.path:sys.path.insert(0,str(BACKEND_DIR))fromapp.mainimportapp OUTPUT_PATH=BACKEND_DIR/"docs"/"openapi.json"defmain()->None:schema=app.openapi()OUTPUT_PATH.parent.mkdir(parents=True,exist_ok=True)OUTPUT_PATH.write_text(json.dumps(schema,ensure_ascii=False,indent=2),encoding="utf-8",)print(f"OpenAPI schema exported to{OUTPUT_PATH}")if__name__=="__main__":main()修复后验证
确保当前目录是:
FastAPI_Vue3_JoyFit/backend再次执行:
python3 scripts/export_openapi.py预期结果:
OpenAPI schema exported to /Users/.../FastAPI_Vue3_JoyFit/backend/docs/openapi.json