简介Req2TestCase 是一款面向测试人员、产品经理、业务分析与实施人员的 Windows 桌面工具用于将 PRD、自然语言需求、Office/PDF 文档及图片需求自动转换为功能测试用例解决手工编写用例耗时、覆盖不全的痛点。资源包为 1 个 docx 操作手册约 7.4MB系统讲解模型配置、需求导入与 OCR、需求点预分析、长文档分段生成、覆盖矩阵、质量检查、缺口补齐、历史版本对比及多格式导出等完整流程。手册中详细列出 DeepSeek、豆包、千问、智谱 GLM 及自定义 OpenAI 兼容接口的 Base URL 与推荐模型并给出安装启动、常见问题排查等实操指引。已有 177 人学习适合希望借助 AI 提升测试用例编写效率、快速上手工具配置与结果导出的读者参考。1. 需求文档到测试用例为什么 AI 自动化识别是当下最值得投入的提效点产品经理甩过来一份 40 页的 PRD里面夹着流程图、状态机、字段表、异常分支说明要求在两天内输出覆盖全链路的测试用例。这种场景做过功能测试的人都不陌生。传统做法是人工逐段读、逐条拆把「用户输入手机号格式错误时提示」这类自然语言描述翻译成前置条件、操作步骤、预期结果三件套。一份中等复杂度的需求文档熟练测试工程师也要花 6 到 10 小时才能产出可评审的用例集而且漏测率往往在 15% 以上——不是能力问题是人脑在长文本里保持一致性太难。AI 自动化识别需求文档生成测试用例工具要解决的就是这个环节。它的核心链路是解析需求文档的结构化与非结构化内容识别出功能点、业务规则、边界条件和异常路径再按测试用例设计方法等价类、边界值、判定表、场景法自动生成可执行的用例条目。适合两类人一是每天被需求文档淹没的功能测试工程师二是想用 AI Agent 把测试左移落到实处的质量团队负责人。这不是让 AI 替代测试思维而是把重复的文本拆解和格式转换交给机器人只做审核和补充。2. 需求文档解析从 PDF/Word 到结构化功能点的完整链路2.1 为什么不能直接把整份文档丢给大模型很多人第一反应是写个 prompt把 PRD 全文塞进 GPT 或国产大模型让它直接输出测试用例。我试过翻车得很彻底。一份 30 页的文档大约 1.5 万到 2 万字塞进上下文窗口后模型对中间部分的注意力明显衰减生成结果会出现三类问题功能点遗漏尤其是表格里的字段校验规则、前后矛盾同一规则在不同章节描述不一致时模型随机选一个、格式漂移要求输出表格却变成段落。更关键的是需求文档里大量信息是「隐含」的。比如「订单状态从待支付变为已支付后库存扣减」这句话隐含了「待支付状态下库存未扣减」「已支付状态不可重复扣减」「扣减失败时状态回滚」三条测试点。直接让模型生成它大概率只覆盖显式描述的那一条。所以正确的做法是分两步先把文档解析成结构化的功能点列表再针对每个功能点生成用例。解析阶段的目标不是理解业务而是把文档「打散」成可独立处理的单元。2.2 文档预处理格式归一化与分块策略不同来源的文档格式差异很大。Word 里的表格、PDF 里的流程图、Confluence 导出的 HTML处理方式完全不同。我一般先用 Python 做格式归一化统一转成 Markdown 再处理。import fitz # PyMuPDF from docx import Document import markdownify def normalize_doc(file_path: str) - str: 将 PDF/Word/HTML 统一转为 Markdown 文本 if file_path.endswith(.pdf): doc fitz.open(file_path) raw \n.join(page.get_text() for page in doc) elif file_path.endswith(.docx): doc Document(file_path) parts [] for para in doc.paragraphs: parts.append(para.text) # 表格单独提取保留行列结构 for table in doc.tables: for row in table.rows: parts.append( | .join(cell.text for cell in row.cells)) raw \n.join(parts) elif file_path.endswith(.html): raw markdownify.markdownify(open(file_path).read()) else: raise ValueError(f不支持的格式: {file_path}) return raw这段代码的逻辑很直接PDF 用 PyMuPDF 逐页提取文本Word 用 python-docx 分别处理段落和表格HTML 用 markdownify 转换。参数上唯一需要注意的是 PDF 提取时如果文档有双栏排版get_text()会按阅读顺序输出但表格内容可能错位这时候需要额外用page.get_text(dict)按坐标块重组。分块策略是解析质量的分水岭。我一般按「章节标题 内容」的粒度切分而不是按固定字数。具体做法是用正则匹配 Markdown 的##和###标题把文档切成若干语义块每块控制在 800 到 1500 字。这样每个块内部的主题是一致的后续识别功能点时不会跨模块混淆。import re def split_by_section(md_text: str, max_len: int 1500) - list: 按标题切分文档超长块二次切分 pattern r(?^#{2,3}\s) sections re.split(pattern, md_text, flagsre.MULTILINE) chunks [] for sec in sections: if len(sec) max_len: chunks.append(sec.strip()) else: # 超长块按段落二次切分 paragraphs sec.split(\n\n) buf for p in paragraphs: if len(buf) len(p) max_len: chunks.append(buf.strip()) buf p else: buf \n\n p if buf: chunks.append(buf.strip()) return [c for c in chunks if c]参数max_len设 1500 是经验值。太短会丢失上下文太长模型识别时容易漏。如果文档里表格特别多建议把表格单独抽出来作为独立块因为表格里的字段名和校验规则是测试用例的重要来源。2.3 用 LLM 抽取功能点Prompt 设计与输出约束分块之后每个块单独调用大模型做功能点抽取。这一步的 prompt 设计直接决定后续用例质量。我常用的模板是这样的EXTRACT_PROMPT 你是一个需求分析助手。请从以下需求文档片段中提取所有可测试的功能点。 输出要求 1. 每个功能点包含功能名称、触发条件、业务规则、输入字段、预期行为 2. 业务规则要拆到最细粒度例如手机号必须为11位数字而不是校验手机号 3. 如果片段中包含状态流转单独列出状态和流转条件 4. 如果某条规则不完整标注信息不完整而不是猜测 5. 以 JSON 数组输出每个元素包含 name, trigger, rules, inputs, expected 五个字段 文档片段 {chunk} 这个 prompt 的关键约束有三条一是要求拆到最细粒度防止模型偷懒输出笼统描述二是要求标注信息不完整避免模型编造规则三是强制 JSON 输出方便后续程序化处理。实际跑的时候我一般用 temperature0.1 到 0.3保证输出稳定。调用时加一层重试和校验import json from tenacity import retry, stop_after_attempt retry(stopstop_after_attempt(3)) def extract_features(chunk: str, llm_client) - list: resp llm_client.chat( promptEXTRACT_PROMPT.format(chunkchunk), temperature0.2 ) try: features json.loads(resp) except json.JSONDecodeError: # 尝试提取 JSON 代码块 import re match re.search(r\[.*\], resp, re.DOTALL) if not match: raise ValueError(模型未返回有效 JSON) features json.loads(match.group()) # 校验必填字段 for f in features: for key in [name, trigger, rules, inputs, expected]: if key not in f: f[key] 信息不完整 return features重试机制用 tenacity 做三次每次失败后重新调用。JSON 解析失败时用正则兜底提取数组。校验环节确保每个功能点都有五个必填字段缺失的标记为「信息不完整」后续生成用例时这些点会单独提示人工补充。这一步跑完一份 40 页的 PRD 大概能抽出 60 到 120 个功能点取决于文档详细程度。我对比过人工拆解的结果AI 抽取的召回率在 90% 左右主要遗漏集中在跨章节的隐含规则上比如某个字段的校验规则写在附录里但主流程章节没提。所以解析完成后我一般会加一步「跨块关联」把所有功能点的 inputs 字段收集起来看是否有字段在主流程中出现但没在任何规则里定义这些就是需要人工确认的盲区。3. 测试用例生成从功能点到可执行用例的映射方法3.1 测试用例设计方法怎么编码进 Prompt功能点抽取完成后下一步是把每个功能点展开成测试用例。这里不能简单让模型「根据功能点写用例」因为模型默认只会写正向流程。必须把等价类划分、边界值分析、判定表、场景法这几种设计方法显式编码进 prompt。我的做法是给每个功能点生成三类用例正常路径、边界路径、异常路径。正常路径覆盖主流程边界路径覆盖输入字段的临界值异常路径覆盖规则不满足时的处理。对应到 prompt 里CASE_PROMPT 你是一个测试用例设计专家。根据以下功能点生成完整的测试用例。 功能点信息 名称{name} 触发条件{trigger} 业务规则{rules} 输入字段{inputs} 预期行为{expected} 生成要求 1. 正常路径覆盖主流程每个输入字段取有效等价类 2. 边界路径对每个有范围约束的字段取最小值、最大值、最小值-1、最大值1 3. 异常路径对每条业务规则构造不满足规则的输入 4. 每条用例包含用例编号、用例标题、前置条件、操作步骤、预期结果、优先级 5. 优先级规则正常路径 P0边界路径 P1异常路径 P1 或 P2 6. 以 JSON 数组输出 注意如果功能点信息不完整在用例标题中标注[待确认]不要编造规则。 这个 prompt 里边界值的取法需要根据字段类型动态调整。比如字符串长度约束取「长度-1、长度、长度1」数值范围取「min-1、min、max、max1」枚举类型取「每个枚举值 一个非法值」。我一般会在代码里先判断字段类型再拼接到 prompt 里。def build_boundary_hint(inputs: list) - str: 根据输入字段类型生成边界值提示 hints [] for inp in inputs: if 长度 in inp or 位数 in inp: hints.append(f{inp}: 取长度-1、长度、长度1) elif 范围 in inp or 之间 in inp: hints.append(f{inp}: 取下界-1、下界、上界、上界1) elif 枚举 in inp or 可选值 in inp: hints.append(f{inp}: 取每个合法值 一个非法值) return \n.join(hints) if hints else 无特殊边界要求这段逻辑把字段描述里的关键词映射到边界值策略。实际项目中字段描述往往不规范比如「手机号」不会写「长度约束」但常识是 11 位。这种情况我一般维护一个字段类型映射表把常见字段名映射到约束类型作为兜底。3.2 生成结果的去重与优先级排序模型生成用例时有个常见问题同一个测试点在不同功能点下重复生成。比如「用户名为空」这个异常在注册功能点和登录功能点下都会出现。如果不去重用例集会有大量冗余。去重策略我一般用「前置条件 操作步骤」的文本相似度做判断相似度超过 0.85 的合并保留优先级高的那条。具体用 difflib 或 sentence-transformers 都行前者轻量后者准确率高但需要额外模型。from difflib import SequenceMatcher def dedup_cases(cases: list, threshold: float 0.85) - list: 基于操作步骤相似度去重 unique [] for case in cases: key case.get(前置条件, ) case.get(操作步骤, ) is_dup False for u in unique: u_key u.get(前置条件, ) u.get(操作步骤, ) ratio SequenceMatcher(None, key, u_key).ratio() if ratio threshold: is_dup True # 保留优先级更高的 if case.get(优先级) u.get(优先级): u.update(case) break if not is_dup: unique.append(case) return unique阈值 0.85 是调出来的。太低会误合并不同场景的用例太高去重效果不明显。优先级排序按 P0 P1 P2 排同一优先级内按功能模块分组方便后续分配到不同测试轮次。3.3 输出格式适配从 JSON 到 TestRail/Excel/禅道生成的用例最终要导入测试管理工具。不同工具支持的导入格式不一样TestRail 支持 CSV禅道支持 ExcelJira 需要插件。我一般先统一输出 JSON再写转换脚本适配不同格式。import pandas as pd def export_to_excel(cases: list, output_path: str): 导出为禅道可导入的 Excel 格式 rows [] for c in cases: rows.append({ 用例编号: c.get(用例编号, ), 所属模块: c.get(模块, ), 用例标题: c.get(用例标题, ), 前置条件: c.get(前置条件, ), 操作步骤: c.get(操作步骤, ), 预期结果: c.get(预期结果, ), 优先级: c.get(优先级, P2), 用例类型: c.get(类型, 功能测试) }) df pd.DataFrame(rows) df.to_excel(output_path, indexFalse)导出时注意操作步骤里的换行符Excel 里需要用\n并设置单元格自动换行否则导入禅道后步骤会挤在一行。我一般在写入前把步骤里的换行统一替换成\n并在 pandas 的 ExcelWriter 里设置wrap_textTrue。4. 避坑与排查AI 生成测试用例的 5 个血泪教训4.1 现象生成的用例全是正向流程异常路径几乎为零原因模型默认倾向于生成「顺利走通」的用例除非 prompt 里显式要求异常路径否则它不会主动构造非法输入。另一个原因是功能点抽取阶段就没有把异常规则拆出来导致生成阶段无据可依。解决在功能点抽取的 prompt 里强制要求列出「异常规则」比如「当输入不满足 XX 时系统应 YY」。生成用例时对每条异常规则单独生成至少一条用例。我一般会在后处理阶段检查异常用例占比低于 30% 就重新生成。4.2 现象边界值取错比如手机号取了 10 位和 12 位但没取 11 位原因模型对「边界」的理解和测试工程师不一致。它可能认为 10 和 12 是边界但实际测试中 11 位才是有效边界10 和 12 是无效边界。如果 prompt 里没写清楚模型会随机取。解决在 prompt 里明确「边界值必须包含有效边界和无效边界」并给出示例。更稳妥的做法是在代码里根据字段约束自动计算边界值作为提示注入 prompt而不是让模型自己算。4.3 现象同一份文档跑两次生成的用例数量和内容差异很大原因大模型的输出有随机性即使 temperature 设得很低不同批次的调用结果也会有波动。如果文档分块后各块独立调用块与块之间的功能点关联没有做会导致某些跨模块用例在两次运行中归属不同。解决temperature 设到 0.1 以下并对每个块固定随机种子如果 API 支持。更重要的是加一步「功能点合并」把所有块抽取的功能点按名称去重合并再统一生成用例而不是每块单独生成。这样即使某次抽取有波动合并后的功能点集合是稳定的。4.4 现象用例里的操作步骤写得太笼统比如「输入合法手机号」而不是具体值原因模型在生成时倾向于抽象描述尤其是当功能点信息里没有给出具体示例值时。它不知道「合法手机号」在测试环境里应该用哪个号码。解决在 prompt 里要求「操作步骤中的输入值必须具体化例如输入 13800138000 而不是输入合法手机号」。同时维护一个测试数据字典把常见字段的测试值预先定义好生成时注入 prompt。比如手机号用 13800138000邮箱用 testexample.com身份证号用符合校验规则的测试号。4.5 现象导入禅道后用例步骤全部挤在一行格式全乱原因Excel 单元格里的换行符在导入时被忽略或者导出时没有设置自动换行。禅道对 Excel 的解析比较严格步骤里的序号和换行需要特定格式。解决导出前把操作步骤格式化为「1. xxx\n2. xxx\n3. xxx」的形式并在 pandas 导出时设置wrap_textTrue。如果禅道版本较老可能需要导出为 CSV 并用 UTF-8 BOM 编码避免中文乱码。5. 进阶技巧用 LangChain 把解析和生成串成可复用的 Agent5.1 为什么需要 Agent 而不是一条龙脚本前面拆开讲的解析、抽取、生成、导出如果写成一个线性脚本每次换一份文档都要改代码。实际项目中需求文档的格式、测试管理工具、用例模板都在变。用 LangChain 把这些步骤封装成 Agent 的 Tool可以让模型根据输入自动决定调用顺序也方便后续替换某个环节的实现。我一般把整个流程拆成四个 Toolparse_document、extract_features、generate_cases、export_cases。Agent 的职责是编排不是替代每个环节的逻辑。from langchain.agents import Tool, initialize_agent from langchain.llms import OpenAI tools [ Tool( nameparse_document, funcparse_document, description输入文件路径返回归一化后的 Markdown 文本和分块列表 ), Tool( nameextract_features, funcextract_features_batch, description输入分块列表返回结构化功能点 JSON 数组 ), Tool( namegenerate_cases, funcgenerate_cases_batch, description输入功能点列表返回测试用例 JSON 数组 ), Tool( nameexport_cases, funcexport_cases, description输入用例列表和目标格式导出为 Excel 或 CSV ) ] agent initialize_agent( tools, llm, agentzero-shot-react-description, verboseTrue )这个 Agent 的价值在于当文档格式变化时只需要改parse_document的实现其他环节不受影响。当测试管理工具切换时只需要加一个export_to_testrail的 ToolAgent 会自动选择。5.2 用缓存降低重复调用成本一份文档从解析到生成用例大模型调用次数等于分块数乘以 2抽取一次、生成一次。40 页文档分 20 块就是 40 次调用。如果每次调试都重新跑成本很高。我一般加一层缓存用文档内容的 hash 作为 key把抽取结果和生成结果存本地。import hashlib import json import os CACHE_DIR .cache def cached_call(func, input_text: str, *args): 基于输入文本 hash 的缓存调用 os.makedirs(CACHE_DIR, exist_okTrue) key hashlib.md5(input_text.encode()).hexdigest() cache_file os.path.join(CACHE_DIR, f{key}.json) if os.path.exists(cache_file): return json.load(open(cache_file)) result func(input_text, *args) json.dump(result, open(cache_file, w), ensure_asciiFalse) return result缓存粒度按块做这样修改某一块的 prompt 时只需要重新跑那一块。实际用下来调试阶段的调用成本能降低 70% 以上。5.3 验证生成质量用覆盖率反推解析漏洞生成完用例后怎么判断质量我一般做两件事一是统计用例对功能点的覆盖率每个功能点至少有一条正常路径和一条异常路径覆盖率低于 90% 就检查是抽取漏了还是生成漏了二是人工抽检 10% 的用例看操作步骤是否具体、预期结果是否可验证。覆盖率检查的代码很简单def check_coverage(features: list, cases: list) - dict: 检查功能点覆盖率 covered set() for c in cases: covered.add(c.get(关联功能点, )) total len(features) hit sum(1 for f in features if f[name] in covered) return { 总功能点: total, 已覆盖: hit, 覆盖率: f{hit/total*100:.1f}%, 未覆盖功能点: [f[name] for f in features if f[name] not in covered] }未覆盖的功能点列表直接反馈到抽取环节看是抽取时漏了还是生成时跳过了。我自己的习惯是每次跑完先看覆盖率低于 90% 不往下走先修解析和抽取的 prompt。这个习惯帮我省了很多返工时间——用例生成得再多漏了关键功能点就是白做。希望帮到你。本文还有配套的精品资源点击获取