轻松聘AI开发
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.
 
 
 

9.6 KiB

R01-C 评分/Fairness 组合测试污染隔离修复(独立纯测试提交)

日期:2026-09-18 分支:refactor/r01-structure-performance R01-C 提交:7d4704ee28e4d8e2d1fbcd174a990fc6bfe68169 本轮性质:纯测试修复 —— 生产代码 diff = 0


0. 结论

*CandidateScoring*Test,*Fairness*Test 组合在任意顺序下均全绿; 污染源已按逐类行级快照实测精确归因(未按类名猜测定性), 5 个候选类经证明零残留、未做任何修改。


1. 最初的错误归因及纠正

阶段 归因 判定
R01-C 复验阶段(初判) CompanyScoringDashboardPersistenceIntegrationTest(用 LocalDate.now() 生成当前期间且无清理) 错误 —— 该类根本不在失败命令内;单独与 Fairness 组合 3 次全绿
二分定位 *CandidateScoring*Test 是污染所在半边;进一步收敛到 CandidateScoringPersistenceIntegrationTest + FairnessMonitoringHttpIntegrationTest(7 用例 / 2 失败) 正确但不完整 —— 修复后污染量仅由 +15 降到 +12,说明仍有其它污染源
本轮(逐类实测) 见 §2,5 个类合计残留 15 个 student 完整 —— 与受害测试观测的 +15(修复前)/ +12(仅修 1 类后)精确吻合

2. 每个候选类的实测残留表

方法:临时诊断探针 ZR01CLeakProbeTest(置于 candidatescore.zz 以确保 alphabetical 下最后运行), 对 candidate_dimension_scores 的当前期间抓取行级快照 (student_user_id / dimension / period / status / version)与相关表计数。 每次 mvn 均为全新 JVM + 全新 H2 库,故"运行后快照"即为该类在干净上下文中的净残留。 探针诊断完成后已删除(非交付物)。

候选类 单独运行 当前期间残留行数 残留 student 数 已有 @AfterEach 是否需修
CandidateScoringPersistenceIntegrationTest 3/3 通过 24(3×8) 3 无 是(已修)
CandidateScoringRefreshClaimFenceTest 7/7 通过 16 2 无 是
CandidateScoringRefreshClaimTokenTest 14/14 通过 8 1 有(不全) 是
CandidateScoringRefreshFailureTransactionTest 4/4 通过 8 1 无 是
CandidateScoringRefreshInfrastructureTest 15/15 通过 57 8 无 是
CandidateScoringClockBoundaryIntegrationTest 3/3 通过 0 0 无 否(无需修改)
CandidateScoringRefreshBatchIsolationTest 5/5 通过 0 0 无 否(无需修改)
R01D4GetWritesSnapshotEvidenceTest 6/6 通过 0 0 无 否(无需修改)
CandidateScoringGovernancePersistenceIntegrationTest 5/5 通过 0 0 无 否(无需修改)
CompanyScoringDashboardPersistenceIntegrationTest 11/11 通过 0 0 无 否(无需修改)

5 个候选类经实测零残留(其中 4 个无 @AfterEach 但依然不留残留, 说明其写入被事务回滚或未落入 current_period)。未因"名字可疑"而修改它们。


3. +15、+12 的精确来源

项 组成 合计
修复前 +15 Persistence 3 + ClaimFence 2 + ClaimToken 1 + FailureTransaction 1 + Infrastructure 8 15 ✔ 与受害测试观测 100 → 115 完全一致
仅修 Persistence 后 +12 ClaimFence 2 + ClaimToken 1 + FailureTransaction 1 + Infrastructure 8 12 ✔ 与观测 100 → 112 完全一致

关键佐证:CandidateScoringRefreshInfrastructureTest:271 以循环 "131" + String.format("%08d", i + 7000)(i=0..7)创建 8 个用户, 与探针实测的 8 个残留 student 精确对应。


4. 修复内容(仅改测试,生产代码 0 改动)

文件 修复前 修复后
CandidateScoringPersistenceIntegrationTest 3 个用例各建 1 个学生并物化当前期间快照,零清理 登记自有 userId;@BeforeEach 断言独占手机号不存在;@AfterEach 按外键顺序精确 id 删除快照 → 角色 → 用户
CandidateScoringRefreshClaimFenceTest 残留 16 行 新增 @AfterEach:按本类独占手机号前缀 193/192 解析出精确 id 后按外键顺序删除
CandidateScoringRefreshClaimTokenTest 残留 8 行(原有 @AfterEach 未覆盖快照) 同上,前缀 190/189
CandidateScoringRefreshFailureTransactionTest 残留 8 行 同上,前缀 130
CandidateScoringRefreshInfrastructureTest 残留 57 行 同上,前缀 132/13107

统一遵守的约束:独占手机号(不与 V1_1 种子账号 138…、其它测试 t<id>、1390000xxx 冲突); 按外键顺序(candidate_dimension_scores / candidate_scoring_refresh_tasks → user_roles → users); 不使用 deleteAll/TRUNCATE/@DirtiesContext/sleep;不捕获忽略清理异常; 不修改生产统计或查询;不通过改期望 populationSize 掩盖污染;不依赖测试执行顺序。

