
1. 为什么这一版回归测试要以“试卷管理考试管理”作为联合验证对象先说背景。这个项目是一个基于SpringAI的在线考试系统后端核心业务其实可以拆成两条链一条是出题与组卷链对应题库、试卷管理、试卷发布一条是考试执行链对应考试创建、考生报名、答题提交、自动判分。两条链各自都有独立的功能模块但真正到了实际使用的时候它们是互相咬合的——试卷管理产生的“成品试卷”是考试管理模块的输入考试管理生成的“考试场次”又反过来决定了试卷的可见时间、可用状态。因此在做回归测试的时候如果只盯着单个模块做孤立验证你会发现单模块全绿联调一跑就崩。这一版回归测试文档我在设计的时候就直接把试卷管理和考试管理作为联合验证对象原因就是它们之间的耦合点比代码层面看起来要多得多。再展开说一下SpringAI在这个系统里的角色。项目的定位是“基于SpringAI”实际落地时SpringAI主要承担了两类职责一类是智能组卷辅助比如根据知识点难度分布自动生成试卷草稿另一类是考试后的学情分析摘要比如把成绩单转成自然语言描述。但必须说清楚SpringAI的能力在考试系统里属于增强侧真正的核心交易链路——试卷的创建、修改、发布考试的开启、提交、判分——仍然是传统的关系型事务逻辑这一点在回归测试中尤其重要。因为一旦有回归用例失败你得首先判断问题出在SpringAI接入层还是业务逻辑层这个区分直接决定了排查路径。这次回归测试的定位也很明确不是从零开始验证功能正确性而是验证“在已有功能基础上经过一轮迭代改动之后既有功能是否被破坏”。文档里的测试范围覆盖了试卷管理的完整生命周期从创建试卷、添加题目、配置分值、设置可见性到试卷的发布和下线以及考试管理的典型流程从创建考试批次、关联试卷、设置考试时间窗口、配置考生名单到考生进入考试、提交答卷、系统自动判分。适合看这篇内容的人一个是准备做毕业设计或者项目实训的同学他们最需要知道“联调回归”到底要测哪些点、怎么设计用例才不至于漏掉关键耦合另一个是工作一到三年的后端开发可能已经写了不少功能但对“测试文档该怎么写得有业务深度”这件事还没有形成方法论。接下来我按实际执行顺序把这一版联合回归测试的完整思路、场景设计、数据准备、踩坑记录和结论整理出来。2. 联合回归的核心逻辑先找到模块之间的“咬合点”2.1 试卷状态与考试场次状态的状态机耦合在做联合回归之前第一步不是写用例而是把两个模块之间的交互关系梳理清楚。试卷管理模块里一张试卷的核心状态链路是草稿 - 已组卷 - 已发布 - 已下线。考试管理模块里一场考试的核心状态链路是草稿 - 未开始 - 进行中 - 已结束。它们之间不是各自独立的两个状态集合而是存在一组映射关系我用一个表来展示试卷状态允许被考试模块引用的状态考试模块中对应的表现草稿否考试创建时无法选择该试卷已组卷是但不可发布可关联但发布考试时系统会提示试卷未发布已发布是正常关联、正常发布已下线部分允许已关联的考试可正常进行新建考试不可选择在这个状态下有一个特别容易踩坑的细节在回归用例中我单独标记成了P0级场景试卷已经发布考试创建也引用了该试卷然后试卷被下线了这时一场原本已经创建好的考试还能不能正常开考这个场景如果只做单模块回归永远测不出来。因为试卷管理模块的单测只看“下线功能是否正常”而考试模块的单测只关心“自己创建的考试是否正常”只有走到联合层面才会发现这里需要一个明确的业务裁定已关联考试的试卷下线不影响正在进行的考试场次但未开始且未关联的考试在开考前如果发现试卷已下线系统应当在考试列表中给出明确的状态提示。这块业务裁定最终在实现层定下的规则是考试创建时把“试卷快照版本号”写入考试记录而不是在考试进行中实时去读试卷表。这样即便试卷被下线、甚至被修改已经生成的考试场次依然按照快照内容运行。这个机制本身不复杂但它带来了一个回归测试上的要求——联合回归不仅要测功能行为还要测快照数据是否在考试创建那一刻被完整固化。我在用例里专门设计了一个验证步骤创建考试后直接在数据库里修改试卷的题目内容然后观察已创建考试里的题面是否保持不变。2.2 关联关系的完整性考试引用试卷时的三种失败场景第二个咬合点在“关联行为”上。试卷管理和考试管理之间有一个非常基础的操作——创建考试时选择试卷。看起来就是一个下拉框选择但这里实际上需要对三种失败情况做回归确认第一种考试引用了一张已经被逻辑删除的试卷。系统里试卷删除走的是逻辑删除此时试卷在题库列表里不可见但考试创建的搜索接口如果没做状态过滤就会出现“能搜到、不能选”的幽灵数据。这个场景在回归中要用一条已经被删除的试卷ID去发起关联确认返回结果是友好的提示而不是空指针异常。第二种同一张试卷被同一场考试重复关联。系统中试卷和考试的关系设计为一对一一场考试只能有一张主试卷但如果有批量导入考试的入口防御没做好就可能出现重复关联。回归用例需要覆盖列表接口去重和数据库唯一索引两条防线。第三种跨租户引用问题。在线考试系统如果做了多租户隔离那么A租户创建的试卷不能被B租户的考试引用。这个场景在回归中要模拟两个租户的数据交叉访问确认数据权限过滤器在“试卷ID”这个维度上也生效了。很多团队做多租户隔离只做了“查询过滤”没做“关联校验”导致租户A的考试能引用租户B的试卷这是个典型的跨模块回归盲区。2.3 回归基线的建立哪些功能点是必须每个版本都重复验证的另外一个重要的设计思路是回归测试不能变成“每次把全量手工用例跑一遍”而是要基于历史缺陷分布建立一条回归基线。我整理了一下这个系统在过去迭代中出现缺陷频率最高的功能点作为这一版联合回归的必测清单试卷分值配置保存后重新打开编辑页分值是否回显正确试卷中题目顺序调整后组卷预览是否同步更新考试时间窗口跨越自然日比如22:00开始次日02:00结束判分结果归属日期是否正确考生在考试时间截止后点击提交按钮是否被正确处理拒绝还是强制提交管理员中途修改考试时长已进入考试的考生是否感知到变化同一考生被分配到两场时间重叠的考试系统是否在进入第二场时给出阻塞提示这六条是历史Bug的高发区也是试卷管理和考试管理数据交互最频繁的区域。每次回归测试我都会先把这六条手工跑一遍再根据当次迭代的代码改动范围去扩充测试点。如果一个版本迭代了“试卷支持富文本编辑器”那么还需要追加一条“富文本内容在考试作答页是否正常渲染”的回归用例如果迭代了“考试支持按班级批量导入考生”就要追加“导入名单中考生去重校验”的用例。回归测试的精髓就在这——它不是一个固定不变的测试集而是一个跟随迭代历史持续生长的缺陷防线。3. 回归数据的构造策略与测试环境的准备细节3.1 数据铺底联合回归需要的最小数据集模型回归测试和功能测试最大的区别在于功能测试关心“功能是否存在”回归测试关心“功能在数据积累到一定规模之后是否依然正确”。因此联合回归对数据准备的要求比单模块测试高得多。我在这一版执行前花了小半天时间专门构造了一套最小但完整的回归基准数据。先说基础数据。题库层需要三个知识领域各至少20道题题型覆盖单选、多选、判断而且每一道题都要标注难度系数。这套题库是给智能组卷用的SpringAI的那个接口会根据知识点和难度分布来生成草稿试卷回归时要验证的是AI生成的草稿试卷分值总和是否等于试卷满分、题型分布是否符合预设比例、有没有出现同题重复引用。试卷层需要准备三种状态的试卷各一份一份草稿、一份已发布、一份已下线。为什么至少三份因为考试管理模块的“选择试卷”接口对这三种状态的试卷表现完全不同。草稿试卷必须不可选已发布试卷必须可选已下线试卷在新建考试时不可选但已有考试可正常运行。只用一份试卷是测不出这个逻辑的完整性。考试层需要准备两场考试一场“未开始”一场“进行中”。未开始的考试用来验证试卷快照机制、考生名单确认、开考前的时间窗口校验进行中的考试用来验证提交判分、时间截止处理、缺考记录生成、并发提交处理等等。另需一个“已结束”的场次用于成绩发布和查询类接口的回归。3.2 测试环境中的Pretest检查项环境这块也要多说一句。联合回归对测试环境的要求是“数据可控服务链路完整”尤其是SpringAI相关的外部依赖必须可用。我在执行之前列了一个Pretest清单每一条都直接决定测试结果是否可信数据库已恢复至基线备份试卷表、考试表、考生答题记录表均为干净的已知状态外部AI服务接口连通智能组卷接口的token未过期且配额充足定时任务无冲突——如果考场结束时需要跑一个自动判分的定时任务要确认测试环境没有其他定时任务占用同一任务队列文件存储服务可用——试卷如果支持附件上传比如听力音频、图片题需要确认存储桶权限正常系统时钟准确——考试时间窗口的校验严重依赖服务器时间这个环境问题在回归中容易被忽略但一错起来所有时间边界用例全部失败其中系统时钟这一点我在之前的回归中就吃过亏。有一轮测试发现“考试时间已到但考生可以继续提交”的Bug排查了半天最后发现是测试环境服务器时间比真实时间慢了十分钟根本不是代码问题。从那儿之后Pretest清单里加入了NTP校验这一项。3.3 构造时间边界场景用SQL直接修正考试时间不再等待回归测试中最费时间的就是时间类场景。一场考试如果设置的时间窗口是“明天上午9点到10点”你不可能真的等24小时再去测“未开始”状态下的行为。我这边处理的办法是先用后端的接口创建考试然后用SQL直接修改数据库里的开始时间、结束时间和考试状态字段把时间“拨”到需要的边界。举个例子要测“考生在考试开始前5分钟是否可以提前进入候考页面”我先把考试的start_time设置为当前时间加5分钟然后用考生账号去访问进入考试接口确认系统返回“考试未开始”的提示。再测“考生在上次提交后超过截止时间30秒再次提交”我把考试的end_time设置为当前时间减30秒然后调提交接口确认系统按“已截止”处理还是按“允许补交”处理。这种方式能极大缩短回归时间但它要求测试人员对数据库表结构足够熟悉能准确操作考试时间相关的字段。另外还要注意修改数据库时间字段之前最好自己先确认接口层的时间是从数据库读取还是用Redis缓存。如果时间信息在Redis里有缓存副本改数据库之后必须同步更新缓存否则会出现“表和缓存不一致”导致的假失败。4. 联合回归用例设计的逐层拆解4.1 正向主链路用例从创建试卷到自动判分的一整条流程回归测试不能上来就测边界条件。我习惯的设计顺序是先设计正向主链路用例把系统的核心业务路径完整走通确认主干没有问题然后再围绕主干分叉出异常和边界用例。在这一版回归中正向主链路我设计了一条完整的业务流管理员登录系统进入题库模块录入8道题目3道单选、2道多选、3道判断每题设置知识领域、难度系数和分值调用SpringAI智能组卷接口指定“数据结构”领域、难度比例容易40%、中等40%、困难20%、总分100分让系统自动生成一份试卷草稿人工编辑抽检把草稿中的一道题替换为另一道手动指定的题目调整两道题的分值然后保存试卷为“已组卷”发布试卷确认试卷状态流转为“已发布”创建一场考试命名为“回归测试专用场次”关联刚才发布的试卷设置考试时间为当前时间后一小时、考试时长60分钟、参考学生名单导入5名测试学生发布考试确认考试状态为“未开始”切换一名考生账号进入考试页面确认能看到试卷题目作答并提交管理员端确认该考生已经交卷系统自动判分产出成绩进入成绩列表对试卷管理端的“已发布试卷”执行下线操作确认试卷状态流转为“已下线”同时确认之前创建的那场考试在“已结束”列表里仍能正常查看成绩这条主链路走通意味着两个模块之间的核心接口没有出现破坏性回归。接下来再深入异常分支。4.2 试卷生命周期中的异常回归用例在试卷管理侧我按“状态流转受阻”和“数据篡改防御”两个维度设计了异常用例。状态流转受阻的第一类问题是“已发布的试卷能否被修改”。系统设计上是禁止直接编辑已发布试卷的编辑入口必须隐藏后端接口也要做状态校验。回归用例是用管理员账号直接调编辑接口传入一个已发布试卷的ID和新的题目JSON确认接口返回“当前状态不允许修改”。同时检查前端编辑页的进入逻辑确认已发布试卷在列表中不显示编辑按钮。第二类问题是“已下线试卷的重新发布”。设计上下线后的试卷允许重新发布但重新发布前必须经过一次内容完整性校验题目是否仍存在、分值配置是否完好。回归用例是对已下线试卷调用重新发布接口确认校验通过后试卷状态恢复为“已发布”。如果中途有任何一道题被逻辑删除接口应该返回具体是哪道题导致发布失败。第三类是删除防御。一张已经被考试引用的试卷无论处于什么状态都不能执行物理删除。回归用例是先删除一张未被引用的试卷确认成功再对已被引用的试卷执行删除确认返回“存在关联考试记录无法删除”的提示。这个用例的价值在于防住了一种常见的线上事故——管理员清理过期数据时把还关联着历史考试的成绩单数据源头删掉了。4.3 考试执行链路中的并发和时间边界回归用例考试管理侧回归用例的重心放在并发和时间边界上。并发场景有两个用例是必须做的第一个是同一考生重复提交。将考试状态调整为“进行中”考生账号提交答卷后网络侧模拟一次“超时重试”看系统是幂等返回第一次的结果还是抛出一条重复提交异常。正确设计是提交接口必须有防重令牌或者提交记录唯一索引第二次提交时直接返回“已提交成功请勿重复操作”。第二个是多人同时交卷时的性能表现。我用测试脚本模拟了30个考生同时在截止时间前10秒提交答卷然后检查交卷接口的平均响应时间、数据库写入是否出现死锁、判分定时任务是否在预期时间内完成。这个用例对于考试系统来说尤其重要因为真实考场中“临近截止时间集体交卷”是常规现象而不是极端情况。时间边界用例我拆成了四个点考试开始前5分钟考生请求考试详情应返回“未开始”倒计时考试进行中正常请求作答应返回题目列表考试截止时间点正在作答页面提交应正常收卷考试截止后10分钟补交请求应被拒绝且记录异常日志特别要说的是“截止时间点提交”这个边界。系统判断用的是服务器时间但考生页面的倒计时是基于服务器返回的截止时间戳本地渲染的。如果考生本地时钟比服务器慢几秒就可能出现“页面还显示剩余3秒服务器已经截止”。正确的处理方式不是拒绝而是以服务器收到请求的时间为准只要请求在截止时间戳之后到达但试卷允许一个缓冲时长比如30秒就应该正常收卷。这个缓冲时长的配置在考试设置里是隐藏项回归用例里需要用管理员接口找到它的默认值并验证。4.4 SpringAI能力在回归中的专项检查SpringAI相关的功能不是纯逻辑型业务它的回归策略和传统模块不同——除了功能正确性之外还需要关注模型输出稳定性和接口依赖异常。智能组卷接口的回归我设计了两组对比用例。第一组用同一份题库和同一组组卷参数请求两次比较两次生成的试卷草稿在题目选择上是否有明显差异——如果 AI 模型更新了 prompt可能会导致同样的参数产出两份完全不同的试卷这会直接破坏“同一套配置组出稳定试卷”的用户预期。第二组故意传入一个不存在的知识领域名称看接口返回的是结构化错误信息还是模型无边界地编造出一个新知识点。实际测试中这种“AI幻觉”场景很容易出现——模型不知道“机器学习基础”这个知识点ID是什么但会顺着prompt自然生成一个类似名称导致脏数据进入试卷表。考试后的学情分析摘要回归重点则是输出的稳定性与安全性。系统调用SpringAI把成绩单转成一段文字评价我要检查的包括成绩为100分的学生摘要中是否出现负面评价成绩为0分但确实参加了考试的学生摘要是否仍能生成而不是报错学生姓名信息在摘要中是否被正确脱敏。这些用例其实不是传统意义上的“功能回归”但它们恰恰是SpringAI接入后新引入的风险面必须纳入回归基线。5. 回归执行过程与缺陷追踪这份文档背后的真实记录5.1 执行顺序为什么被设计成“先数据完整性再功能交互最后AI能力”到了实际执行环节顺序也很有讲究。我反而不会把SpringAI相关用例放在最前面执行——虽然它们看起来是这个项目最“新”的部分但正因为新失败后的定位成本最高如果把它排在前面且失败了你会很难分辨到底是业务基线数据没准备好还是模型接口本身有问题。我的执行顺序是先跑一遍数据完整性基线检查——把上面3.1节准备的基础数据做一次全量校验确认每个测试实体试卷、考试、学生、成绩都处于预期状态再跑正向主链路——确认两个模块的核心交互没有断裂然后跑异常与边界用例——这部分是回归的重头因为大多数迭代破坏性Bug都藏在异常分支里最后跑SpringAI AI能力专项——把智能组卷、学情分析这类依赖外部接口的用例放在最后避免由于外部服务波动而干扰整轮回归的结论这个顺序还有一个额外的好处如果前两步跑挂了那大概率是环境或者数据问题此时没必要继续往下执行直接修环境重新预热数据如果前三步全部通过AI能力跑挂了那可以非常确信是外部接口链路的问题——搜索范围一下子就锁定了。5.2 实际执行中发现的三个二级回归缺陷这一轮回归一共执行了100多条用例最终发现了三个二级缺陷数量不算多但每一个都有代表性。第一个缺陷在“试卷下线后重新发布”功能上。按设计重新发布前要校验试卷内所有题目是否仍处于有效状态但实际发现校验只检查了题目是否存在没有检查题目是否被停用题目的有效状态标记为0。也就是说如果试卷里有一道题被管理员置为停用重新发布依然能成功。线上表现就是考生打开试卷看到一道题显示“题目已失效”整场考试直接受到影响。修起来很简单重发接口里加一个题目状态有效的过滤条件但这无疑是个典型的回归盲区——单测数据里的题目全是有效的所以从来没触发过。第二个缺陷在考试提交的幂等控制上。提交接口已经有一个基于token的防重设计但token的生成时机是“进入考试页面时”。如果考生进入页面后浏览器长期挂着不操作token在服务端过期了此时提交答卷会触发token校验失败但前端没有对401做专项处理考生就卡死在提交页面。回归里我们模拟了这个场景发现这是“前后端联调中的状态一致性问题”单纯做模块测试很容易漏掉但考试场景一定不能漏——因为真实考生中就是会有人开着页面发呆半小时。第三个缺陷在学情分析摘要的数据脱敏上。成绩单里的学生姓名是明文模型生成摘要时被带入了输出内容中。比如“张三同学本次成绩为85分表现优秀”按设计应该输出“该同学”而不是直呼其名。这个问题的严重性取决于系统所在机构的数据合规要求但对于一个面向学生的系统来说直出学生姓名显然是个需要处理的隐患。修复方式是在调用模型之前对输入字段做脱敏清洗输出时再替换回来。因为摘要可能包含多位学生替换逻辑需要做基于占位符的映射不能简单做字符串替换否则会引起姓名错误。5.3 Bug的等级判定与回归结果评估逻辑缺陷等级的划分在这一轮回归中也遇到了一个有意思的讨论第二个缺陷token过期导致提交失败算P1还是P2按影响面看它只在“考生长时间停留页面”这个条件下触发频率不算特别高但按业务影响看这会导致一场考试里某个考生彻底无法交卷在监考老师看来就是“系统故障”负面影响非常大。最终定级还是提到了P1理由不是“发生频率”而是“发生时用户无路可走”——没有任何绕过机制。这是一个很典型的回归测试判级思路频率低但影响严重且有传播风险的缺陷比频率高但容易绕过的缺陷更危险。回归结论的评估不应该只看“通过率多少”。我习惯用一个四象限来判断回归是否通过阻塞发布第一象限P0/P1缺陷且影响核心链路——必须修复后再回归不允许带病发布第二象限P2缺陷但绕过成本低比如管理员手工调整某条配置即可恢复——可以记录Known Issue排期修复第三象限P3级缺陷属于体验优化层面——直接进Backlog第四象限用例设计本身不合理——不算缺陷修正用例这一轮回归没有一个缺陷落在第一象限所以整体测试结论是“有条件通过”第二个缺陷作为P1记录但因为可以被管理员介入重置token状态属于第二象限的可绕过风险允许进入下一开发周期修复但上线时要通知运维人员知晓这个风险点。6. 复盘与改进联合回归测试方案可以长期沉淀的地方6.1 把联合回归的用例沉淀成可复用的自动检查集做完整轮回归之后我认为这个项目最值得沉淀的不是这100多条用例的执行结果而是用例背后的回归基线数据。为了让下一轮回归不用重复造数据我把这次构造的所有基础数据和边界数据做成了三份SQL脚本和一份JSON配置包base_data_release.sql包含题库、试卷、考试、学生等基础主数据可直接恢复到测试环境boundary_time_data.sql包含提前创建的多个时间窗口的考试场次标记了每场的边界含义ai_prompt_case_config.json存储智能组卷和学情分析专项回归的prompt参数和期望断言这样一来下一轮迭代如果只改了试卷模块的某个字段我不需要手工重新准备一套数据把这三份脚本执行一遍再根据迭代内容增量构造少量数据然后直接跑自动检查集即可。这才是回归测试能持续做下去的底层支撑——测试数据的复用能力比用例数量更关键。6.2 联合回归中的异步链路监控与日志追踪还有一个必须做的复盘动作——补全异步链路的监控点。考试自动判分、学情分析摘要生成这两个功能都不是同步返回结果的它们走的是异步任务。在回归中异步任务的问题往往不是功能不跑而是跑得慢、失败后无感知、数据状态不一致。这一轮的学情摘要缺陷本质上就是异步生成过程中对输入数据处理不当导致的。这里有一个建议在测试环境里要确保异步任务的日志能和当前测试产生唯一关联标识。我这次的做法是在考试提交时拿到了一个业务流水号然后在日志链路里按这个流水号追踪判分和摘要生成的完整经过。如果你们项目还没有做全链路traceId至少要在测试环境的日志系统里给每一个考试场次打上一个唯一标识否则回归中异步任务出问题你连排查入口都找不到。6.3 下一轮回归前要补的用例缺口最后记一下这轮回归结束后我认为下轮必须补的用例缺口也给大家作个参考一是数据迁移类回归。考试系统在正式环境中已经跑了一段时间后历史考卷的格式可能和当前代码结构不一致比如老试卷的题目数量、分值方式都和新版不同。下轮应当补一条“携带一批老数据跑考试主链路”的回归用例确保系统向后兼容。二是通知链路的回归。考试发布后系统需要给考生推送通知邮件或站内信。这轮回归主链路里我有意没覆盖通知环节因为通知是旁支功能但说实话真实考生能不能收到考试通知直接影响一场考试能否顺利开展下一轮还是得纳入联合回归范围。三是考试端异常断电/断网的恢复路径。当前测试用例都是理想网络环境的覆盖没有设计“考生提交过程中断网重连后本地缓存答卷与服务器状态不一致”的场景。这一块在考试系统里属于低频但高破坏的故障从产品层面也需要明确行为预期。这些缺口会直接作为下一轮回归测试的待办写入测试计划。做回归测试就是这样每一轮都在和上一轮“赛跑”只有把新路径不断纳入防线功能迭代才不会让旧问题死灰复燃。像我做了这几年组件工程和业务系统之后最大的体会是——回归测试永远不是“跑一遍用例”那么简单它更像是一次对系统记忆的整理每一次执行都能帮你重新确认这堆代码在你上一次改动之后依然还认识它应该认识的人、记得它应该记得的事。如果你正在做考试系统、管理系统这一类的项目不妨就把第一个模块的完整主链路和关键状态流转当作你的第一份回归基线先跑通再慢慢加厚它。