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 集合与顺序,不能只断言数量。
至少覆盖:
- title 关键词;
- description 关键词;
- requirements 关键词;
- 关键词忽略大小写;
- null、空字符串、纯空白关键词;
- keyword 前后空格;
- city 精确匹配与 trim;
- employmentType 精确匹配与 trim;
- companyId;
- salaryMin;
- salaryMax;
- 两端薪资边界相等;
- 薪资区间重叠;
- 薪资区间不重叠;
- 多条件 AND;
- DRAFT、CLOSED 等非公开状态排除;
- 无结果;
- 排序与分页;
%、_、反斜杠、引号等特殊字符的实际既有语义;- 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:
JobSearchSpecification不再是测试零引用;- H2 真实 Repository 测试覆盖全部过滤语义;
- Service 自定义 salary 排序有真实数据库证据;
- MySQL 核心用例真实运行且无 SKIP;
- H2 与 MySQL 返回 ID、顺序和边界语义一致,或明确记录并解决差异;
- 非公开岗位绝不进入公开搜索;
- 分页稳定、total 正确;
- 无业务数据副作用;
- 原 Mock 测试保留且准确标注为编排测试;
- 全量测试和 clean verify 通过;
- OpenAPI 保持 176/214;
- 无新增 Flyway、ErrorCode 或公网接口。
11. 最终状态
实施完成但尚未独立复验时,只能写:
Q01 已完成真实查询测试实现与自动化验证,待独立代码复验及真实 MySQL/Docker 验收。