未修改 FairnessMonitoringHttpIntegrationTest 的任何断言 —— 其 populationSize 期望值 仍为既有的 100 / 200,污染消除后自然精确命中。


5. 修复前失败与修复后结果

组合 修复前 修复后
CandidateScoringPersistenceIntegrationTest,FairnessMonitoringHttpIntegrationTest 7 run / 2 failures 7 / 0 ✔
*CandidateScoring*Test,*Fairness*Test(原失败命令) 296 / 2 failures(另一次 260 / 2) 260 / 0 ✔

失败断言(修复前):$.data.populationSize expected:<100> but was:<115>、 expected:<200> but was:<215>。 修复后 populationSize 精确等于既有期望 100 / 200 ✔


6. 双向顺序各 3 次结果

实际执行顺序从日志核对(非依据命令参数):

顺序 实测类顺序 结果
-Dsurefire.runOrder=alphabetical ×3 candidatescore.CandidateScoringCalculatorTest … → … scoringmonitoring.FairnessStatisticsCalculatorTest;Fairness HTTP 在所有 Candidate 类之后 260 / F=0 / E=0 / S=0 ×3 ✔
-Dsurefire.runOrder=reversealphabetical ×3 scoringmonitoring.FairnessStatisticsCalculatorTest … → … candidatescore.CandidateScoringCalculatorTest;Fairness HTTP 在 Candidate 类之前 260 / F=0 / E=0 / S=0 ×3 ✔

两个方向 6 次运行全部 260/260,Failures=0、Errors=0、Skipped=0。


7. 行级清理与反证

新增独立隔离门禁 R01CScoringFixtureIsolationGateTest(与污染类共用 integrationdb):

# 断言 覆盖要求
1 5 个已修污染类的独占手机号段在库中零残留用户 自有 users 残留为 0
2 若发现残留,进一步报告其当前期间快照数(失败信息含类名/前缀/用户数/快照数) 自有快照残留为 0
3 当前期间不得存在指向已删除用户的孤立快照行 清理不留孤儿
4 其它 period 的行数可查询且非负(清理不波及) 其它 period 不受影响
5 断言以独占前缀为界 → 清理只影响本类登记的数据 不删除其他测试数据
6 门禁与污染类同库同 JVM 运行,只要任一污染类遗留自有行即失败 Fairness 统计不再依赖先前顺序

CandidateScoringPersistenceIntegrationTest 的 @BeforeEach 前置断言 ("独占夹具手机号不得预先存在")即"预置冲突时前置断言失败"的反证: 若上轮清理失效,测试在开始前即失败,而不是让残留静默参与断言。


8. clean verify 与 OpenAPI

docker info       → ServerVersion=29.6.2(Testcontainers 真实执行,未跳过)
mvn clean verify  → Tests run: 2746, Failures: 0, Errors: 0, Skipped: 0   BUILD SUCCESS
python ..\docs\verify_openapi.py → paths: 176 / operations: 214 / All checks passed!
项 数值
测试总数 2746 = R01-C 基线 2743 + 本轮新增隔离门禁 3 ✔(不低于 2743 + 新增数)
Failures / Errors / Skipped 0 / 0 / 0 ✔
OpenAPI 176 / 214 ✔
生产代码 diff 0 ✔

9. 精确文件清单

被修复的测试文件(5)

mydreamjob-server/src/test/java/com/mydreamjob/candidatescore/CandidateScoringPersistenceIntegrationTest.java
mydreamjob-server/src/test/java/com/mydreamjob/candidatescore/refresh/CandidateScoringRefreshClaimFenceTest.java
mydreamjob-server/src/test/java/com/mydreamjob/candidatescore/refresh/CandidateScoringRefreshClaimTokenTest.java
mydreamjob-server/src/test/java/com/mydreamjob/candidatescore/refresh/CandidateScoringRefreshFailureTransactionTest.java
mydreamjob-server/src/test/java/com/mydreamjob/candidatescore/refresh/CandidateScoringRefreshInfrastructureTest.java

新增隔离测试(1)

mydreamjob-server/src/test/java/com/mydreamjob/common/r01c/R01CScoringFixtureIsolationGateTest.java

报告(1):本文件

路线图(1):docs/backend-development-roadmap.md —— 仅追加一条夹具问题关闭记录

临时诊断探针 ZR01CLeakProbeTest 已在诊断后删除,不进入提交。


10. 生产代码修改数 = 0

确认:本轮生产代码 diff = 0。 未修改 V1–V45、ErrorCode、DTO、公网接口、 pom.xml、docs/openapi-rc.json;未修改任何生产查询或统计逻辑; 未修改 FairnessMonitoringHttpIntegrationTest 的断言。


评分与 Fairness 组合测试污染已完成精确归因,并通过独立纯测试提交修复。