Dify 高级实验(09):测试用例生成——如何让 AI 自动产出测试用例?

Dify 高级实验(09):测试用例生成------如何让 AI 自动产出测试用例?

Dify 实验系列 · 高级 09/10 | 实验编号:DIFY-103-09

1. 实验目的

掌握「Agent 编排 + 结构化输出」的生成式工作流范式:让 LLM 扮演 QA 工程师,从一段功能描述自动产出可直接执行的测试用例(CSV 格式)。核心是三层结构化输出链路:

  • 测试点列表:LLM 输出 JSON 数组(类型/描述/优先级),代码节点解析兜底;
  • 逐条用例:迭代节点对每个测试点生成完整用例 JSON(前置条件/步骤/预期结果);
  • CSV 交付 :代码节点 csv.writer 汇总,LLM 最终格式化出统计摘要 + 完整 CSV。

适合:QA / 测试开发 / 需要把「写用例」自动化的团队。前置要求:中级实验 09 代码节点、14 知识库检索、16 迭代节点。

2. 场景设计

输入:feature_desc(功能描述,paragraph)+ module_name(模块名称,text-input);输出:一份含统计摘要与完整 CSV 的测试用例文档,可直接复制进 Excel。

输入「用户注册功能,输入手机号验证码注册」→ 自动产出 10-15 条用例(正常/边界/异常全覆盖,P0-P3 分级)------QA 半天的手工工作量压缩到几分钟。

3. 节点拓扑

text 复制代码
开始(feature_desc + module_name)
  → 知识库检索(测试用例模板,top_k=3)
    → LLM 生成测试点(JSON 数组:类型/描述/优先级)
      → Code 解析测试点(json.loads + 截断 25 条 + 注入 module)
        → 迭代节点(逐条生成详细用例)
          └→ 迭代内 LLM 生成单条用例(JSON:前置条件/步骤/预期)
        → Code 汇总 CSV(csv.writer)
          → LLM 格式化输出(统计摘要 + 完整 CSV)→ 结束

4. 关键配置

4.1 开始节点

yaml 复制代码
- label: 功能描述
  required: true
  type: paragraph
  variable: feature_desc
- label: 模块名称
  required: true
  type: text-input
  variable: module_name

4.2 生成测试点(lm_points)

数量约束 + 「只输出 JSON」+ 结构示例------让输出可被下游代码节点稳定解析:

