You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

7.8 KiB

Q01 岗位搜索真实查询覆盖设计

1. 状态

设计已确认,待实施、专项测试、真实 MySQL 复验与独立验收。

2. 背景与问题

JobSearchSpecification 承担公开岗位搜索的核心过滤规则,但当前 JobSearchServiceTest 将 JobRepository.findAll(Specification, Pageable) Mock 掉后直接返回预设数据。这些测试可以证明 参数校验、分页对象和 DTO 组装,却不能证明 Criteria 谓词、SQL 过滤、排序和分页真的正确。

Q01 的目标是补齐真实数据库证据,不以测试数量替代查询行为覆盖,也不改变现有公网接口和业务语义。

3. 范围

3.1 必须覆盖

  • JobSearchSpecification.searchJobs(...) 的真实 Repository 查询;
  • JobService.searchJobs(...) 的 latest、relevance、salary 排序和分页;
  • PUBLISHED 强制过滤;
  • keyword 对 title、description、requirements 的匹配;
  • city、employmentType、companyId;
  • salaryMin、salaryMax 的区间重叠语义;
  • H2 快速测试与 MySQL 8 行为一致性;
  • 查询参数化和特殊字符边界;
  • 真实 SQL/EXPLAIN 证据及数据副作用检查。

3.2 明确不做

  • 不改变 /jobs/search 请求或响应;
  • 不新增、删除或重命名公网接口;
  • 不修改岗位状态机、岗位归属或角色权限;
  • 不把 relevance 改造成新的全文检索算法;当前 relevance 与 latest 等价,本轮锁定该语义;
  • 不接入 Elasticsearch、搜索引擎或缓存;
  • 不新增 Flyway、表或索引;
  • 不改 Candidate Scoring、推荐、排名、邀约、Offer、申诉、消息、简历和认证;
  • 不删除现有 Mock 测试。现有测试继续承担 Service 编排与参数校验职责;
  • 不进入 R01 性能和结构重构。

4. 当前查询契约

参数 既有语义
status 无论调用方传什么条件,只返回 PUBLISHED
keyword trim 后忽略大小写,匹配 title、description、requirements 任一字段
city trim 后忽略大小写精确匹配;显式归一化以保证 H2/MySQL 一致
employmentType trim 后忽略大小写精确匹配;不做前缀匹配
salaryMin 岗位 salaryMax >= salaryMin
salaryMax 岗位 salaryMin <= salaryMax
companyId 精确匹配
sort=latest/null createdAt DESC, id DESC
sort=relevance 当前与 latest 相同,不改变
sort=salary salaryMax 非空优先、降序;相同值按 id DESC;NULL 最后
page 从 0 开始,不得为负数
size 1–50

薪资过滤是区间重叠,不是“岗位上下限都完全落在用户区间内”。边界相等必须命中。

5. 测试分层

5.1 H2 Repository 快速层

新增 JobSearchSpecificationIntegrationTest:

  • 使用 Spring Data JPA、真实 JobRepository 和真实 H2;
  • 激活 integration-test,由真实 Flyway 建表;
  • 不 Mock Repository、EntityManager、Specification、CriteriaBuilder 或事务;
  • 每个用例创建最小隔离夹具,按精确主键清理;
  • 直接执行 jobRepository.findAll(spec, pageable);
  • 断言返回 ID 集合与顺序,不能只断言数量。

至少覆盖:

  1. title 关键词;
  2. description 关键词;
  3. requirements 关键词;
  4. 关键词忽略大小写;
  5. null、空字符串、纯空白关键词;
  6. keyword 前后空格;
  7. city 精确匹配与 trim;
  8. employmentType 精确匹配与 trim;
  9. companyId;
  10. salaryMin;
  11. salaryMax;
  12. 两端薪资边界相等;
  13. 薪资区间重叠;
  14. 薪资区间不重叠;
  15. 多条件 AND;
  16. DRAFT、CLOSED 等非公开状态排除;
  17. 无结果;
  18. 排序与分页;
  19. %、_、反斜杠、引号等特殊字符的实际既有语义;
  20. null salary 字段的行为。

特殊字符测试首先锁定当前 Criteria LIKE 行为,不得在没有产品决策时把 % 和 _ 自动改成字面量搜索。

5.2 Service 真实查询层

