# 账号注销测试夹具隔离修复报告 日期:2026-09-17 分支:`refactor/r01-structure-performance` 基线 HEAD:`d5c37128e84b4fc83bf5334254dd8efeb88ccf64` 性质:**纯测试修复** —— 生产代码、Flyway、配置与接口零改动 --- ## 1. 结论 > `AccountDeletionControllerTest` 与关联测试组合的**既有交叉污染已消除**;修复后两种执行顺序各 5 次全部 F=0 / E=0 / S=0。 --- ## 2. 稳定复现(修复前) **致冲突组合**(连续运行 5 次,**5/5 稳定复现**): ``` mvn -Dtest="*Deletion*Test,*Verification*Test,*Risk*Test,*Report*Test,*Block*Test, *Notification*Test,*Recommendation*Test,*RequestContext*Test,*RequestId*Test" test ``` 5 次运行**全部**失败于同一位置: ``` AccountDeletionControllerTest.lazyTransition_firstGet_returnsCompleted_andConsistent:228 -> createUser:75 DataIntegrityViolation: Unique index or primary key violation: USERS(PHONE) = '930000000' ``` ### 诊断结论(逐项回答) | 问题 | 结论 | |---|---| | **谁先插入 `phone='930000000'`** | `com.mydreamjob.common.logging.RequestContextMigrationIntegrationTest`(**HEAD 既有类**)。其 `@BeforeEach` 用 `AtomicLong(930_000_000L)` 创建 user | | **冲突发生在哪个方法** | `AccountDeletionControllerTest#createUser:75`(由 `lazyTransition_firstGet_returnsCompleted_andConsistent:228` 调用) | | **共享 H2 数据库名** | `integrationdb`(`jdbc:h2:mem:integrationdb;MODE=MySQL;DB_CLOSE_DELAY=-1`)—— 同一 JVM 内所有 `@SpringBootTest` 共享 | | **static 计数器何时重置** | **不重置** —— `PHONE` 为 per-class static,类加载时初始化为 `930_000_000`;冲突**不是**计数器重置所致,而是**号段被残留记录占用** | | **未清理的记录** | `users`(`phone=930000000`);`user_roles` 随 users 级联删除 | ### 根因链 1. **号段撞车**:`common/logging/RequestContextMigrationIntegrationTest` 与 `deletion/AccountDeletionControllerTest` **都使用基数 `930_000_000`** 2. **污染源不清理**:前者 `@AfterEach` 只清 `RequestContextProbe` 与 `MDC`,**不删 users** → 每个测试方法残留 1 条 `930000000+N` 3. **受害者删除失败**:后者的 `@AfterEach` 用 `userRepository.deleteAll()`(**全表删除**),会因**其他测试类残留 user**(被 `notifications`/`offers`/`resume_versions` 等**无 CASCADE** 的外键引用)而失败 → 自身 user 也未删净 4. **执行顺序**:诊断运行中 `common.logging.RequestContextMigrationIntegrationTest` 排在第 **6** 位、`AccountDeletionControllerTest` 排在第 **13** 位 → 前者残留必然影响后者 ### 与 A2/A3 的关系(如实记录) A2/A3 新建的测试类**均不创建 user**,故**无污染**: - `common/r01a2/RequestContextMigrationIntegrationTest`:无 `AtomicLong`、无 `userRepository` 注入 - A3 的三个测试类:均使用 `@MockBean UserRepository`(不实际写库) **该缺陷完全存在于基线**(污染源文件为 HEAD 既有),与 A2/A3 无关;此前用 HEAD 隔离 worktree 的归因结论**正确**。 > 同号段还有 `candidatescore/CandidateScoringClockBoundaryIntegrationTest`,但其标注 `@Transactional`(每方法自动回滚)→ **不残留**,无风险。 --- ## 3. 修复内容 ### 3.1 `common/logging/RequestContextMigrationIntegrationTest.java`(污染源) | 改动 | 内容 | |---|---| | 号段独占 | `930_000_000L` → **`986_000_000L`**(已确认全仓未占用) | | **清理** | `@AfterEach` 增加:按自身 `studentId` **精确删除**本测试创建的 user(`findById(...).ifPresent(delete)`),并补 javadoc 说明隔离原因 | ### 3.2 `deletion/AccountDeletionControllerTest.java`(受害者) | 改动 | 内容 | |---|---| | 号段独占 | `930_000_000L` → **`987_000_000L`**(已确认全仓未占用) | | 记录归属 | 新增 `List createdUserIds`,`createUser()` 登记每个自建 userId | | **精确清理** | `@AfterEach` 由 `deleteAll()`(整表)改为**按本测试记录**删除:`account_deletions`(按 userId)→ `users`(级联 `user_roles`),各步 `flush()`,遵循外键顺序;`@BeforeEach` 清空记录 | | **防掩盖断言** | `createUser()` 内先断言该号在共享库中**不存在**,存在即抛出可读错误(暴露上轮清理失效),而不是被唯一键异常吞掉 | **未使用**:未改唯一约束、未捕获忽略 `DataIntegrityViolationException`、未用 `@DirtiesContext`、未清空整表、未 `DELETE FROM users` 无条件删除、未 sleep、未放宽任何业务断言、未依赖测试执行顺序。 ### 3.3 新增反证测试 `deletion/AccountDeletionFixtureIsolationTest.java`(5 例) | 反证 | 断言 | |---|---| | 1 | 同一 Spring 上下文内连续创建 5 个夹具**不冲突**且 ID 不重复 | | 2 | 注销记录写入后,按本测试路径清理 → `account_deletions` / `users` / `user_roles` 残留**均为 0** | | 3 | **变异探测**:预置同号记录(等价于"上轮未清理")→ 防掩盖断言**必须失败**并检出该号,证明残留不会被静默吞掉 | | 4 | 独占号段 `987_000_000` 在测试开始前为空(不与任何他类重叠) | | 5 | 清理**精确到本测试记录**:预置的"他类"记录必须保留,证明不是整表删除 | --- ## 4. 验证结果 ### 4.1 两种执行顺序各 5 次 | | 组合 | 结果(5 次) | |---|---|---| | 顺序 A | `*Deletion*Test,*RequestContext*Test,*RequestId*Test` | **95/95,F=0 E=0 S=0** ×5 ✔ | | 顺序 B | `*RequestContext*Test,*Deletion*Test,*RequestId*Test` | **95/95,F=0 E=0 S=0** ×5 ✔ | (修复前该组合稳定失败 5/5;修复后 10/10 全绿) ### 4.2 单类与反证 | 命令 | 结果 | |---|---| | `mvn -Dtest="AccountDeletionControllerTest" test` | **14 / 14** ✔ | | `mvn -Dtest="AccountDeletionFixtureIsolationTest,AccountDeletionControllerTest" test` | **19 / 19**(5 反证 + 14 原)✔ | ### 4.3 全量 | 命令 | 结果 | |---|---| | `mvn clean verify` | **2225 / 2225**,Failures=0、Errors=0、**Skipped=0**,BUILD SUCCESS(≥ 2220 ✔) | | `python docs\verify_openapi.py` | **176 paths / 214 operations**,All checks passed ✔ | 较修复前(2220)新增 **5** 个测试,全部为反证测试。 ### 4.4 边界核验 | 项 | 结果 | |---|---| | 生产源码 `git diff --stat -- src/main/` | **空**(零改动)✔ | | V1–V45 迁移 | **零修改** ✔ | | Flyway / 配置 / 接口 | 零改动 ✔ | | `GlobalExceptionHandler.java` | 仍为工作区 stat 标记(内容零差异),**未处理** ✔ | | Docker | 未操作 ✔ | | 是否进入 A4 | 否 ✔ | --- ## 5. 精确修改文件 ``` 修改(2) mydreamjob-server/src/test/java/com/mydreamjob/common/logging/RequestContextMigrationIntegrationTest.java mydreamjob-server/src/test/java/com/mydreamjob/deletion/AccountDeletionControllerTest.java 新增(1) mydreamjob-server/src/test/java/com/mydreamjob/deletion/AccountDeletionFixtureIsolationTest.java 文档(1) docs/account-deletion-test-isolation-fix-20260917.md ``` --- ## 6. 提交 ``` 提交信息:test: isolate account deletion controller fixtures ``` (精确暂存上述测试文件与报告,敏感扫描后独立提交;推送当前 R01 功能分支) --- > 账号注销测试夹具隔离修复完成;测试交叉污染已消除,生产代码零改动。