做了一个 AI 测试工作台,把需求分析、代码校验、报告校准串起来
做了一个 AI 测试工作台,把需求分析、代码校验、报告校准串起来
为什么要做
用 AI 帮忙做测试分析这件事,聊天窗口其实能做——把需求文档贴进去,让它分析;把代码 diff 贴进去,让它找问题。但每次都要重新贴一遍上下文,分析结果也留不下来。更头疼的是,AI 报的"风险点"里有一半是无效的,每次都要人工过滤一遍。过滤完的经验也留不住——下次换个项目,同样的误报又出来了。
我想要的是一个带记忆的测试工作台:任务创建一次、素材关联一次,反复跑分析;每次分析结果持久保存,人工标注哪些是真问题、哪些是误报;标注数据回流到下次分析的提示词里,让 AI 逐渐学会这个项目的模式。
技术选型和整体架构
前端 React 19 + TypeScript + Vite,后端 Spring Boot 3.3.4 + Java 17 + MyBatis,数据库 SQLite。跑在本地,不需要部署服务器。LLM 接口用 OpenAI 兼容格式,支持 OpenAI、DeepSeek、OpenRouter、Ollama——配好 baseUrl 和 apiKey 就行。不支持 Anthropic 和 Gemini,因为它们的 API 格式不兼容。
项目是 monorepo 结构,前端在 apps/web/,后端放在根目录的 Maven 模块里。SQLite 里建了 12 张表,经过 6 次 migration 迭代:从核心的 tasks/materials/reports 三张表,逐步加了 LLM 调用审计日志、结构化 findings、一致性知识库。
核心设计:任务驱动,不是对话驱动
整个工具围绕"任务"运转,不是围绕"对话"。一个任务对应一次测试活动,包含素材(需求文档、代码 diff)、场景绑定(每种分析场景独立配置模型、提示词、规则)、运行记录和报告。
四种分析场景:需求分析、一致性检查(需求 vs 代码对比)、diff 分析、报告校准。其中一致性检查最复杂——拿需求文档和代码变更逐条比对,检查每个需求点是否被代码覆盖。
报告生成:批次处理和容错
报告生成是后端最重的逻辑。触发一次运行后,后端异步执行:
提示词构建分三层。 系统 prompt 先声明身份("你是一名资深测试开发工程师,不要解释你自己"),然后注入场景特定指令——一致性检查要求输出合法 JSON(协议版本 consistency_findings_v3,带 schema 模板),其他场景要求输出 Markdown(必须包含"当前结论"、"风险点"、"建议测试方向"三个段落)。最后附上当前 skill 规则的正文和 10 多条输出约束。
用户 prompt 则由任务元信息、规则补充、知识库参考、材料内容拼接而成。
大 diff 自动分批。 代码变更超过 22000 字符就触发批次模式。文件按改动行数降序排列,逐个累加到当前批次,超出预算就新开一批。每个文件保持完整(不截断 hunk),但单文件超 22000 的会独立成批。每批独立调一次 LLM,最后合并所有 findings,重新编号(CC-001、CC-002……)。批次失败只记 warning,不中断——这样即使某批因为模型抽风返回了无效结果,其余批次的分析依然可用。
JSON 解析三级容错。 模型不一定老老实实返回 JSON。后端做了三级策略:先直接解析,失败了尝试去掉 markdown 代码块标记再解析,还不行就做大括号匹配提取。另外 MiniMax 的模型经常把结果放在 reasoning 字段而不是 content 里,所以还有一个 reasoning 回退——content 为空就从 reasoning 提取。
LLM 调用全部记日志。 每次调用的请求参数、tokens 消耗、耗时、HTTP 状态码、错误信息都写到 model_request_logs 表。后面排查"这次分析为什么结果偏了"时,可以直接翻日志看当时发送了什么 prompt、模型返回了什么。
一致性知识库:让标注数据回流
这是整个工具的核心设计思路——AI 分析结果不只是看一眼就扔掉,而是通过人工标注形成持久化的知识。
流程是这样的:一致性检查输出结构化 findings(JSON 数组,每条 finding 包含类别、标题、需求原文引用、代码文件路径、代码片段、置信度)。人工逐条标注——"命中"(确实是问题)或"误报"(不是问题)。标注结果同步写入 consistency_knowledge 表,关联项目名和场景类型。
下次同一个项目跑一致性检查时,提示词构建阶段会从知识库里拉取最近 6 条"命中"案例和 4 条"误报"案例,拼接到用户 prompt 里,告诉 LLM:"这些是之前判定过的正例和反例,只有当前需求和代码都支持时才输出 finding。"
这样每次标注都在喂给下一次分析,误报率会逐渐降低。不过目前知识库条目数还不多,效果需要更多数据验证。
提示词约束:控制 AI 输出质量的关键
一致性检查的输出约束规则写了 12 条以上,几条比较关键的:
codeQuote必须是最小节选(3-8 行),禁止贴整个文件- diff 格式只能包含命中问题的 hunk,不能包含整个 diff
- 低置信度问题不要丢掉,在 findings 里输出但标注 confidence
- bugfix 需求优先检查主流程逻辑,其次才看辅助配置
- 最多返回 20 条 findings(防止模型刷 findings 数量凑字数)
这些约束是一条一条踩坑加上去的。比如最早没限制 codeQuote 长度,模型会把整个文件贴进 findings,每条 finding 几百行代码,看着很"全面"但完全没用。加了 3-8 行限制后,finding 质量明显提高。
前端:乐观更新和状态回滚
前端数据层用 TanStack Query(react-query),配置了 retry: false(LLM 调用失败重试没意义,模型不行换模型)和 refetchOnWindowFocus: false(本地工具不需要跨 tab 同步)。
创建运行(触发 LLM 分析)做了乐观更新:点击按钮的瞬间,前端立刻在任务详情页显示一个"报告生成中"的占位卡片(临时 ID),同时保存当前数据的快照。后端异步生成完成后,用真实 reportId 替换临时 ID;如果失败,用快照数据回滚到点击前的状态。这样用户不用盯着 loading 等——点完就能去看其他任务,回来时报告已经生成好了。
校准保存也类似:每条 finding 的标注独立保存,保存成功后乐观更新本地缓存,同时级联失效任务详情和 Dashboard 的查询缓存。
踩的坑
校准 UI 断了。 后端 API 和数据模型都做好了,前端的 ReportPreviewPanel 组件骨架也写了,但没接到 ReportPage 里。等于最关键的"标注 → 知识库回流"链路在界面上是断的。目前标注只能通过 API 直接调用。
冷启动成本太高。 第一次用要先配模型端点、写提示词、上传 skill 和 rule、创建模板、设全局默认值,至少跳 5 个页面点十几次。做了默认模板机制但还是不够——应该做一个一键导入的向导页。
diff 裁剪截掉关键变更。 批次模式按改动行数排文件优先级,行数多的优先。但有时候改动最少的那个文件才是关键(比如一个 if 条件改了一行)。自动排序把它排到了最后一批,如果前面批次已经用完预算,这个文件就被截掉了。暂时没有好的自动排序策略,只能手动调整。
场景绑定不能内联改。 想给某个任务的一致性检查换个模型,得去全局设置页面改绑定。BindingSelectorGroup 组件写了但没嵌进任务详情页。应该做成在任务卡片上直接下拉选择。
不同模型的输出格式差异大。 要求 LLM 返回 JSON,DeepSeek 和 OpenAI 基本能遵守。MiniMax 经常把结果放在 reasoning 字段。Ollama 跑的本地模型有时返回半截 JSON。三级 JSON 解析容错 + reasoning 回退基本能兜住,但偶尔还是会解析失败变成空报告。
现在的样子
前端 13 个页面、24 个组件,约 3500 行 TypeScript。后端 15 个 Controller、15 个 Service,约 4000 行 Java(其中 ReportGenerationService 一个类就 500 行)。数据库 12 张表、6 个 migration。SQLite 支持 100MB 的素材上传。总代码量一万行出头。
能跑,核心流程通了——上传需求 → 触发分析 → 看报告 → 下次分析带上历史知识。但校准 UI 没接上、场景绑定不能内联改、批量操作没做。这些都记在了改进文档里,留着逐步补。

浙公网安备 33010602011771号