多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

AI自动化识别需求文档生成测试用例:关键技术与落地实践

AI自动化识别需求文档生成测试用例:关键技术与落地实践 简介这是一份面向测试人员、产品经理与业务分析人员的工具操作手册介绍 Req2TestCase 如何将 PRD、自然语言需求以及 Office/PDF/图片中的需求自动转换为功能测试用例。手册按工具简介、支持能力、安装启动、模型配置、文档导入与 OCR、需求点预分析、用例生成、编辑补齐、历史对比、结果导出等章节组织并配有主界面和生成结果截图便于对照使用尤其适合想用 AI 降低手工编写用例成本的中级测试从业者。资源包仅 1 个 docx 文档约 7.4MB是 V1.0 操作手册而非安装程序目前已有 179 人学习。读者可从中掌握 DeepSeek、豆包、千问、智谱 GLM 等模型 API 配置方法以及长文档分段生成、覆盖矩阵检查、缺口用例补齐和禅道 CSV 导入导出等关键操作并了解扫描型 PDF 的本地 OCR 处理、历史版本差异对比、多格式导出等细节可直接用于实际测试设计效率提升。1. AI自动化识别需求文档生成测试用例先回答一个现实问题一个迭代版本的需求文档动辄几十页其中真正能指导测试的可能只有几段业务规则和验收标准。手工把这几段拆成用例熟练的测试工程师也要花上大半天等用例评审完常常已经逼近提测节点。AI自动化识别需求文档生成测试用例工具解决的就是这个时间差让模型先读文档、抽取可验证的规则、再按测试设计套路批量产出初稿人只做复核和补漏。它适合三类人被用例撰写占据大量时间的测试工程师想统一团队用例标准的测试负责人以及正在评估AI落地价值的研发效能团队。但必须先说实话——这个方向值得做却不是一个开箱即用的玩具。识别效果、生成质量、与现有工程的衔接方式每一个都决定它到底能帮你省时间还是给你添乱。2. 从需求文档到结构化输入识别与抽取这一步决定了生成质量上限2.1 为什么先抽取再生成而不是直接把整篇文档丢给大模型很多人第一次尝试这个工具时习惯直接把整个需求文档粘贴给大模型说一句“帮我生成测试用例”。结果是看似生成了一堆用例细看却没法用用例里出现了文档里根本没定义的“高级用户”前置条件写着“已登录系统”但系统压根没有登录模块断言全是“系统正常响应”。这不是模型能力不够而是输入方式错了。需求文档是给项目成员读的叙述文本里面有背景介绍、技术方案、历史遗留说明真正可以作为测试依据的内容只占一部分。模型分不清哪些是废话、哪些是规则就会在无关上下文里“脑补”出用例来。所以常见的做法是拆成两步先做需求识别与抽取把非结构化文档变成结构化条目再做用例生成基于结构化条目按测试设计方法产出用例。第一步输出的质量直接决定了第二步的天花板。抽取这一步的核心不是让模型“理解”文档而是让模型“提取”文档里可验证的信息业务规则、输入约束、前置条件、验收标准。提取不出来或提取错了后面的生成环节再精巧也白搭。2.2 切片与结构化抽取的实现一个最小可跑的抽取脚本实际操作中需求文档很少是干净的纯文本常见来源是Markdown、Word导出、Confluence页面复制。我一般先把所有格式统一成文本然后按章节切片再逐块交给大模型抽取结构化条目。切片必须做因为大多数模型的上下文窗口虽大但把整篇长篇文档一次性塞进去抽取的精细度反而下降还会漏掉长文档尾部的权限规则。下面这个脚本是一个最小可跑的抽取骨架你把它接到自己实际使用的模型网关就能跑起来# extract_requirements.py # 把需求文档切成块逐块抽取结构化需求条目 import json from typing import List class LlmClient: def chat(self, messages, temperature0.2, max_tokens2000): # 替换成你实际使用的模型网关封装 # 返回的是模型输出的纯文本 raise NotImplementedError def slice_document(text: str, max_chars: int 3000) - List[str]: # 优先按标题行切分避免把两个章节的内容截进同一块 lines text.splitlines() chunks, cur, cur_len [], [], 0 for line in lines: # 识别Markdown标题和中文需求文档常见章节特征 if line.startswith((#, ##, 第)) and cur_len 500: chunks.append(\n.join(cur)) cur, cur_len [], 0 cur.append(line) cur_len len(line) if cur: chunks.append(\n.join(cur)) return chunks def extract_entries(block: str, client: LlmClient) - List[dict]: prompt f 你是测试需求分析助手。从下面这段需求文档中抽取可作为测试依据的信息。 只输出JSON数组每项字段含义如下 user_story: 一句话描述用户场景 rule: 具体的业务规则或约束原文没有就不写 preconditions: 前置条件列表原文没有则为空 acceptance: 可验证的验收标准尽量保留原文关键词 无法从原文确认的字段留空禁止编造。 文档内容 {block} resp client.chat( [{role: user, content: prompt}], temperature0.2, max_tokens2000 ) # 容错模型偶尔会在JSON前后加说明文字剥掉围栏标记 text resp.strip() if text.startswith(): text text.strip().removeprefix(json).strip() arr json.loads(text) return arr代码里的切片逻辑有个容易被忽略的点if cur_len 500这个条件是为了避免在标题行密集出现时切出碎片块保证每个chunk至少有500字的内容量再切。如果文档标题很多但正文很短不加这个条件会切出大量半截块抽取时模型拿不到完整上下文规则容易断章取义。temperature0.2是抽取阶段的推荐值抽取是确定性任务温度太高会让模型发挥“创造性”把文档没有的规则也抽出来。max_tokens2000是给单次输出的上限如果某个块内容特别多宁可让输出截断也不要无限扩大截断后可以把没抽完的块再切细一轮。2.3 抽取结果的字段设计与质量校验抽取结果的字段设计直接影响后续生成用例的灵活度。我见过的失败案例是字段太少只抽了“标题”和“描述”结果生成用例时缺少规则约束模型只能凭印象写。推荐至少保留五个字段user_story用于场景理解rule用于正向和反向用例设计preconditions用于前置条件搭建acceptance用于断言设计再加上一个原文来源标识用于追溯。如果需求文档里包含接口定义或数据字典还应该单独抽一个字段约束表比如字段名、类型、长度、是否必填、枚举值这些是参数化用例的直接素材。质量校验放在抽取之后、生成之前用一条简单的规则就能拦住大部分低级错误凡是模型在rule字段里给出的内容必须在原文中能找到对应关键词找不到的标记为uncertain人工决定去留。这一步是防止模型“幻觉”最廉价的手段。抽出来的条目还应该做一次去重同一份文档里描述相近的规则合并成一条否则后面生成的用例会大量重复。常见做法是用文本相似度粗筛把相似度超过0.85的条目归并归并时保留信息更完整的那个。3. 测试用例生成策略场景覆盖、参数化与断言的三个必调参数3.1 正向、反向、边界三类用例的生成模板设计有了结构化的需求条目生成用例就不是让模型自由发挥了而是给它明确的模板约束。我一般把用例模板分成三类正向用例验证功能在合法输入下按预期工作反向用例验证系统在非法输入或越权操作下能明确拒绝边界用例验证数值范围、长度限制、枚举值临界点上的行为。三类模板的prompt需要分别设计因为它们关注的侧重点完全不同。反向用例最容易诱发模型编造因为模型总倾向于“让系统正常工作”。所以反向模板里必须强调“预期结果是系统明确拒绝”并且要求给出拒绝的具体表现比如返回错误码、置灰按钮、提示文案。边界模板则要给出明确的数值策略等于边界、略小于边界、略大于边界各一条。一个常见的翻车点是模型只会写“边界值”三个字而不给出具体数据解决方法是把需求条目里的字段约束直接拼进prompt让它照着约束里的数值范围生成。# generate_cases.py # 基于结构化需求条目生成测试用例JSON import json TEST_TEMPLATES [ { category: 正向, instruction: 设计一条能验证功能正常完成的用例输入数据要合法。 }, { category: 反向, instruction: 设计一条试图绕过业务规则的用例输入非法或越权预期结果是系统明确拒绝。 }, { category: 边界, instruction: 针对数值范围、长度限制、枚举值边界设计用例覆盖等于边界、略小、略大三种情况。 }, ] def build_case_prompt(entry: dict, template: dict, context: str) - str: return f 你正在为下面这条需求生成测试用例。 需求条目{json.dumps(entry, ensure_asciiFalse)} 系统上下文{context} 用例类型{template[category]} {template[instruction]} 输出格式{{ case_id: TC-{entry.get(story_id, UNKNOWN)}-001, title: 一句话描述用例目的, preconditions: [], steps: [], expected: , data: {{}} }} 只生成一条用例。标题里体现场景和类型。不要输出额外字段。 这个模板里最关键的变量是context也就是系统上下文。它可以是“该系统有登录态角色分为管理员和普通用户”这样一句话也可以是接口鉴权方式、环境地址等。我建议在抽取阶段就把系统级信息单独维护一份生成时拼进每个prompt。这样做的原因是模型不太可能记住你上一次对话里的系统设定每次生成都是独立调用上下文显式传入才能保证前置条件不跑偏。三个类的生成比例我一般控制为正向40%、反向35%、边界25%这个比例覆盖了主要风险又不至于用例爆炸。3.2 参数化与用例去重控制生成数量而不是无限发散如果不加约束模型会为同一个规则生成大量看起来不同、实际步骤雷同的用例比如三条用例的差异只是把“用户名”换成了“user1”“user2”“user3”。这就是用例爆炸。控制手段有两个一是在prompt里限制“同一规则最多生成N条”二是生成后做一次去重。去重不能只看标题要看steps和data的组合是否重复。我常用的去重方法是把输入数据序列化后算一个hashhash相同直接丢弃。更精细一点的做法是先用标题做粗筛再对粗筛保留的用例做一次步骤级比对。下面是一个简单的实现# dedup_cases.py # 基于标题和步骤的归一化去重 import hashlib import json import re def normalize_case(case: dict) - str: # 归一化去掉标点差异和具体数据值只看步骤结构 steps case.get(steps, []) clean_steps [] for step in steps: text re.sub(r[^\w\u4e00-\u9fa5], , str(step)) # 把具体数字替换成占位符避免边界值不同导致误判重复 text re.sub(r\d, N, text) clean_steps.append(text) return json.dumps(clean_steps, ensure_asciiFalse) def is_duplicate(case: dict, seen: set) - bool: key hashlib.md5(normalize_case(case).encode(utf-8)).hexdigest() if key in seen: return True seen.add(key) return False这个归一化的细节值得说一句为什么要把数字替换成占位符因为边界用例里“输入金额100元”和“输入金额101元”是两条不同的用例但如果把数字保留下来它们会被判成不重复事实上除了数值其他断言都一样在批量生成场景下属于冗余。而反过来把数字全替换成占位符又会让“金额100元成功”和“金额100元失败”被误判为重复——所以真正稳妥的做法是先按用例类型分组再在组内做归一化去重。同一组里数字不同但结构相同的才合并。3.3 断言生成从验收标准到可执行断言断言的生成是AI生成用例里最容易被糊弄过去的一环。模型默认输出“系统正常响应”“操作成功”这种断言写进用例里等于没写。要让断言可执行必须把它和验收标准里的可观察结果绑定。比如验收标准写“退款金额原路返回至支付账户”那断言就应该是“返回成功的支付网关回调且金额等于订单实付金额”而不是“退款成功”。我的做法是在生成断言时把acceptance字段拆成可观察的要素状态码、返回码、页面提示文案、数据库状态变更、异步通知结果、金额计算规则。然后要求模型至少引用两个可观察要素写进expected。如果acceptance字段本身信息不足就把它标记为待人工补充而不是让模型去猜。这一步是质量和效率的平衡点纯靠人工补断言很慢但全交给模型又不可靠做一次“机器生成人工微调”是性价比最高的。4. 把生成结果接进现有测试工程格式对齐、双向追溯与最小可跑链路4.1 输出格式对齐用例JSON与现有管理系统的双向映射生成出来的用例如果只是躺在JSON文件里对团队来说就没有价值。它必须能导入现有的用例管理系统或者直接生成可执行的自动化脚本。每个团队的管理系统字段不同常见的有用例编号、所属模块、优先级、前置条件、操作步骤、预期结果、关联需求。这里不需要为每个系统都写一套适配器实际上有一条通用路径把生成的用例JSON作为中间态再写一层薄映射到目标格式。映射层要处理的最大问题是字段名不一致。比如系统里叫“操作步骤”JSON里叫“steps”系统里叫“预期结果”JSON里叫“expected”。这种映射看起来简单但真正麻烦的是多值字段的拼接方式比如preconditions可能是一组列表管理系统里可能只有一行文本那就需要用分号或换行符拼接。一个容易被忽视的点是枚举值字段的映射管理系统里用例优先级只接受“高/中/低”AI生成的可能是“P0/P1/P2”映射层要负责转换而不是原样写入。# adapt_to_system.py # 把AI生成的用例JSON映射为指定系统的导入格式 import json import re def map_to_excel_row(case: dict, module: str) - dict: # 对齐用例管理系统常见字段 return { 用例编号: case[case_id], 所属模块: module, 优先级: map_priority(case.get(priority, medium)), 前置条件: ; .join(case.get(preconditions, [])), 操作步骤: \n.join(f{i1}. {s} for i, s in enumerate(case.get(steps, []))), 预期结果: case.get(expected, ), 关联需求: case.get(requirement_id, ) } def map_priority(priority: str) - str: mapping {high: 高, medium: 中, low: 低} return mapping.get(priority.lower(), 中)这个映射函数里有个细节值得注意操作步骤的拼接用了1.这种编号前缀。很多管理系统支持富文本但不支持自动编号如果你直接把steps列表原样贴进去导入后可能丢失顺序。编号前缀是通用做法到了系统里即便格式拉平阅读者依然能看出执行顺序。另一个细节是关联需求字段我建议把抽取阶段的requirement_id一路透传到这里不只在用例内部保留还要写入管理系统这样后续不管是统计覆盖率还是追查漏测都能从需求侧反查。4.2 从需求条目到条用力的双向追溯case_id的编码规则双向追溯是AI生成用例工具和普通文本生成工具最大的区别。用例必须能回答两个问题这条用例是从哪条需求来的这条需求生成了哪些用例做不到这一点生成结果只能当草稿不敢进正式测试流程。我的做法是在case_id里编码来源信息格式是TC-{源需求编号}-{序号}。比如TC-REQ-014-003表示来自编号014的需求条目这是本条目下第3条用例。这个编码规则虽简单但要注意源需求编号也要有讲究。最好不要用抽取阶段模型自己生成的编号因为模型生成的编号不稳定同一份文档跑两遍可能编号就变了。正确做法是给每个结构化条目在入库时分配稳定的自增id并把原文里的章节位置记录在同一条记录里。这样哪怕后面修改文档增量重跑旧的用例编号依然对应旧的需求条目不会错位。case_id一旦写入系统就不允许变更只能在确认废弃时做状态调整。4.3 生成pytest可执行骨架把AI结果变成能跑的脚本对于接口类测试生成的可执行脚本价值立竿见影。如果需求文档里有接口定义哪怕是近似的路径和参数都可以让AI生成requests调用的骨架再由测试工程师补充鉴权和数据准备。下面是把用例JSON转成pytest用例的适配器# adapt_to_pytest.py # 把用例JSON转成pytest可执行骨架 import json def case_to_pytest(case: dict, module_name: str) - str: data case.get(data, {}) payload_lines [] for k, v in data.items(): payload_lines.append(f {json.dumps(k)}: {json.dumps(v, ensure_asciiFalse)},) payload \n.join(payload_lines) if payload_lines else {} func_name test_ re.sub(r[^a-z0-9], _, case[case_id].lower()) return f # 生成来源{case[title]} # 前置条件需人工确认{case.get(preconditions, [])} def {func_name}(): url {case.get(url, )} payload {{ {payload} }} resp requests.post(url, jsonpayload) assert resp.status_code 200, resp.text # TODO: 按业务验收标准补充断言 assert resp.elapsed.total_seconds() 3 这段骨架里我故意留了一个TODO和一条兜底断言resp.elapsed.total_seconds() 3。留TODO是因为AI生成的业务断言需要人工确认硬塞一条看似高深的断言反而会误导后续维护的人。兜底超时断言则是为了保证这个用例就算没补充业务断言也至少能拦截超时这类基础问题不会让一个空壳用例混进测试报告。参数说明上module_name参数在函数里没用上实际使用时会拼进文件名或测试类名这里保留它是为了提醒你不同模块要落到不同的测试文件里不要全部塞进一个文件否则后续定位失败用例要多花一倍时间。5. 生成用例实战避坑最常翻车的五个问题与排查路径5.1 现象一需求文档里的废句被当成真需求现象AI把“系统应支持多语言”这种描述生成了一堆用例但文档里根本没有定义支持哪几种语言用例也没法执行。原因抽取阶段没有区分“需求意图”和“可验证规则”模型把模糊表述也抽成了rule字段。解决抽取prompt里加一条硬约束——rule字段必须是包含具体对象、动作、约束条件的句子无法满足的就标记为uncertain交给人工判断。同时用关键词清单做一轮后置过滤像“系统应”“建议”“待定”这类词开头的条目直接降级为待确认。5.2 现象二断言太弱只验证了状态码现象生成的接口用例全部是assert resp.status_code 200一跑全绿但业务逻辑错得离谱也一样通过。原因断言模板默认取最容易生成的字段状态码是模型最熟悉的“安全选项”。解决生成prompt里强制要求expected必须引用验收标准里的可观察结果比如数据库记录状态、回调通知内容、金额计算值并在适配层做校验如果expected长度不足15个字就拦截下来不让入库。这条规则会让一部分用例被打回重写但能显著提高生成结果的有效率。5.3 现象三同一条规则被重复生成造成回归冗余现象同一个需求条目下出现了三条用例标题分别是“正常创建订单”“正确创建订单”“订单创建成功”操作步骤几乎相同。原因生成时没有在组内去重模型对语义近似的表述没有稳定判断。解决生成阶段把temperature降到0.2以下让模型输出更收敛生成后用3.2节里的归一化去重脚本做组内过滤。还有一个隐蔽原因需要排查如果抽取阶段本身没去重同一个规则被抽成两条相似条目那不管生成阶段怎么去重都没用要回到抽取结果里查。5.4 现象四前置条件引用了不存在的系统状态现象用例的前置条件写“用户已登录”但被测系统根本没有登录功能或者写“存在一条审批中的订单”但业务上审批单创建后立即生效。原因模型根据通用业务知识做了补全而不是基于文档上下文做推断。解决生成时把系统上下文显式拼进每个prompt上下文里列清楚系统有的角色、状态、能力边界同时在前置条件字段加一行生成规则“只能引用上下文和需求条目中出现过的状态否则留空。”留空比补全更安全人工补前置条件一分钟就能完成排查一条幻觉前置条件可能花半小时。5.5 现象五长文档跨章节依赖在切片后被切断现象需求文档前面定义“会员折扣根据用户等级计算”后面在订单章节才写“折扣只对自营商品生效”。单独看每个块都正常生成的用例却丢了“折扣排除第三方商品”这个规则。原因切片按照标题切分后跨章节的公共规则被隔离在不同的chunk里抽取时模型看局部看不到全局。解决切片后做一轮跨块依赖扫描把所有包含“会员”“折扣”“权限”“额度”这类跨章节关键词的段落收集起来连同对应规则一起绑定抽提。也就是说切分不是一刀切的物理操作还要有一层逻辑合并。这一层做不做决定了长文档场景下工具的可用性上限。6. 从“能生成”到“敢用”效果度量的四个指标与人工复核边界6.1 用四个指标度量生成质量引入这个工具后团队最关心的问题是“它到底省了多少时间质量掉没掉”。我建议至少跟踪四个指标需求覆盖率即已生成用例的需求条目占可测试需求条目的比例低于80%说明抽取环节有遗漏用例有效率即评审后无需修改即可执行的用例占比第一轮跑下来低于50%就要回去调抽取prompt重复率即组内去重后的冗余比例超过10%说明生成温度或模板设计有问题缺陷回补率指生成的用例在回归阶段发现缺陷的数量与总缺陷的比例这个指标要连续观察两个迭代才能下结论单次数据没有参考价值。6.2 人工复核的三条边界即使指标看起来不错人工复核仍不能省但可以把复核范围收敛到三条线上。第一前置条件与系统上下文是否相符这条最容易出幻觉第二断言是否包含可观察结果而不是模糊表述第三用例数据是否真实存在比如测试数据里的订单号、用户ID是否在对应环境里能查到。三条线复核完其他内容可以信任AI的输出。评审时间控制在每十条用例五分钟左右超过这个时间说明生成质量在下降需要回到参数层面排查而不是继续硬审。6.3 一个进阶技巧需求diff驱动增量生成全量重新生成用例在第二个迭代版本开始就会变得不划算。我现在的习惯是只对需求变更的部分做增量生成把新版本需求文档和上一版本做diff抽取出新增和修改的段落只对这两类段落走抽取和生成流程未变更的部分继续沿用历史用例。这个做法能让每迭代一轮的AI生成工作量缩减到全量生成的20%左右同时维护成本低很多。具体实现上diff可以用常见的文本对比工具做段落级比较把变更段落替换原文档的同名章节后重新跑一遍抽取但注意保留历史结构化条目的稳定id只更新内容不清空编号这样关联到旧用例的追溯关系不会被破坏。最开始做这个方向时我因为跳过抽取直接让模型读全文被生成结果里虚构的系统状态坑过整整一个迭代。后来老老实实把结构化抽取、组内去重、断言校验这三层加回去工具的可用性才真正立起来。现在每次看生成结果跑红我的第一反应不是怀疑模型而是回头查抽取结果和上下文信息够不够。这个排查顺序救过我好几次。这个方向只要能接受“机器出初稿、人工做裁判”这一定位是值得投入的提效路径希望帮到你。本文还有配套的精品资源点击获取
返回列表