jinja2 复制代码
你是一个 QA 工程师。根据功能描述生成测试点列表。
功能描述:{{#start.feature_desc#}}
模块:{{#start.module_name#}}
参考模板:{{#kb_templates.result#}}
要求:
1. 覆盖功能测试、边界测试、异常测试
2. 每个测试点包含:类型(功能/边界/异常)、描述、优先级(P0-P3)
3. P0 是核心功能必须覆盖,P3 是边缘场景
4. 测试点数量控制在 10-15 个
5. 输出 JSON 数组格式(只输出 JSON,不要输出其他任何内容)
输出格式:[{"type": "功能", "description": "正常登录", "priority": "P0"}, ...]

注意该节点必须 reasoning_format: separated------思考过程一旦混入 text,下方 cd_parse 的 json.loads 直接失败,流程卡在步骤 0。

4.3 解析测试点(cd_parse)

两个关键设计:① 迭代数组上限 30,points[:25] 截断留余量;② 把 module 注入每个 item------迭代内 LLM 只引用 item,不引用外层节点(自包含模式,依赖只进不出):

python 复制代码
def main(llm_text: str, module_name: str) -> dict:
    import json, re
    text = (llm_text or "").strip()
    points = []
    m = re.search(r"\[.*\]", text, re.DOTALL)
    if m:
        try:
            parsed = json.loads(m.group(0))
            if isinstance(parsed, list):
                points = parsed
        except Exception:
            points = []
    points = points[:25]  # 迭代数组上限 30,截断留余量
    items = []
    for p in points:
        if not isinstance(p, dict):
            continue
        items.append({
            "type": str(p.get("type", "功能")),
            "description": str(p.get("description", "")),
            "priority": str(p.get("priority", "P2")),
            "module": str(module_name or "未指定模块")
        })
    return {"test_points": items, "count": len(items)}

4.4 迭代内生成单条用例(lm_case)

迭代容器输入 [cd_parse, test_points],输出收集 [lm_case, text](string 是迭代 output_selector 可见类型)。内部 prompt 全部从 item 取字段,同样 reasoning_format: separated

jinja2 复制代码
根据测试点生成完整的测试用例。
模块:{{#iter_cases.item.module#}}
测试点:{{#iter_cases.item.description#}}
类型:{{#iter_cases.item.type#}}
优先级:{{#iter_cases.item.priority#}}
输出 JSON(只输出 JSON,不要输出其他任何内容):
{"module": "模块名", "test_point": "测试点描述", "type": "功能/边界/异常",
 "priority": "P0-P3", "precondition": "前置条件", "steps": ["步骤1", "步骤2"],
 "expected": "预期结果"}
要求:前置条件具体、步骤可执行、预期结果可验证。

4.5 汇总 CSV(cd_summary)

迭代输出收集到的是 array[string],每条可能是字符串------先 json.loads 还原再写 CSV:

python 复制代码
def main(all_cases: list) -> dict:
    import csv, io, json
    cases = []
    for c in all_cases or []:
        if isinstance(c, str):
            try:
                c = json.loads(c)
            except Exception:
                c = {}
        if isinstance(c, dict):
            cases.append(c)
    output = io.StringIO()
    writer = csv.writer(output)
    writer.writerow(["模块", "测试点", "类型", "优先级", "前置条件", "步骤", "预期结果"])
    for case in cases:
        steps = case.get("steps", []) or []
        if not isinstance(steps, list):
            steps = [str(steps)]
        writer.writerow([case.get("module", ""), case.get("test_point", ""),
            case.get("type", ""), case.get("priority", ""),
            case.get("precondition", ""), "; ".join(str(s) for s in steps),
            case.get("expected", "")])
    return {"csv_output": output.getvalue(), "case_count": len(cases)}

4.6 格式化输出(lm_format)

max_tokens: 8000 + 明确「只输出正文」------长交付物场景的标配双保险:

jinja2 复制代码
你是 QA 测试用例交付专员。基于以下 CSV 内容生成一份可直接交付的测试用例文档。
共生成 {{#cd_summary.case_count#}} 条测试用例,CSV 内容如下:
{{#cd_summary.csv_output#}}
请输出:1. 测试用例统计摘要(总数、功能/边界/异常分布)2. 完整 CSV 内容(保持原样,方便复制导入 Excel)。
只输出最终报告正文,不要输出任何解释性文字、思考过程或 Markdown 代码块标记。

5. 运行验证

输入(feature_desc) 预期 实测
用户注册功能,输入手机号验证码注册 10-15 条用例,覆盖功能/边界/异常,输出统计摘要 + CSV 与预期一致
购物车结算功能,支持优惠券 15-20 条用例,优先级 P0-P3 分级 与预期一致
空功能描述 / 超长描述 不崩溃,输出空用例文档或少量兜底用例 与预期一致(cd_parse 空数组兜底)

6. 采坑点

现象 修复
迭代链路 LLM 思考污染 lm_points 未开推理标签分离时,text 混入 <think> 思考,cd_parse 的 json.loads 解析失败,流程卡在步骤 0 lm_points / lm_case 显式 reasoning_format: separated(DeepSeek 推理模型默认 tagged,必须显式写)
迭代输入数组超过 30 个元素 运行报 length of var "item" must be less than 30 elements 双保险:prompt 限「10-15 个」+ cd_parse points[:25] 截断留余量(dify103_09 P3 实测)
迭代内 LLM 引用外层节点字段 上下文依赖外传、字段取不到,调试困难 自包含模式:进入迭代前把 module 等上下文注入每个 item,迭代内只引用 {``{#iter_cases.item.xxx#}}
迭代输出每条是字符串,直接当 dict 用报错 迭代 output_selector 只能收集 string,对 str 调 case.get("module") 抛 AttributeError cd_summary 先 isinstance(c, str)json.loads 还原
长交付文档 text 为空 思考与输出共享 max_tokens(默认 2000)被挤空,或内容写进思考部分 lm_format max_tokens: 8000 + prompt「只输出最终报告正文」(dify103_09 实测:只加 prompt 约束即恢复)

7. 实验文档及源码获取

文章聚焦核心配置与采坑点;实验的完整分步操作(知识库种子准备/节点搭建/参数表)见实验文档原文。


下一篇:Dify 高级实验(10):综合实战------如何从零搭建一个企业级智能客服平台?

💬 你在这个实验的场景里踩过什么坑?欢迎评论区分享你的实战经验。

相关推荐
回归2722 小时前
LM Studio切换国内HF-Mirror镜像进行模型下载
人工智能
m4Rk_2 小时前
【论文阅读】Agent 记忆机制(38):AMA——用多智能体协作动态选择记忆粒度
论文阅读·人工智能·学习
青春不败 177-3266-05202 小时前
基于R、Python的Copula变量相关性分析及AI大模型应用
人工智能·python·r语言·贝叶斯·统计学·copula
wx_xkq12882 小时前
2026企业级AI Agent落地观察:从工具集到组织级智能体
大数据·人工智能·saas·ai营销
AI_AGENT_DEV_AI2 小时前
AI 英语口语 APP的开发
人工智能
熊猫_豆豆2 小时前
WOKWI 仿真 ESP32S3 SSD1306 播放GIF图
人工智能·esp32s3·硬件开发·ssd1306
玫瑰互动GEO2 小时前
2026豆包AI搜索GEO优化:从豆包大模型到字节生态信源引用
大数据·人工智能·豆包·geo优化
dozenyaoyida2 小时前
AI与大模型新闻日报 | 2026-08-13
人工智能·ai·大模型·新闻
梦想的旅途23 小时前
企微 API 二次开发:结合 AI 提取聊天记录并智能生成会议纪要
人工智能