第9组 · 缪茂锦 · 第1次评审详情 - sebestp/coursewise GitHub Wiki
- 报告期次:第1次 | 报告日期:2026-10-08 | 项目组号:第9组 | 项目名称:在线购物系统
-
已完成工作:编写《测试计划》:明确测试范围与范围外说明、测试策略、测试环境、组织分工、进度安排、准入准出标准与风险应对
-
成果物:测试计划
-
已完成工作:设计并执行首轮黑盒功能测试 113 条,覆盖商品浏览、登录鉴权、商品管理、排队口令码、交易流程、历史记录、意向管理、CSV 导出、图片上传、并发一致性、安全与健壮性、前端联调 12 个模块;通过 111 条,通过率 98.2%;建立需求追溯矩阵(测试执行日期 2026-10-07)
-
成果物:测试用例
-
已完成工作:发现并登记确认缺陷 2 个:BUG-001 发布商品价格不足 1 分导致 HTTP 500(中级)、BUG-002 历史搜索未转义 LIKE 通配符(低级);均给出复现步骤、根因分析、日志证据与修复建议
-
成果物:BUG 记录与汇总
-
已完成工作:汇总观察项与改进建议 10 项,覆盖上传接口未鉴权、文件类型未做魔数校验、需求与设计文档不一致等
-
成果物:BUG 记录与汇总
-
已完成工作:编写《测试报告》:含测试概述、依据与范围、执行情况、缺陷分析、需求覆盖情况、测试结论与遗留问题
-
成果物:测试报告
-
已完成工作:完成功能验收校验 47 项(通过 45 项)与上线合规检查 35 项(通过 22 项、有条件通过 4 项);并搭建打通前后端联调环境(后端 8000 / 前端 5173),执行仓库自带 pytest 回归 28 条全部通过
-
成果物:功能验收校验清单
编写人:缪茂锦(测试岗) | 被测系统:在线购物系统(单卖家版) | 仓库:https://github.com/tyq-arch/single-merchant-shop-9 功能测试执行日期:2026-10-07 | 适用迭代:第 1 次迭代(2026-09-24 ~ 2026-10-08) | 对应评审:第 1 次阶段性评审(2026-10-08)
明确测试范围、策略、环境、组织分工、进度安排与准入准出标准,作为测试岗开展功能测试、 缺陷记录与验收校验的统一依据,同时作为阶段性评审与项目验收时测试工作的说明材料。
被测对象:single-merchant-shop-9 仓库当前主分支的前后端完整实现。
- 后端:Python 3.12 + FastAPI + Uvicorn + SQLite(WAL 模式),24 个接口;
- 前端:Vue 3 + Vite + Vue Router + Pinia + Element Plus,12 条路由(买家端 5 页 + 卖家后台 7 页);
- 数据层:6 张业务表,含唯一在售约束与金额 CHECK 约束。
覆盖范围
| 序号 | 测试类型 | 覆盖内容 |
|---|---|---|
| 1 | 接口功能测试 | 24 个 HTTP 接口的正常流与异常流 |
| 2 | 业务流程测试 | 浏览 → 提交意向 → 排队 → 进入交易 → 标记结果的端到端流程 |
| 3 | 状态机测试 | 商品 3 态、意向 7 态、口令码 2 态的合法与非法迁移 |
| 4 | 边界与异常测试 | 字段长度、金额边界、分页边界、非法枚举、非法 JSON、超长参数 |
| 5 | 并发一致性测试 | 多线程并发提交意向、并发进入交易 |
| 6 | 安全性测试 | 鉴权覆盖、越权访问、SQL 注入、路径穿越、XSS 存储 |
| 7 | 隐私保护测试 | 买家电话脱敏规则 |
| 8 | 前端可用性测试 | 12 条路由可访问性、模块编译、前后端代理联调 |
| 9 | 文档一致性核对 | 需求 / 接口 / 页面文档之间的一致性 |
不覆盖范围
- 专业性能压测与长时间稳定性测试(NFR-007 可用性 ≥99% 无法在课程单机环境验证);
- 多浏览器、多终端兼容性矩阵测试;
- HTTPS、CSRF 等部署层安全项(课程环境为本地 HTTP 明文部署);
- 需求文档中列为 TBD 且尚未实现的功能(TBD-01 ~ TBD-14)。
| 层次 | 测试类型 | 执行方式 | 责任人 |
|---|---|---|---|
| 第 1 层 | 开发自测(单元/接口) | 仓库自带 pytest 用例 | 郭子涵(后端) |
| 第 2 层 | 黑盒功能测试 | 自动化套件对运行中的服务发起真实请求 | 缪茂锦(测试) |
| 第 3 层 | 前端功能与联调测试 | 浏览器人工走查 + HTTP 层可用性校验 | 吴顺宝 / 缪茂锦 |
| 第 4 层 | 验收测试与合规检查 | 依据验收清单与合规表逐项核对 | 缪茂锦(测试) |
测试方法:黑盒测试为主、等价类划分与边界值分析、状态迁移测试、场景法、 并发测试(20 线程)、错误推测法(结合代码走查针对金额换算、LIKE 通配符、鉴权缺失等高危点)。
| 项目 | 配置 |
|---|---|
| 操作系统 | Windows |
| 后端运行时 | Python 3.12.10 + FastAPI 0.142.2 + Uvicorn 0.54.0 |
| 数据库 | SQLite(WAL 模式,每轮测试前重建独立临时库) |
| 前端运行时 | Node.js ≥ 22(实测 v24.21.0)+ Vite 8.3.3 |
| 服务地址 | 后端 127.0.0.1:8000 前端 127.0.0.1:5173 |
| 测试工具 | 自研 Python 黑盒功能测试套件、urllib 直连接口、多线程并发测试 |
| 角色 | 测试相关职责 |
|---|---|
| 测试岗(缪茂锦) | 编写测试计划与用例;执行功能测试;记录汇总缺陷;编写测试报告;验收校验;上线合规检查 |
| 后端开发岗(郭子涵) | 提供后端服务与接口文档;修复缺陷;维护开发自测用例 |
| 前端开发岗(吴顺宝) | 提供前端页面;配合前端与联调测试;修复前端缺陷 |
| 设计岗(周育民) | 提供需求与设计文档;澄清需求歧义;修订文档不一致 |
| 组长 | 协调测试资源与进度;汇总成果物;对接评审与提交 |
准入:被测版本已完成开发自测且 pytest 全部通过;前后端均可正常启动; 需求/数据库/接口/页面设计文档已提交;测试环境就绪、数据库可重建。
准出:用例执行率 100%,无因环境问题被阻塞的用例;无「高」级别未修复缺陷; 「中」级别缺陷已修复并回归通过或在报告中说明影响与规避方式; 功能验收通过率不低于 95%;测试报告完成且结论明确。
| 序号 | 风险 | 影响 | 应对措施 |
|---|---|---|---|
| 1 | 需求文档存在重复条目与图文不一致 | 中 | 以正文条款与状态表为准,记录歧义并在评审时确认 |
| 2 | 接口文档章节覆盖不完整 | 低 | 以实际接口行为为准补充用例,差异记入观察项 |
| 3 | 并发用例依赖线程时序,可能偶发不稳定 | 中 | 固定 20 线程,重复执行确认稳定后再采信 |
| 4 | 金额精度、字符集等边界易被遗漏 | 中 | 边界值分析 + 错误推测法,结合代码走查 |
| 5 | 前端交互细节难以自动化验证 | 低 | 代码走查与人工走查结合,报告中说明验证方式 |
| 6 | 课程环境无法验证性能与可用性需求 | 中 | 明确说明未覆盖项与原因,避免过度承诺 |
测试计划、测试用例、BUG 记录与汇总表、测试报告、功能验收校验清单与上线合规检查表、 自动化测试脚本与原始结果。
编写人:缪茂锦(测试岗) | 共 113 条用例,通过 111 条,失败 2 条,通过率 98.2% 执行方式:黑盒功能测试,对运行中的后端发起真实 HTTP 请求。 完整用例含「前置条件 / 测试步骤与输入 / 实际结果」等列,见仓库
docs/测试/02-测试用例.xlsx。
| 测试模块 | 用例数 | 通过 | 失败 | 通过率 |
|---|---|---|---|---|
| 商品浏览 | 6 | 6 | 0 | 100.0% |
| 登录鉴权 | 10 | 10 | 0 | 100.0% |
| 商品管理 | 21 | 20 | 1 | 95.2% |
| 排队口令码 | 15 | 15 | 0 | 100.0% |
| 交易流程 | 18 | 18 | 0 | 100.0% |
| 历史记录 | 7 | 6 | 1 | 85.7% |
| 意向列表 | 2 | 2 | 0 | 100.0% |
| 分页 | 4 | 4 | 0 | 100.0% |
| 操作日志 | 2 | 2 | 0 | 100.0% |
| CSV 导出 | 3 | 3 | 0 | 100.0% |
| 图片上传 | 8 | 8 | 0 | 100.0% |
| 并发 | 4 | 4 | 0 | 100.0% |
| 安全 | 4 | 4 | 0 | 100.0% |
| 健壮性 | 4 | 4 | 0 | 100.0% |
| 前端 | 5 | 5 | 0 | 100.0% |
| 合计 | 113 | 111 | 2 | 98.2% |
| 用例编号 | 模块 | 用例标题 | 需求追溯 | 预期结果 | 实际结果 | 结果 |
|---|---|---|---|---|---|---|
| TC-A01 | 商品浏览 | 无商品在售时返回 NONE 空状态 | FR-044 | state=NONE, product=null, message=暂无商品在售 | {"state": "NONE", "product": null, "message": "暂无商品在售"} | ✅ |
| TC-A02 | 商品浏览 | 在售时返回 ON_SALE 与完整商品信息 | FR-045 | state=ON_SALE, price=88.5, status_label=在售 | state=ON_SALE price=88.5 label=在售 | ✅ |
| TC-A03 | 商品浏览 | 商品详情接口按 id 返回商品 | FR-043 | HTTP 200 且 product.id 一致 | HTTP 200 id=141afe77954840dfa70233e0277059ad | ✅ |
| TC-A04 | 商品浏览 | 冻结时返回 FROZEN 与交易中提示 | FR-046 | state=FROZEN, message=商品交易中 | state=FROZEN message=商品交易中 | ✅ |
| TC-A05 | 商品浏览 | 已下架商品详情返回 404 | FR-043 | HTTP 404 NOT_FOUND | HTTP 404 NOT_FOUND | ✅ |
| TC-A06 | 商品浏览 | /current 路由优先于 /{product_id} 通配路由 | 接口设计§2 | 返回三态结构(含 state 字段),而非商品详情结构 | keys=['message', 'product', 'state'] | ✅ |
| TC-B01 | 登录鉴权 | 正确账号密码登录成功并下发令牌 | FR-032 | HTTP 200 + token + expires_at + seller.username=seller | HTTP 200 token=True | ✅ |
| TC-B02 | 登录鉴权 | 密码错误 时返回 401 | FR-032 | HTTP 401 UNAUTHORIZED | HTTP 401 UNAUTHORIZED | ✅ |
| TC-B03 | 登录鉴权 | 用户名不存在 时返回 401 | FR-032 | HTTP 401 UNAUTHORIZED | HTTP 401 UNAUTHORIZED | ✅ |
| TC-B04 | 登录鉴权 | 无令牌访问受保护接口返回 401 | NFR-006 | HTTP 401 | HTTP 401 | ✅ |
| TC-B05 | 登录鉴权 | 伪造令牌被拒绝 | NFR-006 | HTTP 401 | HTTP 401 | ✅ |
| TC-B06 | 登录鉴权 | 不提供注册接口 | FR-033 | HTTP 404 | HTTP 404 | ✅ |
| TC-B07 | 登录鉴权 | 修改密码时旧密码校验失败返回 401 | FR-035 | HTTP 401 | HTTP 401 | ✅ |
| TC-B08 | 登录鉴权 | 新密码少于 8 位被拒绝 | FR-035 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-B09 | 登录鉴权 | 改密成功后旧密码失效、新密码生效 | FR-035 | 改密 200;旧密码登录 401;新密码登录 200 | 改密=200 旧密码=401 新密码=200 | ✅ |
| TC-B10 | 登录鉴权 | 令牌有效但参数非法时正常返回 422 | 接口设计§1 | HTTP 422 | HTTP 422 | ✅ |
| TC-C01 | 商品管理 | 发布商品成功并置为在售 | FR-001 | HTTP 200, status=ON_SALE, price=99.9 | HTTP 200 status=ON_SALE | ✅ |
| TC-C02 | 商品管理 | 已有在售商品时重复发布被拒(唯一在售) | FR-008 | HTTP 409 CONFLICT | HTTP 409 当前已有在售或交易中的商品,需先下架后才能发布新商品 | ✅ |
| TC-C03 | 商品管理 | 名称为纯空格 被校验拒绝 | FR-001 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-C04 | 商品管理 | 价格为 0 被校验拒绝 | FR-001 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-C05 | 商品管理 | 价格为负数 被校验拒绝 | FR-001 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-C06 | 商品管理 | 价格超上限 被校验拒绝 | FR-001 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-C07 | 商品管理 | 缺少名称字段 被校验拒绝 | FR-001 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-C08 | 商品管理 | 名称超 120 字 被校验拒绝 | FR-001 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-C09 | 商品管理 | 描述超 2000 字 被校验拒绝 | FR-001 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-C10 | 商品管理 | 价格 0.001 元(不足 1 分)应被优雅拒绝而非 500 | FR-001 / NFR-009 | HTTP 422(参数校验失败) | HTTP 500,响应体=Internal Server Error | ❌ |
| TC-C11 | 商品管理 | 手动冻结商品成功 | FR-009 | HTTP 200, status=FROZEN | HTTP 200 status=FROZEN | ✅ |
| TC-C12 | 商品管理 | 重复冻结被拒 | FR-009 | HTTP 409 | HTTP 409 | ✅ |
| TC-C13 | 商品管理 | 冻结期间不接收新意向 | FR-012/FR-016 | HTTP 409 且提示含「交易中」 | HTTP 409 商品交易中,暂停接收新意向 | ✅ |
| TC-C14 | 商品管理 | 手动解冻恢复在售 | FR-010 | HTTP 200, status=ON_SALE | HTTP 200 status=ON_SALE | ✅ |
| TC-C15 | 商品管理 | 非冻结状态解冻被拒 | FR-010 | HTTP 409 | HTTP 409 | ✅ |
| TC-C16 | 商品管理 | 存在进行中交易时禁止手动解冻 | FR-010/FR-011 | HTTP 409 | HTTP 409 | ✅ |
| TC-C17 | 商品管理 | 下架商品成功 | FR-003 | HTTP 200, status=OFF_SHELF | HTTP 200 status=OFF_SHELF | ✅ |
| TC-C18 | 商品管理 | 重复下架被拒 | FR-003 | HTTP 409 | HTTP 409 | ✅ |
| TC-C19 | 商品管理 | 已下架商品不接收新意向 | FR-003 | HTTP 409 | HTTP 409 商品已下架,无法提交购买意向 | ✅ |
| TC-C20 | 商品管理 | 冻结不存在商品返回 404 | 接口设计§1 | HTTP 404 NOT_FOUND | HTTP 404 | ✅ |
| TC-C21 | 商品管理 | 后台不提供商品编辑接口(发布后不可改) | FR-002 | PATCH /api/seller/products/{id} 不存在(HTTP 404/405) | PATCH -> HTTP 404 | ✅ |
| TC-D01 | 排队口令码 | 提交意向返回口令码与排队序号 1 | FR-013/FR-014 | HTTP 200, queue_position=1, status=QUEUED, command_code 非空 | HTTP 200 pos=1 status=QUEUED code=True | ✅ |
| TC-D02 | 排队口令码 | 口令码为 10 位且不含易混淆字符 0/O/1/I/l | NFR-004 | 长度 10,字符集为 ABCDEFGHJKMNPQRSTUVWXYZ23456789 | code=SZ7REGN84Q | ✅ |
| TC-D03 | 排队口令码 | 连续提交按先到先得分配 1..5 | FR-015/NFR-014 | 序号依次为 [1,2,3,4,5] | [1, 2, 3, 4, 5] | ✅ |
| TC-D04 | 排队口令码 | 凭口令码查询意向状态与位次 | FR-022 | HTTP 200, position=1, can_edit=true, can_cancel=true | HTTP 200 pos=1 edit=True cancel=True | ✅ |
| TC-D05 | 排队口令码 | 无效口令码返回 410 | FR-022/NFR-005 | HTTP 410 COMMAND_CODE_INVALID | HTTP 410 COMMAND_CODE_INVALID | ✅ |
| TC-D06 | 排队口令码 | 修改联系电话后位次不变、未提交字段保持原值 | FR-023 | 电话=13900002222, 姓名=张三, position 仍为 1 | HTTP 200 phone=13900002222 name=张三 pos=1 | ✅ |
| TC-D07 | 排队口令码 | 修改请求未携带任何可改字段被拒 | FR-023 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-D08 | 排队口令码 | 撤销排队后口令码立即失效 | FR-024/FR-026 | 撤销 200;再次查询 410 | 撤销=200 再查=410 | ✅ |
| TC-D09 | 排队口令码 | 撤销后队列序号连续补位(无空洞) | FR-024 | 等待中序号应为连续整数 1..n | 等待中序号=[1, 2, 3, 4, 5] | ✅ |
| TC-D10 | 排队口令码 | 姓名为空 被校验拒绝 | FR-013 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-D11 | 排队口令码 | 电话格式非法(字母) 被校验拒绝 | FR-013 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-D12 | 排队口令码 | 电话不足 5 位 被校验拒绝 | FR-013 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-D13 | 排队口令码 | 电话超 20 位 被校验拒绝 | FR-013 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-D14 | 排队口令码 | 姓名超 64 字 被校验拒绝 | FR-013 | HTTP 422 VALIDATION_ERROR | HTTP 422 | ✅ |
| TC-D15 | 排队口令码 | 向不存在的商品提交意向返回 404 | 接口设计§1 | HTTP 404 NOT_FOUND | HTTP 404 | ✅ |
| TC-E01 | 交易流程 | 队列为空时进入交易被拒 | FR-021 | HTTP 409 且提示「队列为空」 | HTTP 409 当前队列为空,没有可交易的意向购买人 | ✅ |
| TC-E02 | 交易流程 | 进入交易:队首转 IN_TRANSACTION 且商品自动冻结 | FR-011 | HTTP 200;order.status=IN_TRANSACTION;product.status=FROZEN;取队首买家A | HTTP 200 order=IN_TRANSACTION product=FROZEN | ✅ |
| TC-E03 | 交易流程 | 已有进行中交易时不可重复进入 | FR-011 | HTTP 409 | HTTP 409 | ✅ |
| TC-E04 | 交易流程 | 进入交易后买家不可撤销 | FR-025 | HTTP 409 | HTTP 409 | ✅ |
| TC-E05 | 交易流程 | 进入交易后买家不可修改信息 | FR-023/FR-025 | HTTP 409 | HTTP 409 | ✅ |
| TC-E06 | 交易流程 | 进入交易后口令码仍可查询且标记不可改不可撤 | FR-022 | HTTP 200;position=1;can_edit=false;can_cancel=false | HTTP 200 pos=1 edit=False cancel=False | ✅ |
| TC-E07 | 交易流程 | 标记交易失败后由队列下一位自动递补并再冻结 | FR-021/FR-029 | A=FAILED;B=IN_TRANSACTION;商品保持 FROZEN | A=FAILED B=IN_TRANSACTION product=FROZEN | ✅ |
| TC-E08 | 交易流程 | 交易失败者口令码立即失效 | FR-026 | HTTP 410 | HTTP 410 | ✅ |
| TC-E09 | 交易流程 | 非交易失败状态不可作废 | FR-030 | HTTP 409 | HTTP 409 | ✅ |
| TC-E10 | 交易流程 | 交易失败者可作废 | FR-030 | HTTP 200, status=VOIDED | HTTP 200 status=VOIDED | ✅ |
| TC-E11 | 交易流程 | 已作废订单不可再标记交易结果 | FR-027 | HTTP 409 | HTTP 409 | ✅ |
| TC-E12 | 交易流程 | 交易成功:商品下架并进入历史 | FR-028 | HTTP 200;order=SUCCESS;product=OFF_SHELF | HTTP 200 order=SUCCESS product=OFF_SHELF | ✅ |
| TC-E13 | 交易流程 | 交易成功后关联口令码全部失效 | FR-026 | HTTP 410 | HTTP 410 | ✅ |
| TC-E14 | 交易流程 | 交易失败且队列为空时商品恢复在售 | FR-029 | product.status=ON_SALE | product=ON_SALE | ✅ |
| TC-E15 | 交易流程 | 失败者重新排队:放回队尾并生成新口令码 | FR-030/TBD-03 | HTTP 200, status=REQUEUED, queue_position=1, 返回新 command_code | HTTP 200 status=REQUEUED pos=1 newcode=True | ✅ |
| TC-E16 | 交易流程 | 重新排队后新口令码可正常查询 | FR-030 | HTTP 200 | HTTP 200 | ✅ |
| TC-E17 | 交易流程 | 同一失败者最多重新排队 1 次 | FR-030 | HTTP 409 | HTTP 409 | ✅ |
| TC-E18 | 交易流程 | 已重新排队的订单不可再作废(仅 FAILED 可作废) | FR-030 | HTTP 409 | HTTP 409 | ✅ |
| TC-F01 | 历史记录 | 历史列表仅包含已下架商品 | FR-004 | 列表中包含「历史测试商品」,且全部为 OFF_SHELF | total=20 名称=['历史测试商品', '失败重排商品', '交易测试商品', '排队测试商品', '商品A'] | ✅ |
| TC-F02 | 历史记录 | 历史列表交易概览统计失败轮次与最终结果 | FR-004 | failed_rounds=1, final_result=SUCCESS, buyer_count=2 | {"failed_rounds": 1, "buyer_count": 2, "final_result": "SUCCESS", "final_result_label": "交易成功", "last_transaction_at": "2026-10-14 17:45:27"} | ✅ |
| TC-F03 | 历史记录 | 历史详情完整保留多轮交易记录(不可只留最终结果) | FR-031/NFR-015 | transactions 为 [FAILED, SUCCESS] 两条 | transactions=['FAILED', 'SUCCESS'] | ✅ |
| TC-F04 | 历史记录 | 历史商品买家联系电话脱敏展示 | FR-019/NFR-003 | 电话形如 138****0001(保留前3后4) | ['1380001', '1380002'] | ✅ |
| TC-F05 | 历史记录 | 未下架商品查看历史详情返回 409 | FR-005 | HTTP 409 | HTTP 409 | ✅ |
| TC-F06 | 意向列表 | 商品在售、订单未终结时卖家可见完整电话 | FR-019 | buyer_phone=13800007777(不脱敏) | 13800007777 | ✅ |
| TC-F07 | 意向列表 | 按状态筛选意向列表 | FR-017 | 全部返回项 status 均为 QUEUED | total=1 状态集={'QUEUED'} | ✅ |
| TC-F08 | 分页 | 分页参数 page=0 越界被拒 | 接口设计§1 | HTTP 422 | HTTP 422 | ✅ |
| TC-F09 | 分页 | 分页参数 page_size=0 越界被拒 | 接口设计§1 | HTTP 422 | HTTP 422 | ✅ |
| TC-F10 | 分页 | 分页参数 page_size=101 越界被拒 | 接口设计§1 | HTTP 422 | HTTP 422 | ✅ |
| TC-F11 | 分页 | 分页参数 page=-1 越界被拒 | 接口设计§1 | HTTP 422 | HTTP 422 | ✅ |
| TC-F12 | 操作日志 | 关键操作被记入操作日志 | FR-042 | 日志含 LOGIN / PUBLISH_PRODUCT / OFF_SHELF_PRODUCT 等 | ['CHANGE_PASSWORD', 'FREEZE_PRODUCT', 'LOGIN', 'OFF_SHELF_PRODUCT', 'PUBLISH_PRODUCT', 'REQUEUE_ORDER', 'START_TRANSACTION', 'TRANSACTION_FAILED'] | ✅ |
| TC-F13 | 操作日志 | 每条日志含可读中文描述与时间 | FR-042/NFR-010 | 所有日志项 detail 非空且 created_at 存在 | 共 85 条,detail 齐全=True | ✅ |
| TC-F14 | 历史记录 | 历史列表支持按名称关键词模糊搜索 | FR-004 | 搜索「历史」命中「历史测试商品」 | total=2 名称=['历史测试商品', '历史测试商品'] | ✅ |
| TC-F15 | 历史记录 | 关键词中的 LIKE 通配符应被转义(_ 不应匹配任意字符) | FR-004 | 搜索「_」应命中 0 条(无商品名含下划线) | 搜索「_」命中 20 条;不带关键词共 20 条 | ❌ |
| TC-G01 | CSV 导出 | 导出接口返回 CSV 且带 UTF-8 BOM | FR-018 | Content-Type 含 text/csv;内容以 BOM 开头 | Content-Type=text/csv; charset=utf-8 BOM=True | ✅ |
| TC-G02 | CSV 导出 | CSV 表头与数据行正确 | FR-018 | 表头=姓名,联系电话,提交时间,排队序号,状态;共 1+2 行 | 表头=姓名,联系电话,提交时间,排队序号,状态 行数=3 | ✅ |
| TC-G03 | CSV 导出 | 未登录不可导出 | NFR-006 | HTTP 401 | HTTP 401 | ✅ |
| TC-H01 | 图片上传 | 上传合法 PNG 成功并返回可访问 URL | FR-001 | HTTP 200 且 url 形如 /uploads/xxx.png | HTTP 200 url=/uploads/fb55e3f81e1646cfaaf2be7fcd8b20aa.png | ✅ |
| TC-H02 | 图片上传 | 已上传图片可经静态路由访问 | FR-001 | HTTP 200 | HTTP 200 Content-Type=image/png | ✅ |
| TC-H03 | 图片上传 | 非图片扩展名被拒绝 | NFR-012 | HTTP 400 INVALID_IMAGE_FORMAT | HTTP 400 INVALID_IMAGE_FORMAT | ✅ |
| TC-H04 | 图片上传 | 空文件被拒绝 | NFR-012 | HTTP 400 EMPTY_FILE | HTTP 400 EMPTY_FILE | ✅ |
| TC-H05 | 图片上传 | 超过 5MB 的图片被拒绝 | NFR-012 | HTTP 400 IMAGE_TOO_LARGE | HTTP 400 IMAGE_TOO_LARGE | ✅ |
| TC-H06 | 图片上传 | 大写扩展名按小写规则放行 | NFR-012 | HTTP 200 | HTTP 200 | ✅ |
| TC-H07 | 图片上传 | 无扩展名文件被拒绝 | NFR-012 | HTTP 400 | HTTP 400 | ✅ |
| TC-H08 | 图片上传 | 仅校验扩展名,不校验真实文件内容 | NFR-012 | 观察项:扩展名为 .png 即放行,即使内容不是图片 | HTTP 200 | ✅ |
| TC-I01 | 并发 | 20 个并发提交意向全部成功且无丢失 | NFR-013 | 成功 20 笔 | 成功 20 笔 | ✅ |
| TC-I02 | 并发 | 并发下排队序号唯一且连续(无重复、无空洞) | NFR-013/NFR-014 | 序号恰为 1..20 各一次 | 得到 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] | ✅ |
| TC-I03 | 并发 | 并发后不存在两个同时进行中的交易(防超卖) | NFR-013 | IN_TRANSACTION 数量为 0(尚未进入交易) | IN_TRANSACTION 数量=0 | ✅ |
| TC-I04 | 并发 | 连续进入交易仅第一次成功(先到先得不可干预) | FR-020/NFR-014 | 第一次 200,第二次 409 | 第一次=200 第二次=409 | ✅ |
| TC-K01 | 安全 | 历史搜索关键词中的 SQL 注入载荷不生效 | NFR-005 | HTTP 200 且返回 0 条(按字面匹配),无数据库错误 | HTTP 200 total=0 | ✅ |
| TC-K02 | 安全 | 静态资源路径不可穿越到后端源码 | NFR-005 | HTTP 404/403,且响应不含 SECRET_KEY 源码内容 | HTTP 404 含SECRET_KEY=False | ✅ |
| TC-K03 | 安全 | 口令码只能访问自身对应意向,不可越权 | NFR-004 | A 的查询结果中 order_id 等于 A,B 的等于 B,两者不同 | A=677e786b B=d7cea909 | ✅ |
| TC-K04 | 安全 | 含 HTML 标签的商品名按原文存储(转义责任在前端) | NFR-005 | 接口原样返回,前端 Vue 插值自动转义({{ }}) |
存储值=<script>alert(1)</script> | ✅ |
| TC-K05 | 健壮性 | 超长口令码返回 410 而非 500 | NFR-009 | HTTP 410 | HTTP 410 | ✅ |
| TC-K06 | 健壮性 | 缺少必需字段返回 422 而非 500 | NFR-009 | HTTP 422 | HTTP 422 | ✅ |
| TC-K07 | 健壮性 | 请求体为非法 JSON 时返回 422 而非 500 | NFR-009 | HTTP 422 | HTTP 422 | ✅ |
| TC-K08 | 健壮性 | 交易结果取值非法被拒 | NFR-009 | HTTP 422 | HTTP 422 | ✅ |
| TC-J01 | 前端 | 12 个前端路由均可访问(SPA 回退正常) | FR-051 | 全部返回 200 且包含挂载点 #app | 异常路由: 无 | ✅ |
| TC-J02 | 前端 | Vite 开发服务器正常编译入口模块 | FR-051 | HTTP 200 且 Content-Type 为 JavaScript | HTTP 200 CT=text/javascript | ✅ |
| TC-J03 | 前端 | Vue 单文件组件被正常编译 | FR-051 | HTTP 200 | HTTP 200 | ✅ |
| TC-J04 | 前端 | Vite 代理 /api 到后端成功(前后端联调打通) | FR-051 | HTTP 200 且返回后端 JSON(含 state 字段) | HTTP 200 body=b'{"state":"FROZEN","product":{"id":"29a0422d29774d5985b452aba' | ✅ |
| TC-J05 | 前端 | 卖家发布页组件可编译 | FR-051 | HTTP 200 | HTTP 200 | ✅ |
| 用例 | 关联缺陷 | 说明 |
|---|---|---|
| TC-C10 | BUG-001 | 价格 0.001 元(不足 1 分)触发 HTTP 500,应返回 422 |
| TC-F15 | BUG-002 | 关键词 _ 被当作 LIKE 通配符,返回全部历史商品而非 0 条 |
详细根因与修复建议见 BUG记录与汇总。
编写人:缪茂锦(测试岗) | 缺陷总数 2(中级 1、低级 1),观察项 10 项,无阻塞级缺陷。
| 统计维度 | 分类 | 数量 |
|---|---|---|
| 按严重级别 | 高(Critical/Blocker) | 0 |
| 按严重级别 | 中(Major) | 1 |
| 按严重级别 | 低(Minor) | 1 |
| 按状态 | 待修复 | 2 |
| 按模块 | 商品管理 | 1 |
| 按模块 | 历史记录 | 1 |
| 缺陷密度 | 每百条用例 | 1.8 |
| 项目 | 内容 |
|---|---|
| 所属模块 | 商品管理 / 发布商品 |
| 需求追溯 | FR-001、NFR-009 |
| 严重级别 / 优先级 | 中(Major) / 高 |
| 缺陷状态 | 待修复 |
| 发现方式 | 黑盒功能测试 TC-C10 |
| 发现日期 | 2026-10-07 |
| 复现概率 | 必现(100%) |
| 测试环境 | 后端 127.0.0.1:8000,SQLite,Python 3.12 + FastAPI 0.142.2 |
| 复现步骤 | 1) POST /api/seller/login,body={"username":"seller","password":"seller123"},取得 Bearer 令牌; 2) 确认当前无在售商品(否则先下架); 3) POST /api/seller/products,Header 带令牌,body={"name":"亚分价格","price":0.001}; 4) 观察响应状态码与响应体 |
| 预期结果 | HTTP 422,响应体符合接口文档统一错误结构:{"error":{"code":"VALIDATION_ERROR","message":"..."}} |
| 实际结果 | HTTP 500,响应体为纯文本 "Internal Server Error"(非统一错误结构) |
| 根因分析 | ① schemas.py 中 ProductCreateRequest.price = Field(gt=0, le=9999999),仅约束大于 0,未约束最小货币单位; ② utils.cents_from_price() 以 Decimal ROUND_HALF_UP 换算分,0.001 元 → 0 分; ③ schema.sql 中 product.price_cents 定义 CHECK (price_cents > 0); ④ product_service.publish_product() 直接执行 INSERT,未捕获 sqlite3.IntegrityError; ⑤ main.py 仅注册了 BusinessError 与 RequestValidationError 处理器,未兜底其他异常,异常穿透 ASGI 后被 Starlette 转为 500。 |
| 证据 | 后端日志堆栈: File "backend/app/routers/seller.py", line 67, in publish_product File "backend/app/services/product_service.py", line 29, in publish_product conn.execute( sqlite3.IntegrityError: CHECK constraint failed: price_cents > 0 |
| 可达性分析 | 正常 UI 操作不可达:PublishView.vue 使用 ,用户无法通过界面输入 0.001。但该接口为文档公开契约,直接调用 API(自动化脚本、Postman、第三方客户端)可稳定复现。 |
| 影响分析 | ① 接口契约被破坏,违反接口设计文档 §1「错误格式统一」约定;② 服务端出现未处理异常,属稳定性与防御性校验缺失;③ 前端在极端情况下只能显示通用失败提示,无有效错误信息。 |
| 修复建议 | 建议三处同时加固: ① 将校验前移到金额语义:ProductCreateRequest 使用 Decimal 并约束 ge=Decimal("0.01"); ② 在 publish_product() 中校验 cents_from_price(price) > 0,否则抛 BusinessError(message 如「价格不能低于 0.01 元」,status_code=422); ③ 在 main.py 增加通用 Exception 处理器,将未捕获异常统一转换为 {"error":{"code":"INTERNAL_ERROR",...}} 结构,避免 500 返回纯文本。 |
| 回归验证方法 | 重新执行 TC-C10,期望返回 HTTP 422 且响应体为统一错误结构;同时回归 TC-C01(正常价格发布成功)确保未被误伤。 |
| 项目 | 内容 |
|---|---|
| 所属模块 | 历史记录 / 商品搜索 |
| 需求追溯 | FR-004 |
| 严重级别 / 优先级 | 低(Minor) / 中 |
| 缺陷状态 | 待修复 |
| 发现方式 | 黑盒功能测试 TC-F15 |
| 发现日期 | 2026-10-07 |
| 复现概率 | 必现(100%) |
| 测试环境 | 后端 127.0.0.1:8000,SQLite,Python 3.12 + FastAPI 0.142.2 |
| 复现步骤 | 1) 以卖家令牌登录; 2) 确认历史中存在若干已下架商品; 3) GET /api/seller/history?keyword=_ ; 4) 对比 GET /api/seller/history 的 total 值 |
| 预期结果 | keyword 按字面字符串匹配。由于无任何商品名含下划线字符,应返回 total=0 |
| 实际结果 | 返回 total=7,等于不带关键词时的全部历史商品数,即「_」被当作 LIKE 单字符通配符 |
| 根因分析 | product_service.list_history() 中: where += " AND name LIKE ?" params.append(f"%{keyword}%") 未对用户输入中的 SQL LIKE 元字符 % 与 _ 做转义,也未使用 ESCAPE 子句。(查询本身是参数化的,因此不构成 SQL 注入,仅影响匹配语义。) |
| 证据 | TC-F15 实测:搜索「_」命中 7 条;不带关键词共 7 条;两值相等。 |
| 可达性分析 | 可通过前端历史商品页搜索框直接触发(输入 _ 或 % 后点击查询)。 |
| 影响分析 | 用户以含 % 或 _ 的关键词搜索时结果不准确(_ 匹配任一单字符、% 匹配任意长度);不含这两个字符的常规关键词不受影响,故影响面有限。 |
| 修复建议 | 对关键词做 LIKE 元字符转义后再拼接,并在 SQL 中显式声明转义符: safe = keyword.replace("\", "\\").replace("%", "\%").replace("", "\") where += " AND name LIKE ? ESCAPE '\'" params.append(f"%{safe}%") |
| 回归验证方法 | 重新执行 TC-F15,期望 keyword=_ 返回 total=0;回归 TC-F14(keyword=历史 正常命中)确保未被误伤。 |
| 编号 | 级别 | 模块 | 标题 | 说明 | 建议 |
|---|---|---|---|---|---|
| OBS-001 | 中 | 文件上传 | POST /api/files/images 上传接口未做鉴权 | 任意调用者无需令牌即可上传文件(单个最大 5MB),文件落盘后经 /uploads 公开可访问,存在被匿名大量上传占用磁盘的风险。 | 为该接口增加卖家令牌校验(Depends(get_current_seller)),并同步更新接口文档;如需保留公开能力,建议增加频率限制与总量配额。 |
| OBS-002 | 低 | 文件上传 | 上传仅校验扩展名,未校验文件真实类型 | 服务端只检查文件扩展名是否在白名单内,不读取文件头(Magic Number),可将任意内容命名为 .png 上传。 | 增加文件头魔数校验(PNG/JPEG/GIF/WEBP);当前风险较低,因为静态资源按扩展名下发 Content-Type: image/png,浏览器不会按脚本执行。 |
| OBS-003 | 低 | 商品管理 / 前端渲染 | 商品名按原文存储,接口不做 HTML 转义 | 商品名称可包含 HTML 标签并原样存储与返回,转义责任完全在前端渲染层。 | 保持「接口存原文、前端插值转义」的现有约定,并把它写入前端代码评审检查项;后续若引入富文本渲染需重新评估。 |
| OBS-004 | 提示 | 商品状态机 | 手动冻结与交易自动冻结在状态上不可区分 | 两种冻结原因在 product.status 上同为 FROZEN,接口未提供区分字段;前端只能通过「是否存在 IN_TRANSACTION 订单」间接推断应显示「手动解冻」还是「标记交易成功/失败」。 | 属实现与需求的表达力问题,当前功能正确(TC-C11~TC-C16 均通过)。建议在需求或接口上补充冻结来源字段,降低前端推断耦合。 |
| OBS-005 | 提示 | 口令码 | 口令码有效期规则未在需求/接口文档中明确 | 数据库 command_code.expires_at 字段存在,配置默认 30 天(SHOP_COMMAND_CODE_TTL_DAYS),但需求分析文档将其列入 TBD,接口设计文档也未描述过期行为。 | 需求侧补充确认口令码有效期时长与到期提示文案,接口文档补充 410 的过期场景说明。当前实现可用(TC-D05、TC-E08、TC-E13 均通过),属文档缺口。 |
| OBS-006 | 提示 | 隐私脱敏 | 交易失败买家电话立即脱敏,与卖家处置需求存在张力 | 订单进入 FAILED(属终结状态)后买家电话立即按 FR-019/NFR-003 脱敏,但卖家恰需据此决定「作废」还是「重新排队」;重新排队后(REQUEUED)才恢复明文。 | 建议明确「失败待处置订单」的展示规则(例如处置前保持明文、处置后脱敏),在需求文档中补充说明。 |
| OBS-007 | 文档 | 需求文档一致性 | 需求分析文档存在重复条目与图文不一致 | ① FR-001 与 FR-036(发布商品)、FR-035 与 FR-041(修改密码)内容完全重复;② 订单状态机图缺少「排队中→已作废」边,而状态表列出了该迁移;③ 商品状态机图触发条件为「交易失败且队列为空」,状态表为「手动解冻或交易失败」;④ 术语「再售/在售」混用。 | 由设计岗(周育民)统一去重并修正图与表的一致性,避免用例追溯出现一对多歧义。 |
| OBS-008 | 文档 | 接口文档完整性 | 接口文档章节覆盖不完整 | 接口设计文档 §2 总览表列出 24 条接口,但 §3~§6 仅详细展开 #2~#24,GET /(服务信息)没有详细章节;§5.14「作废」未写明成功响应体结构。 | 补齐 GET / 的响应结构说明与作废接口的成功响应示例。 |
| OBS-009 | 文档 | 页面原型文档一致性 | 侧边栏项数描述与实际不符 | 页面原型设计文档称卖家后台侧边栏「固定 5 项」,但同处及示意图均列出 6 项(工作台、发布商品、意向购买人、历史商品、修改密码、操作日志)。 | 修正文档表述为 6 项。 |
| OBS-010 | 提示 | 分页 | 分页页码超出总页数时返回空列表且无提示 | GET /api/seller/history?page=999 返回 HTTP 200 与空 items,未做友好提示。 | 属可接受行为(REST 常规语义)。建议前端在结果为空且 total>0 时提示用户返回首页。 |
| OBS-011 | 中 | 卖家账号 | 需求要求新密码不少于 8 位,后端实现仅要求 6 位 | 需求 FR-035 / FR-041 明确「新密码长度不少于 8 位」;后端 schemas.py 中 ChangePasswordRequest.new_password 仅约束 min_length=6。长度为 6~7 位的新密码会被后端接受,违反需求。 | 将后端校验收紧为 min_length=8,并同步前端改密页的校验提示;补充「6~7 位新密码应被拒绝」的测试用例。 |
| OBS-012 | 中 | 文件上传 | 需求要求图片仅支持 JPG/PNG,后端实现额外允许 GIF/WebP | 需求 FR-001 / FR-036 与 NFR-012 明确「仅支持 JPG/PNG 格式,单张 ≤5MB」;后端 config.py 的 ALLOWED_IMAGE_EXT 为 {.jpg, .jpeg, .png, .gif, .webp},gif 与 webp 会被接受,超出需求范围。 | 若确认仅需 JPG/PNG,将白名单收紧为 {.jpg, .jpeg, .png};若需保留 GIF/WebP,则应修订需求。二者取一,避免需求与实现长期不一致。 |
| OBS-013 | 中 | 历史记录 | 需求要求历史列表可筛选,接口仅实现关键词检索 | 需求 FR-004 / FR-037 要求历史商品列表「可分页、可检索、可筛选」;后端 GET /api/seller/history 仅提供 keyword 模糊检索与分页参数,无状态筛选参数,前端也无筛选控件。 | 为历史列表补充 status 筛选参数并在前端增加筛选控件;或将需求修订为「可分页、可检索」。 |
说明:观察项不构成功能缺陷,但影响系统稳健性、安全性或文档一致性,建议纳入后续迭代。
编写人:缪茂锦(测试岗) | 缺陷总数 2(中级 1、低级 1),观察项 10 项,无阻塞级缺陷。
| 统计维度 | 分类 | 数量 |
|---|---|---|
| 按严重级别 | 高(Critical/Blocker) | 0 |
| 按严重级别 | 中(Major) | 1 |
| 按严重级别 | 低(Minor) | 1 |
| 按状态 | 待修复 | 2 |
| 按模块 | 商品管理 | 1 |
| 按模块 | 历史记录 | 1 |
| 缺陷密度 | 每百条用例 | 1.8 |
| 项目 | 内容 |
|---|---|
| 所属模块 | 商品管理 / 发布商品 |
| 需求追溯 | FR-001、NFR-009 |
| 严重级别 / 优先级 | 中(Major) / 高 |
| 缺陷状态 | 待修复 |
| 发现方式 | 黑盒功能测试 TC-C10 |
| 发现日期 | 2026-10-07 |
| 复现概率 | 必现(100%) |
| 测试环境 | 后端 127.0.0.1:8000,SQLite,Python 3.12 + FastAPI 0.142.2 |
| 复现步骤 | 1) POST /api/seller/login,body={"username":"seller","password":"seller123"},取得 Bearer 令牌; 2) 确认当前无在售商品(否则先下架); 3) POST /api/seller/products,Header 带令牌,body={"name":"亚分价格","price":0.001}; 4) 观察响应状态码与响应体 |
| 预期结果 | HTTP 422,响应体符合接口文档统一错误结构:{"error":{"code":"VALIDATION_ERROR","message":"..."}} |
| 实际结果 | HTTP 500,响应体为纯文本 "Internal Server Error"(非统一错误结构) |
| 根因分析 | ① schemas.py 中 ProductCreateRequest.price = Field(gt=0, le=9999999),仅约束大于 0,未约束最小货币单位; ② utils.cents_from_price() 以 Decimal ROUND_HALF_UP 换算分,0.001 元 → 0 分; ③ schema.sql 中 product.price_cents 定义 CHECK (price_cents > 0); ④ product_service.publish_product() 直接执行 INSERT,未捕获 sqlite3.IntegrityError; ⑤ main.py 仅注册了 BusinessError 与 RequestValidationError 处理器,未兜底其他异常,异常穿透 ASGI 后被 Starlette 转为 500。 |
| 证据 | 后端日志堆栈: File "backend/app/routers/seller.py", line 67, in publish_product File "backend/app/services/product_service.py", line 29, in publish_product conn.execute( sqlite3.IntegrityError: CHECK constraint failed: price_cents > 0 |
| 可达性分析 | 正常 UI 操作不可达:PublishView.vue 使用 ,用户无法通过界面输入 0.001。但该接口为文档公开契约,直接调用 API(自动化脚本、Postman、第三方客户端)可稳定复现。 |
| 影响分析 | ① 接口契约被破坏,违反接口设计文档 §1「错误格式统一」约定;② 服务端出现未处理异常,属稳定性与防御性校验缺失;③ 前端在极端情况下只能显示通用失败提示,无有效错误信息。 |
| 修复建议 | 建议三处同时加固: ① 将校验前移到金额语义:ProductCreateRequest 使用 Decimal 并约束 ge=Decimal("0.01"); ② 在 publish_product() 中校验 cents_from_price(price) > 0,否则抛 BusinessError(message 如「价格不能低于 0.01 元」,status_code=422); ③ 在 main.py 增加通用 Exception 处理器,将未捕获异常统一转换为 {"error":{"code":"INTERNAL_ERROR",...}} 结构,避免 500 返回纯文本。 |
| 回归验证方法 | 重新执行 TC-C10,期望返回 HTTP 422 且响应体为统一错误结构;同时回归 TC-C01(正常价格发布成功)确保未被误伤。 |
| 项目 | 内容 |
|---|---|
| 所属模块 | 历史记录 / 商品搜索 |
| 需求追溯 | FR-004 |
| 严重级别 / 优先级 | 低(Minor) / 中 |
| 缺陷状态 | 待修复 |
| 发现方式 | 黑盒功能测试 TC-F15 |
| 发现日期 | 2026-10-07 |
| 复现概率 | 必现(100%) |
| 测试环境 | 后端 127.0.0.1:8000,SQLite,Python 3.12 + FastAPI 0.142.2 |
| 复现步骤 | 1) 以卖家令牌登录; 2) 确认历史中存在若干已下架商品; 3) GET /api/seller/history?keyword=_ ; 4) 对比 GET /api/seller/history 的 total 值 |
| 预期结果 | keyword 按字面字符串匹配。由于无任何商品名含下划线字符,应返回 total=0 |
| 实际结果 | 返回 total=7,等于不带关键词时的全部历史商品数,即「_」被当作 LIKE 单字符通配符 |
| 根因分析 | product_service.list_history() 中: where += " AND name LIKE ?" params.append(f"%{keyword}%") 未对用户输入中的 SQL LIKE 元字符 % 与 _ 做转义,也未使用 ESCAPE 子句。(查询本身是参数化的,因此不构成 SQL 注入,仅影响匹配语义。) |
| 证据 | TC-F15 实测:搜索「_」命中 7 条;不带关键词共 7 条;两值相等。 |
| 可达性分析 | 可通过前端历史商品页搜索框直接触发(输入 _ 或 % 后点击查询)。 |
| 影响分析 | 用户以含 % 或 _ 的关键词搜索时结果不准确(_ 匹配任一单字符、% 匹配任意长度);不含这两个字符的常规关键词不受影响,故影响面有限。 |
| 修复建议 | 对关键词做 LIKE 元字符转义后再拼接,并在 SQL 中显式声明转义符: safe = keyword.replace("\", "\\").replace("%", "\%").replace("", "\") where += " AND name LIKE ? ESCAPE '\'" params.append(f"%{safe}%") |
| 回归验证方法 | 重新执行 TC-F15,期望 keyword=_ 返回 total=0;回归 TC-F14(keyword=历史 正常命中)确保未被误伤。 |
| 编号 | 级别 | 模块 | 标题 | 说明 | 建议 |
|---|---|---|---|---|---|
| OBS-001 | 中 | 文件上传 | POST /api/files/images 上传接口未做鉴权 | 任意调用者无需令牌即可上传文件(单个最大 5MB),文件落盘后经 /uploads 公开可访问,存在被匿名大量上传占用磁盘的风险。 | 为该接口增加卖家令牌校验(Depends(get_current_seller)),并同步更新接口文档;如需保留公开能力,建议增加频率限制与总量配额。 |
| OBS-002 | 低 | 文件上传 | 上传仅校验扩展名,未校验文件真实类型 | 服务端只检查文件扩展名是否在白名单内,不读取文件头(Magic Number),可将任意内容命名为 .png 上传。 | 增加文件头魔数校验(PNG/JPEG/GIF/WEBP);当前风险较低,因为静态资源按扩展名下发 Content-Type: image/png,浏览器不会按脚本执行。 |
| OBS-003 | 低 | 商品管理 / 前端渲染 | 商品名按原文存储,接口不做 HTML 转义 | 商品名称可包含 HTML 标签并原样存储与返回,转义责任完全在前端渲染层。 | 保持「接口存原文、前端插值转义」的现有约定,并把它写入前端代码评审检查项;后续若引入富文本渲染需重新评估。 |
| OBS-004 | 提示 | 商品状态机 | 手动冻结与交易自动冻结在状态上不可区分 | 两种冻结原因在 product.status 上同为 FROZEN,接口未提供区分字段;前端只能通过「是否存在 IN_TRANSACTION 订单」间接推断应显示「手动解冻」还是「标记交易成功/失败」。 | 属实现与需求的表达力问题,当前功能正确(TC-C11~TC-C16 均通过)。建议在需求或接口上补充冻结来源字段,降低前端推断耦合。 |
| OBS-005 | 提示 | 口令码 | 口令码有效期规则未在需求/接口文档中明确 | 数据库 command_code.expires_at 字段存在,配置默认 30 天(SHOP_COMMAND_CODE_TTL_DAYS),但需求分析文档将其列入 TBD,接口设计文档也未描述过期行为。 | 需求侧补充确认口令码有效期时长与到期提示文案,接口文档补充 410 的过期场景说明。当前实现可用(TC-D05、TC-E08、TC-E13 均通过),属文档缺口。 |
| OBS-006 | 提示 | 隐私脱敏 | 交易失败买家电话立即脱敏,与卖家处置需求存在张力 | 订单进入 FAILED(属终结状态)后买家电话立即按 FR-019/NFR-003 脱敏,但卖家恰需据此决定「作废」还是「重新排队」;重新排队后(REQUEUED)才恢复明文。 | 建议明确「失败待处置订单」的展示规则(例如处置前保持明文、处置后脱敏),在需求文档中补充说明。 |
| OBS-007 | 文档 | 需求文档一致性 | 需求分析文档存在重复条目与图文不一致 | ① FR-001 与 FR-036(发布商品)、FR-035 与 FR-041(修改密码)内容完全重复;② 订单状态机图缺少「排队中→已作废」边,而状态表列出了该迁移;③ 商品状态机图触发条件为「交易失败且队列为空」,状态表为「手动解冻或交易失败」;④ 术语「再售/在售」混用。 | 由设计岗(周育民)统一去重并修正图与表的一致性,避免用例追溯出现一对多歧义。 |
| OBS-008 | 文档 | 接口文档完整性 | 接口文档章节覆盖不完整 | 接口设计文档 §2 总览表列出 24 条接口,但 §3~§6 仅详细展开 #2~#24,GET /(服务信息)没有详细章节;§5.14「作废」未写明成功响应体结构。 | 补齐 GET / 的响应结构说明与作废接口的成功响应示例。 |
| OBS-009 | 文档 | 页面原型文档一致性 | 侧边栏项数描述与实际不符 | 页面原型设计文档称卖家后台侧边栏「固定 5 项」,但同处及示意图均列出 6 项(工作台、发布商品、意向购买人、历史商品、修改密码、操作日志)。 | 修正文档表述为 6 项。 |
| OBS-010 | 提示 | 分页 | 分页页码超出总页数时返回空列表且无提示 | GET /api/seller/history?page=999 返回 HTTP 200 与空 items,未做友好提示。 | 属可接受行为(REST 常规语义)。建议前端在结果为空且 total>0 时提示用户返回首页。 |
| OBS-011 | 中 | 卖家账号 | 需求要求新密码不少于 8 位,后端实现仅要求 6 位 | 需求 FR-035 / FR-041 明确「新密码长度不少于 8 位」;后端 schemas.py 中 ChangePasswordRequest.new_password 仅约束 min_length=6。长度为 6~7 位的新密码会被后端接受,违反需求。 | 将后端校验收紧为 min_length=8,并同步前端改密页的校验提示;补充「6~7 位新密码应被拒绝」的测试用例。 |
| OBS-012 | 中 | 文件上传 | 需求要求图片仅支持 JPG/PNG,后端实现额外允许 GIF/WebP | 需求 FR-001 / FR-036 与 NFR-012 明确「仅支持 JPG/PNG 格式,单张 ≤5MB」;后端 config.py 的 ALLOWED_IMAGE_EXT 为 {.jpg, .jpeg, .png, .gif, .webp},gif 与 webp 会被接受,超出需求范围。 | 若确认仅需 JPG/PNG,将白名单收紧为 {.jpg, .jpeg, .png};若需保留 GIF/WebP,则应修订需求。二者取一,避免需求与实现长期不一致。 |
| OBS-013 | 中 | 历史记录 | 需求要求历史列表可筛选,接口仅实现关键词检索 | 需求 FR-004 / FR-037 要求历史商品列表「可分页、可检索、可筛选」;后端 GET /api/seller/history 仅提供 keyword 模糊检索与分页参数,无状态筛选参数,前端也无筛选控件。 | 为历史列表补充 status 筛选参数并在前端增加筛选控件;或将需求修订为「可分页、可检索」。 |
说明:观察项不构成功能缺陷,但影响系统稳健性、安全性或文档一致性,建议纳入后续迭代。
编写人:缪茂锦(测试岗) | 报告类型:阶段性功能测试报告 | 日期:2026-10-14 适用迭代:第 1 次迭代(2026-09-24 ~ 2026-10-08) | 对应评审:第 1 次阶段性评审(2026-10-08) 完整版见仓库
docs/测试/05-测试报告.docx。
本报告说明《在线购物系统(单卖家版)》本轮功能测试的执行情况、测试结论与遗留问题, 为项目组判断当前版本质量水平、安排缺陷修复优先级、以及阶段性评审与项目验收提供客观依据。
测试对象:single-merchant-shop-9 仓库当前主分支的前后端完整实现。 测试方式:黑盒功能测试(自动化套件对运行中的服务发起真实请求)+ 前端可用性校验 + 代码走查。
| 序号 | 依据文档 | 用途 |
|---|---|---|
| 1 | 需求规格说明书(V2.0) | FR-001 |
| 2 | docs/03-接口设计文档.md | 24 个接口的方法、路径、参数、响应结构与错误码约定 |
| 3 | docs/05-页面原型设计.md | 12 个页面的路由、元素、交互与空/异常状态设计 |
| 4 | docs/02-数据库设计文档.md | 表结构、约束与索引 |
| 5 | 商品状态图 / 订单状态机图 / 口令码周期图 | 状态迁移的图形依据 |
| 项目 | 配置 |
|---|---|
| 后端 | Python 3.12.10 + FastAPI 0.142.2 + Uvicorn 0.54.0 + SQLite(WAL) |
| 前端 | Node.js ≥ 22 + Vue 3.5.43 + Vite 8.3.3 + Element Plus 2.14.7 |
| 服务地址 | 后端 127.0.0.1:8000 前端 127.0.0.1:5173 |
| 数据 | 每轮测试前重建独立临时 SQLite 库,保证结果可重复 |
| 指标 | 数值 | 说明 |
|---|---|---|
| 用例总数 | 113 | 全部为黑盒功能测试用例 |
| 通过 | 111 | 实际结果与预期一致 |
| 失败 | 2 | 已登记缺陷 |
| 通过率 | 98.2% | 通过 / 总数 |
| 阻塞用例 | 0 | 无用例因环境问题无法执行 |
| 确认缺陷 | 2 | BUG-001(中)、BUG-002(低) |
| 观察项 | 10 | OBS-001 ~ OBS-010 |
仓库自带 backend/tests 共 28 条用例,执行结果 28 passed,另有 1 条依赖废弃告警
(StarletteDeprecationWarning:使用 httpx 配合 starlette.testclient 已废弃)。
| 检查项 | 结果 | 说明 |
|---|---|---|
| 12 条前端路由可访问性 | 通过 | 全部 HTTP 200 且含挂载点 #app,SPA 回退正常 |
| Vite 模块编译 | 通过 | 入口模块、App.vue、页面组件均可编译 |
| 前后端代理联调 | 通过 | 经 Vite 代理访问 /api/products/current 正常返回后端数据 |
| 状态驱动渲染 | 通过(走查) | 买家端按 NONE/ON_SALE/FROZEN 分支;卖家端按商品状态分支 |
| 危险操作二次确认 | 通过(走查) | 下架、撤销、作废均实现确认对话框 |
| 隐私告知文案 | 通过(走查) | 买家填写页展示用途与脱敏告知 |
| 前端环境依赖 | 存在问题 | Node v20 下安装 [email protected] 报 EBADENGINE(要求 node>=22) |
| 统计维度 | 分类 | 数量 |
|---|---|---|
| 严重级别 | 高 / 中 / 低 | 0 / 1 / 1 |
| 状态 | 待修复 / 已关闭 | 2 / 0 |
| 模块 | 商品管理 / 历史记录 | 1 / 1 |
缺陷分布说明:本轮无阻塞级缺陷;中级缺陷位于商品发布的金额边界处理, 低级缺陷位于历史商品搜索的参数转义。两者均不涉及核心交易状态机与排队逻辑。
详见 BUG记录与汇总。
- 整体质量:黑盒功能测试 113 条,通过 111 条,失败 2 条,通过率 98.2%; 开发自测 pytest 28 条全部通过。整体质量良好。
- 核心业务:三类状态机的合法迁移全部可达、非法迁移均被正确拒绝; 先到先得排队在 20 线程并发下仍保持序号唯一连续; 口令码在交易成功、交易失败、商品下架、买家撤销四种情形下均正确失效。 核心交易主流程未发现功能缺陷。
- 安全性:鉴权覆盖、越权访问、SQL 注入、路径穿越、口令哈希与随机性等检查项均通过; 发现 1 项安全性改进点(上传接口未鉴权)。
- 上线判断:当前版本尚不满足上线条件,需完成下述整改。
| 优先级 | 整改事项 | 关联项 |
|---|---|---|
| P0 | 修复 BUG-001:为发布商品接口补充分值下限校验并增加通用异常处理器,使客户端输入错误不再返回 500 | BUG-001 |
| P1 | 修复 BUG-002:转义历史搜索关键词中的 SQL LIKE 元字符 | BUG-002 |
| P1 | 为文件上传接口 POST /api/files/images 增加鉴权 |
OBS-001 |
| P2 | 补充数据库备份与恢复方案说明;完善 README(环境要求、启动步骤、Node 版本要求) | 合规检查项 |
| P3 | 统一修订需求与设计文档中的不一致与缺失章节 | OBS-007/008/009 |
| P3 | 补充文件头校验、分页兜底提示等改进项 | OBS-002/010 |
- 未覆盖:性能压测与长时间稳定性(NFR-007 无法在课程环境验证)、多浏览器兼容矩阵、 部署层安全(HTTPS/CSRF)、TBD 项功能;
- 后续建议:缺陷修复后执行回归并覆盖相邻功能;补充前端交互层端到端自动化; 引入接口契约测试避免文档与实现漂移;建立缺陷复测记录形成完整生命周期闭环。
编写人:缪茂锦(测试岗) | 共 47 项验收点,通过 45 项。
| 序号 | 验收模块 | 验收点 | 需求追溯 | 验收标准 | 执行用例 | 结果 | 结论 |
|---|---|---|---|---|---|---|---|
| 1 | 买家端商品浏览 | 无商品在售时展示空状态,不展示任何商品信息 | FR-043/FR-044 | state=NONE 且 product=null | TC-A01 | ✅ 通过 | 验收通过 |
| 2 | 买家端商品浏览 | 在售商品展示名称、价格、描述、主图与购买入口 | FR-045 | state=ON_SALE 且商品字段完整 | TC-A02/TC-A03 | ✅ 通过 | 验收通过 |
| 3 | 买家端商品浏览 | 商品交易中展示冻结提示且禁止购买 | FR-046 | state=FROZEN 且 message=商品交易中 | TC-A04 | ✅ 通过 | 验收通过 |
| 4 | 买家端商品浏览 | 已下架商品对外不可见 | FR-003 | HTTP 404 | TC-A05 | ✅ 通过 | 验收通过 |
| 5 | 卖家后台 | 固定账号可登录,错误密码有明确提示 | FR-032 | 正确 200 下发令牌;错误 401 | TC-B01/TC-B02/TC-B03 | ✅ 通过 | 验收通过 |
| 6 | 卖家后台 | 不提供注册流程 | FR-033 | HTTP 404 | TC-B06 | ✅ 通过 | 验收通过 |
| 7 | 卖家后台 | 受保护接口需有效令牌 | NFR-006 | HTTP 401 | TC-B04/TC-B05 | ✅ 通过 | 验收通过 |
| 8 | 卖家后台 | 修改密码需校验旧密码、新密码不少于 8 位,改后旧密码失效 | FR-035/FR-041 | 旧密码 401、新密码 200 | TC-B07/TC-B08/TC-B09 | ✅ 通过 | 验收通过 |
| 9 | 商品管理 | 可发布商品,名称与价格为必填 | FR-001/FR-036 | 合法 200;非法 422 | TC-C01/TC-C03~TC-C09 | ✅ 通过 | 验收通过 |
| 10 | 商品管理 | 同一时间只允许一件商品在售 | FR-008 | HTTP 409 | TC-C02 | ✅ 通过 | 验收通过 |
| 11 | 商品管理 | 价格不足最小货币单位时优雅拒绝(非 500) | FR-001/NFR-009 | HTTP 422 | TC-C10 | ❌ 未通过 | 验收不通过(BUG-001) |
| 12 | 商品管理 | 商品发布后不可编辑 | FR-002 | 接口不存在(404/405) | TC-C21 | ✅ 通过 | 验收通过 |
| 13 | 商品管理 | 手动冻结后不再接收新意向 | FR-009/FR-012/FR-016 | HTTP 409 且提示交易中 | TC-C11/TC-C13 | ✅ 通过 | 验收通过 |
| 14 | 商品管理 | 手动解冻恢复在售,不直接进入交易 | FR-010 | status=ON_SALE,可继续收意向 | TC-C14/TC-C15 | ✅ 通过 | 验收通过 |
| 15 | 商品管理 | 存在进行中交易时禁止手动解冻 | FR-010/FR-011 | HTTP 409 | TC-C16 | ✅ 通过 | 验收通过 |
| 16 | 商品管理 | 下架后关联口令码全部失效、不接收新意向 | FR-003/FR-026 | 口令码 410;提交意向 409 | TC-C17~TC-C19 | ✅ 通过 | 验收通过 |
| 17 | 排队机制 | 提交意向生成唯一口令码并分配排队序号 | FR-013/FR-014 | 返回非空口令码与序号 1 | TC-D01 | ✅ 通过 | 验收通过 |
| 18 | 排队机制 | 口令码长度与字符集满足不可猜测要求 | NFR-004 | 10 位,去除易混淆字符 0/O/1/I/l | TC-D02 | ✅ 通过 | 验收通过 |
| 19 | 排队机制 | 严格按提交时间先到先得 | FR-015/NFR-014 | 序号依次 1..5 | TC-D03 | ✅ 通过 | 验收通过 |
| 20 | 排队机制 | 卖家无法手动调整队列或挑选买家 | FR-020 | 无调序/选人接口与控件 | 用例设计核对 | ✅ 通过 | 验收通过 |
| 21 | 口令码 | 凭口令码可查询意向状态与位次 | FR-022 | HTTP 200 且字段正确 | TC-D04/TC-E06 | ✅ 通过 | 验收通过 |
| 22 | 口令码 | 口令码无效/失效时返回 410 | FR-026/NFR-005 | HTTP 410 | TC-D05/TC-D08/TC-E08/TC-E13 | ✅ 通过 | 验收通过 |
| 23 | 口令码 | 排队中可修改信息且位次不变 | FR-023 | 字段更新、位次仍为 1 | TC-D06/TC-D07 | ✅ 通过 | 验收通过 |
| 24 | 口令码 | 撤销排队后自动补位 | FR-024 | 后续序号连续无空洞 | TC-D08/TC-D09 | ✅ 通过 | 验收通过 |
| 25 | 交易流程 | 与队首进入交易,商品自动冻结、队列封口 | FR-011 | order=IN_TRANSACTION;product=FROZEN | TC-E02 | ✅ 通过 | 验收通过 |
| 26 | 交易流程 | 进入交易后买家不可修改、不可撤销 | FR-025 | 均返回 409,can_edit/can_cancel=false | TC-E04/TC-E05/TC-E06 | ✅ 通过 | 验收通过 |
| 27 | 交易流程 | 交易成功:商品下架、口令码全失效、进入历史 | FR-028/FR-031 | product=OFF_SHELF;口令码 410;历史可见 | TC-E12/TC-E13/TC-F01 | ✅ 通过 | 验收通过 |
| 28 | 交易流程 | 交易失败:有人则自动递补并再冻结 | FR-021/FR-029 | 下一位转 IN_TRANSACTION;商品仍 FROZEN | TC-E07 | ✅ 通过 | 验收通过 |
| 29 | 交易流程 | 交易失败且队列为空时恢复在售 | FR-029 | product=ON_SALE | TC-E14 | ✅ 通过 | 验收通过 |
| 30 | 交易流程 | 失败者可作废或重新排队,仅影响本人 | FR-030 | 状态转 VOIDED / REQUEUED,队列尾插入 | TC-E09~TC-E11/TC-E15 | ✅ 通过 | 验收通过 |
| 31 | 交易流程 | 重新排队最多 1 次 | FR-030 | 首次 200,再次 409 | TC-E17 | ✅ 通过 | 验收通过 |
| 32 | 历史记录 | 历史列表仅含已下架商品,支持分页与搜索 | FR-004 | 全部 OFF_SHELF;关键词命中 | TC-F01/TC-F14 | ✅ 通过 | 验收通过 |
| 33 | 历史记录 | 搜索关键词中的通配符被正确转义 | FR-004 | 按字面匹配,_ 应命中 0 条 | TC-F15 | ❌ 未通过 | 验收不通过(BUG-002) |
| 34 | 历史记录 | 历史详情完整保留多轮交易记录 | FR-031/NFR-015 | transactions 含两条且顺序正确 | TC-F03 | ✅ 通过 | 验收通过 |
| 35 | 历史记录 | 订单生成时保存商品快照 | FR-007 | 订单含 product_snapshot 全字段 | TC-D01/接口核对 | ✅ 通过 | 验收通过 |
| 36 | 隐私保护 | 交易结束后买家电话脱敏展示 | FR-019/NFR-003 | 形如 138****0001 | TC-F04/TC-F06 | ✅ 通过 | 验收通过 |
| 37 | 意向管理 | 意向列表支持按状态筛选与分页 | FR-017/FR-039 | 返回项状态一致、分页字段正确 | TC-F07~TC-F11 | ✅ 通过 | 验收通过 |
| 38 | 意向管理 | 意向购买人可导出 CSV | FR-018 | text/csv + BOM,表头与数据行正确 | TC-G01/TC-G02/TC-G03 | ✅ 通过 | 验收通过 |
| 39 | 文件上传 | 商品主图仅支持 JPG/PNG 且单张不超过 5MB | FR-001/NFR-012 | 合法 200;非法格式/超限/空文件 400 | TC-H01/TC-H03~TC-H07 | ✅ 通过 | 验收通过 |
| 40 | 审计日志 | 卖家关键操作可追溯 | FR-042/NFR-010 | 含登录、发布、下架等动作与中文描述 | TC-F12/TC-F13 | ✅ 通过 | 验收通过 |
| 41 | 并发一致性 | 并发提交不丢失、排队序号唯一连续 | NFR-013/NFR-014 | 20 笔全部成功,序号恰为 1..20 | TC-I01/TC-I02 | ✅ 通过 | 验收通过 |
| 42 | 并发一致性 | 并发下不出现双份进行中交易(防超卖) | NFR-013 | 同时仅一笔 IN_TRANSACTION | TC-I03/TC-I04 | ✅ 通过 | 验收通过 |
| 43 | 安全性 | SQL 注入载荷不生效 | NFR-005 | 按字面匹配,无异常 | TC-K01 | ✅ 通过 | 验收通过 |
| 44 | 安全性 | 静态资源目录不可穿越读取后端源码 | NFR-005 | HTTP 403/404 | TC-K02 | ✅ 通过 | 验收通过 |
| 45 | 安全性 | 口令码不可越权访问他人意向 | NFR-004 | 各自仅能查到自身订单 | TC-K03 | ✅ 通过 | 验收通过 |
| 46 | 健壮性 | 异常输入返回统一 4xx 而非 500 | NFR-009 | 均返回 4xx | TC-K05~TC-K08 | ✅ 通过 | 验收通过 |
| 47 | 前后端联调 | 前端页面可访问且与后端联通 | FR-051/FR-052 | 全部 HTTP 200,代理返回后端数据 | TC-J01~TC-J05 | ✅ 通过 | 验收通过 |
功能验收共 47 项,通过 45 项,不通过 2 项,通过率 95.7%。 两处不通过分别对应缺陷 BUG-001(价格边界导致 500)与 BUG-002(搜索通配符未转义), 均不影响核心交易主流程。建议修复后回归复验,其余功能可判定为验收通过。
(评审后填写)