首页 / 资讯中心 / 文章详情

生成式AI驱动需求分析与测试用例生成实践

生成式AI驱动需求分析与测试用例生成实践 ★ FEATURED ARTICLE
简介Vector Consulting Services发布的PDF报告基于2025年Vector China TechDay技术分享聚焦生成式AI在需求工程与测试环节的落地实践面向汽车电子、嵌入式系统与工业自动化领域的需求与测试工程师、功能安全及网络安全专家。报告用多个案例说明GenAI如何优化需求一致性、可测性与完整性自动生成高覆盖测试用例识别边缘场景、消除冗余并支撑TARA分析、漏洞识别与ISO 26262/ISO 21434标准合规同时介绍了以小型语言模型、语义搜索和RAG构建私有化安全数据库的策略强调工程师需作为“驾驶员”对AI输出进行审查与闭环控制。资料为1个PDF文件约1.77MB已有97人学习下载。读者可从中获得实际项目中的AI提示工程思路、上下文构建方法、人工闭环设计要点以及结合CANoe、vTESTstudio、PREEvision等工具链推进AI辅助持续验证的路径参考。1. 生成式AI在软件工程里的价值不在“自动写文档”而在把模糊需求变成可检查的产物打开需求文档的时候最怕的往往不是写不出来而是写出来的内容每个人理解都不一样业务方嘴里的“查询”开发做成了模糊搜索测试按精确匹配验证上线之后才发现跟用户预期完全对不上。这是我见过最多的一种翻车现场。软件工程里引入生成式AI做需求优化与测试优化核心价值并不是让大模型替你写一份漂亮的文档而是把一段含混的原始描述拆成用户故事、验收标准和测试用例这些可以被检查、被追踪、被反驳的结构化产物。这篇文章适合需求分析师、测试开发、工程效能团队里真正想落地这个方向的人。后续几章会讲可复现的提示词模板、最小可运行的脚本、关键参数怎么设以及最容易让整个方案翻车的几个坑。2. 用生成式AI做需求优化从用户故事到可验收的规格书需求侧的工作通常不是“没有文档”而是“文档里的内容不可验证”。生成式AI在这里可以扮演两个角色一个角色是整理手把零散的对话记录、会议纪要和邮件梳理成统一格式的用户故事另一个角色是考官用反问把模糊的边界暴露出来。这两个角色我都在实际项目里用过稳定性和收益差别很大下面分别展开。2.1 最小闭环两段式提示词把需求草稿变成用户故事常见做法是调用一个兼容 OpenAI Chat Completions 格式的服务内网可以部署通用网关外部则走现成的模型服务。我的建议是第一版不要引入太多依赖直接写一个 Python 函数封装对话接口后续所有需求生成逻辑都复用它。下面这段代码能直接跑起一个“把需求草稿转成用户故事”的闭环import os import requests def llm_completion(system_prompt: str, user_prompt: str, temperature: float 0.2, max_tokens: int 1024) - str: api_base os.getenv(LLM_API_BASE, http://localhost:8000/v1/chat/completions) api_key os.getenv(LLM_API_KEY, ) headers {Authorization: fBearer {api_key}} payload { model: os.getenv(LLM_MODEL, local-llm), messages: [ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], temperature: temperature, max_tokens: max_tokens, } resp requests.post(api_base, headersheaders, jsonpayload, timeout30) resp.raise_for_status() return resp.json()[choices][0][message][content] def user_story_from_raw(raw_text: str) - str: system_prompt ( 你是一名资深需求分析师。只依据输入内容输出用户故事 不得补充不存在的业务规则信息缺失时标注“待澄清”。 ) user_prompt ( 请把下面的需求草稿拆成用户故事输出 Markdown 格式\n 故事标题、角色、功能诉求、业务价值、验收标准。\n 如果草稿缺少某部分请标注“待澄清”不要编造。\n\n f需求草稿\n{raw_text} ) return llm_completion(system_prompt, user_prompt, temperature0.2, max_tokens2048)这段代码的逻辑很直接先固定一个系统提示词把模型“关进”需求分析师的角色里并且要求它不得补充不存在的规则再通过用户提示词把需求草稿喂进去。系统提示词里“只依据输入内容输出”这半句话非常重要没有这条约束模型非常容易自己脑补业务规则。参数方面temperature 我一般设到 0.2不是为了“有创造力”而是希望稳定。生成式AI在需求侧的价值本来就是整理现有信息不是发明业务规则。max_tokens 设 2048是为了避免用户故事被截断尤其是验收标准部分如果写到一半断了后面没法用。timeout 设 30 秒是因为模型服务在首次加载时通常会比较慢设太短会导致可用性下降。2.2 需求澄清会话让模型反向提问把模糊项挖出来生成用户故事只是第一步更大的坑在于原始需求本身就自相矛盾。比如运营说“用户可以查看所有历史订单”但没有说分页条数测试按 100 条分页设计开发却只查最近 30 天上线后自然对不上。与其让模型直接给结论不如让它变成一个“考官”只提问、不回答把模糊点全部列出来。这个思路在需求评审会上非常能用。def requirement_clarification(raw_text: str) - str: system_prompt 你是一个专门做需求评审的测试专家。你的任务是只提问不回答不输出解决方案。 user_prompt ( 请基于以下需求草稿输出待澄清问题清单分为四类 业务目标、用户操作、数据约束、异常分支。\n 每个问题必须与草稿中的原文对应并在括号里引用原文。\n\n f需求草稿\n{raw_text} ) return llm_completion(system_prompt, user_prompt, temperature0.1, max_tokens1536)这里把 temperature 压到 0.1因为提问环节不需要发散发散了反而会让评审会失焦。实际使用中我会把生成的问题清单打印出来逐个在需求例会上过一遍。这个做法的好处是问题清单本身天然带有需求原文引用业务方回答时可以直接定位到具体句子不用从头解释。很多团队觉得 AI 生成的需求不可信其实是没用对场景让 AI 直接下结论不可信让 AI 列出“哪里没说清楚”却很稳因为逻辑是反推的。2.3 需求语义查重用 embedding 找冲突与重复一个大型项目的需求池里经常出现两条需求描述相似但实际互斥的情况。比如一条说“导出功能支持 CSV”另一条说“导出功能仅支持 Excel”。靠人去翻需求池很难发现但用 embedding 做一个相似度计算能让这种冲突在评审前暴露出来。def get_embedding(text: str) - list[float]: api_base os.getenv(EMBEDDING_API_BASE, http://localhost:8000/v1/embeddings) api_key os.getenv(LLM_API_KEY, ) payload { model: os.getenv(EMBEDDING_MODEL, local-embedding-model), input: text, } resp requests.post(api_base, headers{Authorization: fBearer {api_key}}, jsonpayload, timeout30) resp.raise_for_status() return resp.json()[data][0][embedding] def cosine_similarity(a: list[float], b: list[float]) - float: dot sum(x * y for x, y in zip(a, b)) norm_a sum(x * x for x in a) ** 0.5 norm_b sum(y * y for y in b) ** 0.5 return dot / (norm_a * norm_b)用法很简单把两条需求的标题和核心描述拼在一起分别算 embedding然后算余弦相似度。我一般把阈值定在 0.82超过这个值就放进人工复核列表。要说明的是这个 0.82 不是拍脑袋是我用一批历史需求做标注测出来的低于 0.8 的大多是“同一模块但不同功能”高于 0.85 的基本可以判定为同一需求的变体。你们自己在落地时也应该抽 50 条历史需求做一个快速标注把阈值调成适合自己业务形态的值而不是照搬。2.4 把规格书输出成结构化 JSON为测试生成做准备用户故事最终要喂给测试生成环节所以不能停留在 Markdown最好一步到位输出成结构化 JSON。常见做法是加一层转换提示词让模型把用户故事按固定 Schema 抽取。这里的关键是 temperature 必须设为 0因为转换不允许任何创造性。import json def export_acceptance_criteria(user_story_output: str) - dict: system_prompt 你是一个结构化信息抽取器只输出 JSON不输出任何其他内容。 user_prompt ( 请把下面这段用户故事按固定 JSON Schema 输出字段包括\n story_id, role, feature, value, acceptance_criteria。\n acceptance_criteria 必须是字符串数组每个条目只能对应一条可验证规则。\n\n f用户故事\n{user_story_output} ) content llm_completion(system_prompt, user_prompt, temperature0.0, max_tokens1024) return json.loads(content)这段代码完成后需求侧就有一个干净的结构化产物每条需求都有自己的 story_id有自己的验收标准数组。后续做测试优化、做追踪矩阵全依赖这一层结构化输出。我建议团队在代码里把json.loads包一层异常处理因为模型偶尔会输出带解释文字的 JSON解析会直接抛错。实际工程里可以在解析失败后自动重试一次第二次强制提示“只输出 JSON 代码块不要解释”。3. 用生成式AI做测试优化用例生成、数据合成与自动断言测试侧的落地路径比需求侧更成熟因为测试用例本身是一种高度结构化的产物正好是生成式AI的舒适区。但“舒适区”不等于“无脑生成”如果不做约束模型生成的大量用例会集中在主路径上边界条件和异常分支反而被忽略。这一章的目标是让 AI 生成的测试用例能真正跑起来而不是看起来很多。3.1 从用户故事到可执行测试用例场景、前置、步骤、断言我常用的做法是直接吃上一章产生的结构化 JSON让模型按固定字段产出测试用例列表。这个环节的提示词要特别强调“期望结果必须是可自动断言的具体描述”否则模型会写出一堆“系统正常”“页面显示正确”这种没法验证的空话。def generate_test_cases_from_story(story_json: dict) - list[dict]: story_text json.dumps(story_json, ensure_asciiFalse, indent2) system_prompt 你是资深测试开发工程师只输出 JSON不输出解释。 user_prompt ( 基于下面的用户故事生成测试用例每个用例必须包含\n 用例ID、模块、预置条件、操作步骤、期望结果、优先级。\n 期望结果必须是可自动断言的具体描述不要写‘正常’‘成功’等模糊词。\n 必须覆盖主路径、分支路径、异常路径和边界值。\n\n f用户故事\n{story_text} ) content llm_completion(system_prompt, user_prompt, temperature0.3, max_tokens4096) return json.loads(content)这里把 temperature 提高到 0.3是因为需要一点多样性来覆盖不同分支但不要超过 0.3超过之后生成的用例会偏离原始需求。max_tokens 设 4096是因为完整覆盖一个用户故事的用例集通常要输出 20 到 40 条用例。代码里的重点是“必须覆盖主路径、分支路径、异常路径和边界值”这句话没有这句话模型默认只输出 5 到 8 条主路径用例。实际跑下来我发现要让生成结果更专业还可以在系统提示词里加一句“请站在探索式测试的视角不要只验证需求写得对的部分还要验证需求没写到的部分”。这句话对异常分支的生成率提升非常明显。3.2 单元测试补全基于函数签名和代码片段批量生成 pytest接口测试和用户故事生成是同一套路单元测试则不太一样模型需要看到真实函数代码不能只看业务描述。实际工程里一个项目有成百上千个函数不可能手动逐个提问常见做法是写一个脚本自动扫描代码库里的函数再逐函数喂给模型。import ast def extract_functions(file_path: str) - list[tuple[str, tuple[int, int]]]: with open(file_path, encodingutf-8) as f: source f.read() tree ast.parse(source) result [] for node in ast.walk(tree): if isinstance(node, ast.FunctionDef): result.append((node.name, (node.lineno, node.end_lineno))) return result def generate_unit_tests(func_name: str, func_code: str) - str: user_prompt ( f请为下面的函数生成 pytest 测试代码。\n 要求只输出代码不输出注释覆盖正常、异常和边界\n 不要 mock 外部依赖除非函数本身调用 IO。\n f函数名{func_name}\n f函数代码\n{func_code} ) return llm_completion(你是 Python 测试工程师只返回单个 .py 文件内容。, user_prompt, temperature0.2, max_tokens2048)这个脚本会先通过 AST 解析函数名和代码行号再取出代码内容传给模型。需要注意的坑是不要把一个几百行的长函数整个传给模型模型处理不了太长上下文而且测试生成效果会下降。我一般只传函数签名、docstring 和前 30 行核心代码超出部分让模型先归纳逻辑再生成测试。另外生成结果不会一次就全对必须在 CI 里先跑一遍把失败结果回喂给模型让它修正这个“生成—运行—反馈—修正”的循环比一次性生成的成功率高很多。3.3 测试数据合成用规则生成边界值避免隐私数据入库测试用例跑起来马上会遇到一个问题没有数据。生产环境的数据不能随便用手工造数据又慢。生成式AI很适合做测试数据合成但最大的风险是模型会生成“看起来真实”的个人信息这在合规上过不去。我一般会在提示词里硬性规定字段枚举范围并对输出做一次过滤确保不落库。def generate_boundary_data(schema: dict, count: int 20) - list[dict]: user_prompt ( f根据下面的字段约束生成 {count} 条测试数据以 JSON 数组返回。\n 要求覆盖最小值、最大值、合法值、非法值、空值、超长字符串。\n 不要包含真实个人姓名、手机号、身份证号。\n 手机号必须使用 13800000000 到 13800000010 范围内的号码。\n\n f字段约束\n{json.dumps(schema, ensure_asciiFalse)} ) content llm_completion(你是测试数据合成器只输出 JSON。, user_prompt, temperature0.4, max_tokens4096) return json.loads(content)这里 temperature 设 0.4 是为了让边界值有一定随机性但又借助规则限制住了格式比如手机号限定在一个极小的范围内就不会生成一串真实号码。还有一个小技巧在字段约束里直接注明“非法值必须包括负数、超过精度的小数、空字符串、长度超过 256 的字符串”这能让模型生成的数据更符合测试需求而不是常见的那种“全是合法值”。4. 常见问题与避坑排查生成式AI落地需求与测试的五条高频踩坑记录生成式AI在软件工程里的落地绝大多数失败不是模型能力不够而是工程手段没有跟上。这里记录五条我从实际项目里踩出来的经验每条都按“现象、原因、解决”的结构写方便你直接对照排查。4.1 现象AI 生成的测试用例数量很多一跑覆盖率还是上不去这是最常见的问题。团队用生成式AI产出了几百条用例看起来把所有功能点都覆盖了结果用 JaCoCo 一跑分支覆盖率还是不到 40%。原因在于模型只会基于用户故事里出现的分支去扩展故事里没写的分支它根本不知道自然生成不出来。解决方法是不要把用户故事作为唯一输入要把已有的接口文档、数据库约束、状态流转图一并喂进去。实际操作中我通常在提示词里附上一段“已知状态枚举”和“已知异常码表”这样模型才有机会生成那些“需求没提但代码里有”的用例。4.2 现象生成的接口测试断言格式正确但内容完全是错的模型写出来的断言长得很专业比如assert resp.json()[data][type] string看起来没有问题但接口实际返回的type是 Integer。原因很好理解模型没有见过真实接口返回数据它是根据字段名反推的。字段名叫type它就想当然认为是字符串。解决方法是把接口文档里的 Response Example 粘贴到提示词里并明确要求“断言必须基于示例数据中的字段类型不要推测字段类型”。更稳妥的做法是让模型只生成状态码断言和字段存在性断言具体类型判断留给测试代码里的公共断言函数去处理。4.3 现象需求材料一长模型后半段输出开始“幻觉”输入几十页需求文档后模型前面整理得不错到后面就开始编造不存在的业务规则。这不是模型变笨了而是上下文窗口被填满后半段原始信息被截断模型只能靠猜测继续输出。解决方法是不要“一段吞下”改成两阶段处理先用低 temperature 把文档按章节拆成摘要再用摘要合并成完整需求规格书。这个分块合并的操作比直接输入长文档的稳定性高非常多。我一般把单次输入控制在 4000 字以内超过就分块。4.4 现象AI 生成的规格书被业务方发现硬伤整个方案被一票否决只要小队第一次把 AI 生成的规格书直接当成最终交付物发出去就很容易因为一两处错误让整个 AI 方向在团队里失去信任。本质原因不是错误本身而是没有一个“人工复核节点”也没有给 AI 输出留出质疑空间。我的做法是永远不让 AI 输出“结论”只让它输出“带出处的整理结果”。具体操作是在提示词里强制要求每条验收标准都标注来源比如“引用原文第 3.2 节第 4 条”然后人工只需要核对引用是否准确而不是从零判断业务规则对不对。4.5 现象AI 调用接入 CI 后流水线频繁超时失败生成式AI服务通常不是本地进程走 HTTP 必然有延迟和抖动。把同步调用直接塞进 CI经常会出现构建失败而失败原因仅仅是模型服务偶发超时。解决方法是两层一是给请求加超时和指数退避重试二是把 AI 调用设置成“非阻断”的。所谓非阻断就是说 AI 生成失败时CI 不直接失败而是跳过该步并用缓存结果或标记 unresolved。明显有问题的用例等人工确认后再回填。这个设计看起来少了点“全自动”的味道但工程上稳定比自动化程度更重要。5. 把生成结果接进工程闭环从 JSON 用例到 pytest 与需求追踪矩阵前两章解决了“怎么生成”这一章解决“怎么让生成结果进入现有工程体系”。很多 AI 辅助测试的 demo 止步于“生成了一份漂亮的 JSON”却跑不起来原因是没有做最后一公里转换。这里的做法是把 AI 看作一个“产物生成器”而不是“测试执行器”后续仍沿用项目已有的 pytest 和 GitLab CI 门禁体系。5.1 把生成的用例 JSON 渲染成 pytest 文件接入现有测试集上一章生成的测试用例是 JSON 格式pytest 并不认识。我写了一个转换脚本把用例的预置条件、操作步骤、期望结果渲染成 pytest 函数骨架让团队直接在骨架上补真实断言。import json def render_pytest_module(test_cases: list[dict]) - str: lines [import pytest, from api_client import api_client\n] for tc in test_cases: test_name ftest_{tc[id]} lines.append(fdef {test_name}(api_client):) lines.append(f # 预置条件{tc[precondition]}) lines.append( # 操作步骤:) for step in tc[steps]: lines.append(f # - {step}) lines.append(f # 期望结果{tc[expected]}) lines.append( # TODO: 替换为真实断言) lines.append( assert api_client is not None) lines.append() return \n.join(lines) if __name__ __main__: with open(test_cases.json, encodingutf-8) as f: cases json.load(f) module render_pytest_module(cases) with open(generated_test_cases.py, w, encodingutf-8) as f: f.write(module)这个脚本的逻辑很朴素把 JSON 里的字段逐条映射为 pytest 函数的注释和骨架。脚本里预留了api_client这个 fixture实际接项目时这个 fixture 会负责登录、拿 token、创建上下文。这里特别说明一下注释里的操作步骤和期望结果不是摆设它们会在后续迭代中作为人工补充断言的指引。代码生成断言需要模型理解接口细节风险较高用骨架加人工补断言稳得多。第一次跑这个脚本时建议打开生成的 .py 文件逐条检查确认步骤没有遗漏。5.2 生成需求追踪矩阵并配置成可执行的覆盖率门禁测试用例接入 pytest 之后还差一个关键环节证明这些用例确实覆盖了需求。很多测试报告只写“用例通过率”不看需求到用例的映射关系。用生成的 story_id 与测试用例的关联关系可以直接渲染一张追踪矩阵。def build_trace_matrix(stories: list[dict], cases: list[dict]) - str: rows [] for story in stories: story_id story[story_id] covered_cases [ c[id] for c in cases if story_id in c.get(related_stories, []) ] rows.append((story_id, len(covered_cases), ; .join(covered_cases))) lines [| 需求ID | 覆盖用例数 | 用例列表 |, |---|---|---|] for story_id, count, case_list in rows: lines.append(f| {story_id} | {count} | {case_list} |) return \n.join(lines)这个矩阵要解决的问题是需求变更的时候一眼就能看出哪些测试用例会受影响。比如需求STORY-103改了矩阵里显示它被 6 条用例覆盖你只需要把这几条用例找出来跑一遍回归而不是整包重跑。进一步我建议把“需求覆盖率”做成一个质量门禁所有故事必须有至少一条关联用例否则 CI 直接失败。这样就能强制团队在提交测试代码时必须带上需求ID的关联信息从机制上避免“测试做了一堆需求没人理”的情况。5.3 与现有质量模型搭配AI 不替代测试替代的是重复性整理接入工程闭环之后会有一种声音说“AI 生成的用例都跑起来了是不是测试工程师可以少招了”。我不建议往这个方向宣传。真正被替代的是“把用户故事整理成用例清单”“把用例映射成测试代码骨架”“把需求变更同步到测试矩阵”这类重复性整理工作这些工作原本占测试开发工程师 40% 左右的时间。消掉这部分时间之后测试人员可以把精力放到探索式测试、性能测试、安全测试以及 AI 生成内容的最终判定上。也就是说AI 在测试侧提升的是“信息整理效率”而不是“质量保障能力”质量保障能力仍取决于有没有一个有经验的测试负责人把关。6. 进阶用法用评估集锁住提示词改动守住生成质量的底线提示词不是写一次就完事的每次调参会带来质量波动。如果你今天把 temperature 从 0.3 调成 0.4明天就会收到一批“看起来可以但实际上偏了”的用例而且你很难说清楚是哪次改动导致的。我的做法是搭建一个固定评估集用非常小的成本把提示词改动控制在可接受范围内。评估集的结构非常简单找 15 到 20 条有代表性的历史需求每条需求配上人工整理的“标准测试用例关键词”和“标准验收标准关键词”存成一个 Python 列表。每次调整提示词后用同一批需求跑一遍统计关键词命中率。这个命中率不需要做到 100%但至少不能出现明显下滑。下面是一个最小的评估脚本骨架EVAL_SET [ {raw: 用户可以导出订单列表, expected_keyword: 导出为空}, {raw: 验证码 60 秒后过期, expected_keyword: 超时}, # 更多样本... ] def evaluate_prompt(user_prompt_builder): hit 0 for sample in EVAL_SET: output llm_completion( 你是资深测试工程师只输出 JSON。, user_prompt_builder(sample[raw]), temperature0.0, max_tokens2048, ) if sample[expected_keyword] in output: hit 1 return hit / len(EVAL_SET)这个脚本的逻辑是把每条历史需求的期望关键字预先人工写好用新提示词跑一遍计算命中率。temperature 设 0排除随机性只评估提示词结构变化。我一般要求命中率不低于 0.8低于这个值就回滚改动。要注意评估集里的历史需求要覆盖不同业务模块不要全部集中在登录注册这类通用功能上否则评估结果会有偏。每过几个月我会检查一次评估集淘汰一些已经失去代表性的需求加入近期的真实需求。使用这个方法后我对“调参”这件事有了完全不同的看法。以前我也热衷于调整 temperature、top_p 这些参数总觉得多调一点质量就能上来。后来发现真正影响产出质量的往往是提示词里的结构约束比如“必须覆盖异常分支”“期望结果必须是可自动断言的具体描述”这些结构性指令比任何采样参数都重要。而评估集的存在能把这些经验沉淀成团队可复用的一份资产而不是某个人脑子里的“玄学”。现在每次上线新提示词我都会先跑一遍这个评估集再推广到团队这个习惯帮我挡掉了不少翻车事故。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站