新增或扩展 JobSearchServiceIntegrationTest:

  • 使用真实 JobService、JobRepository、CompanyRepository、EntityManager;
  • 不 Mock findAll;
  • latest/null/relevance 的排序结果一致且稳定;
  • salary 排序验证 NULL LAST、降序、相同薪资按 ID 倒序;
  • 验证 page/size、totalElements、totalPages;
  • 验证企业信息批量组装,不改变 DTO;
  • 保留现有非法 sort、非法分页、非法薪资范围的测试。

5.3 MySQL 真实层

优先新增 JobSearchSpecificationMySqlIntegrationTest,使用项目已有 Testcontainers 依赖:

  • @Testcontainers(disabledWithoutDocker = true) 仅允许本地快速执行时环境性跳过;
  • 独立验收和最终冻结时 Docker/MySQL 核心用例不得 SKIP;
  • 使用 MySQL 8.x 容器、正式 Flyway 和 ddl-auto=validate;
  • 使用动态属性注入 JDBC URL、用户名和密码;
  • 不打印数据库凭据;
  • 至少运行一组与 H2 完全相同的参数化用例,并对返回 ID 与顺序逐项对账;
  • 重点对账大小写、LIKE 特殊字符、NULL 排序、边界比较、分页稳定性。

如果仓库约定不允许 Testcontainers 进入普通 mvn test,可以将其放入独立 profile;但最终报告必须给出真实执行命令和非 SKIP 结果,不能只提交一个从未运行的测试类。

6. 夹具设计

使用明确区分的岗位:

  • 三个包含关键词的岗位,关键词分别只存在于 title、description、requirements;
  • 相同城市和不同城市;
  • FULL_TIME、PART_TIME 等不同 employmentType;
  • 至少两个企业;
  • 薪资区间覆盖边界相等、部分重叠、不重叠、NULL;
  • 相同 createdAt 的两个岗位,用 ID 验证稳定排序;
  • 相同 salaryMax 的两个岗位,用 ID 验证稳定排序;
  • PUBLISHED、DRAFT、CLOSED 等状态。

夹具必须以测试生成的唯一标识隔离,不能依赖 V1_1 演示数据或真实业务数据。

7. 数据库与副作用

  • 查询测试不得修改岗位状态、企业、用户或其他业务表;
  • 查询前后对相关稳定业务字段做行级快照,而不是只比较 COUNT;
  • Testcontainers 由测试生命周期销毁;
  • 使用负责人现有 MySQL 做独立黑盒时,只能精确清理本轮夹具;
  • 不允许清库、重置自增、删除 volume;
  • 不新增 Flyway 或索引。发现执行计划问题只记录到 R01,不在 Q01 修复。

8. SQL 与 EXPLAIN

真实 MySQL 验收需记录:

  • 组合过滤 SQL;
  • latest 分页;
  • salary 排序;
  • count 查询;
  • 使用索引、access type、estimated rows、filesort/temporary 情况;
  • 1、20、50 条结果规模下查询次数。

本阶段以“语义正确且无明显发布阻断”为通过条件,不为了 EXPLAIN 修改索引。

9. OpenAPI 与兼容性

  • 不修改 path 和 operation;
  • 目标仍为 176 paths / 214 operations;
  • 不改变参数名、默认值、响应 DTO 或错误码;
  • docs/openapi-rc.json 不应因仅新增测试而变化;
  • 必须运行 docs/verify_openapi.py 防止意外契约漂移。

10. 通过条件

只有同时满足以下条件才能通过 Q01:

  1. JobSearchSpecification 不再是测试零引用;
  2. H2 真实 Repository 测试覆盖全部过滤语义;
  3. Service 自定义 salary 排序有真实数据库证据;
  4. MySQL 核心用例真实运行且无 SKIP;
  5. H2 与 MySQL 返回 ID、顺序和边界语义一致,或明确记录并解决差异;
  6. 非公开岗位绝不进入公开搜索;
  7. 分页稳定、total 正确;
  8. 无业务数据副作用;
  9. 原 Mock 测试保留且准确标注为编排测试;
  10. 全量测试和 clean verify 通过;
  11. OpenAPI 保持 176/214;
  12. 无新增 Flyway、ErrorCode 或公网接口。

11. 最终状态

实施完成但尚未独立复验时,只能写:

Q01 已完成真实查询测试实现与自动化验证,待独立代码复验及真实 MySQL/Docker 验收。