AI测试用例生成脚本

0 阅读15分钟

项目由来与对测试工程师的价值(Origin & Impact)

本文档用于说明本项目为什么被创建、解决了测试工程里哪些痛点,以及能给测试工程师带来什么效果与提效方式。


1. 项目由来(为什么要做这个)

在需求驱动的研发流程中,测试用例的产出往往面临以下共性问题:

  1. 用例产出成本高、节奏快

    • 需求文档到用例编写之间的时间通常很短;
    • 大量用例属于“重复性/模板化/覆盖规则明确”的工作。
  2. 覆盖不稳定、质量受个人经验影响

    • 不同测试工程师对“正常/异常/边界/权限/数据校验”等覆盖维度的理解与粒度不同;
    • 容易出现遗漏或重复,导致评审返工。
  3. 文档与用例难以标准化、难以导入 TMS

    • 手工整理导致格式不统一;
    • 导入禅道/TestLink/TestRail 时需要二次加工。
  4. 需求变更带来的维护成本

    • 需求细节变化后,用例需要重新梳理;
    • 人工更新成本高且容易遗漏关联点。

基于以上痛点,本项目选择把“大模型擅长的结构化抽取与文本生成能力”用于测试设计:从需求文档自动生成测试点与结构化测试用例,并输出可导入、可审查的格式资产。


2. 设计理念(本项目如何更适合测试场景)

为了让生成结果更“可落地”,本项目采取了几项关键策略:

  1. 两阶段生成

    • 先生成“测试大纲”(摘要 + 测试点 + 思维导图结构);
    • 再围绕测试点分批生成大量测试用例。
  2. 结构化输出 + 强校验

    • 用 schema 强约束输出结构;
    • 解析失败时把原始模型返回写入 debug/,便于定位问题;
    • 最终产物统一封装为 Pydantic 模型,减少“格式不一致”的问题。
  3. 工程化可观测性

    • 全链路日志落盘到 log/
    • 统计任务耗时与单文档耗时;
    • 把失败原因分类型输出(网络/长度/JSON 解析),让排障更快。
  4. 去重与兜底机制

    • 在收集阶段边收集边去重,尽量贴近 --max-cases 的目标;
    • 增加兜底停止条件,避免模型持续重复导致的无限循环。

3. 对测试工程师的效果(能带来什么)

3.1 提效:从“手写”到“生成初稿 + 快速审校”

测试工程师通常需要先形成“初始用例骨架”,再做评审与补全。 本项目可以把这一步自动化:

  • 生成测试点与结构化用例初稿;
  • 测试工程师把精力从“写第一版”转移到“审查覆盖、补充关键业务细节、确认预期结果”。

结果是:需求评审与用例产出节奏更快,整体产出周期更短。

3.2 质量:覆盖维度更完整、粒度更一致

通过统一的生成规则与 schema,项目能较稳定地覆盖:

  • 正常流程、异常流程、边界条件
  • 权限/角色(如需求涉及)
  • 数据校验(如需求涉及)
  • 幂等/并发与可观测性(如需求涉及)

对团队来说,这意味着测试资产更容易做到“结构一致、评审更高效”。

3.3 标准化:输出格式可导入、可追溯

项目输出:

  • *.testcases.md:便于在文档/评审会议中直接查看
  • *.testcases.xlsx:便于筛选、导入 TMS
  • *.meta.md:记录测试点、假设、风险与不在范围,提升对齐效率
  • *.xmind:形成测试点到用例的思维导图结构,提升理解成本收益

测试工程师可直接把产物用于流程化管理,而不是把“格式转换”当作额外工作。

3.4 维护:需求变更时更容易更新测试资产

当需求文档更新后,仍可基于新文档重新生成结构化产物,再进行对比与增量审查。 这降低了“维护用例”阶段的人工整理负担。


4. 如何量化提效(建议你在团队落地时做的评估)

如果你希望把效果写进工作汇报/简历,建议用以下指标做一次对比:

  1. 用例产出周期:需求到可提交用例的时间(小时/天)
  2. 返工率:评审被要求补充/修改的次数或比例
  3. 覆盖完整度:异常/边界/权限/数据校验维度的命中率
  4. 导入成功率:直接导入 TMS 的成功次数 vs 需要二次处理的次数

通常你会发现:第一版更快、更标准,评审集中在业务细节与风险确认,而不是格式与结构本身。


5. 核心功能是什么?

本项目是一个 测试用例自动生成器

  1. 从本地 input/ 读取需求文档(支持:docx / pdf / md / markdown / txt
  2. 抽取文档文本并归一化
  3. 调用大模型进行两阶段生成:
    • 阶段一:生成 测试大纲
      • 提炼可复用摘要 context_summary
      • 列出 test_points(测试点)
      • 生成思维导图 mindmap_mermaid
      • 输出 assumptions / risks / out_of_scope
    • 阶段二:按测试点分批生成 结构化测试用例
      • 每批输出一个严格 JSON:{"test_cases": [...]}(字段需与 schema 一致)
  4. 将结果多格式导出到 output/
    • *.xmind 思维导图
    • *.testcases.md 测试用例表格(Markdown)
    • *.testcases.xlsx 测试用例表格(Excel)
    • *.meta.md 元信息 + 质量校验告警

6. 使用方式(运行与参数)

6.1 安装依赖

git clone https://github.com/Junmet/test_case.git
cd test_case

pip install -r requirements.txt

6.2 配置环境变量

  1. 复制并编辑 .env.example -> .env
  2. 至少设置 DEEPSEEK_API_KEY
  3. 可选设置:
    • DEEPSEEK_BASE_URL
    • DEEPSEEK_MODEL
    • DEEPSEEK_TIMEOUT
    • DEEPSEEK_MAX_TOKENS

6.3 运行命令

python -m src.main

常用参数:

  • --input:输入目录(默认 input
  • --output:输出目录(默认 output
  • --language:输出语言(zh/en)
  • --encoding:读取文本/Markdown 编码(默认 utf-8
  • --max-cases:每份文档目标生成的“唯一用例数”(内部会去重)
  • --batch-size:每次调用模型期望生成的用例条数(越大越容易触发长度上限)
  • --max-chars:参与生成的大文档最大字符数(超出会截断)

注:当 --input 目录不存在时,程序会自动创建并提示你放入文档后重新运行。


7. 输出文件说明

对每个输入文件 xxx.ext,在 output/ 下生成:

  • xxx.xmind
    • XMind 思维导图:包含“测试点”和“测试用例”两层结构
  • xxx.testcases.md
    • Markdown 表格:列字段为 编号/优先级/模块/测试标题/...
  • xxx.testcases.xlsx
    • Excel 表格:同样字段映射,方便导入测试管理系统
  • xxx.meta.md
    • 生成信息(时间、来源、语言)
    • 测试点列表、假设、风险、不在范围
    • 质量检查告警(空标题、空步骤、重复编号等)

同时运行时会生成:

  • log/YYYMMDD_HHMMSS.log:本次任务全链路日志
  • debug/debug_*.txt:当模型返回无法解析 JSON 时,把原始响应落盘以便排查

8. 关键流程总览(主流程链路)

入口在 src/main.pymain()

  1. 初始化日志与耗时计时
  2. 遍历 input/ 支持文件,逐个处理:
    • parse_document():抽取文本
    • 截断文本(--max-chars
    • generate_outline():生成测试大纲
    • 循环 generate_cases_batch()
      • 每批生成后立即“边收集边去重”
      • 达到 --max-cases 则停止
      • 启用兜底:最大批次上限 + 连续多批新增为 0 时停止
    • 组装 GenerationResultwrite_outputs()

核心效果:即使模型重复输出/批次返回变短,最终导出的用例数仍尽量贴近 --max-cases,并且出错可定位。


9. 代码结构与文件说明

9.1 src/main.py(CLI 入口 + 端到端编排)

主要职责:

  • 解析命令行参数(_parse_args
  • 初始化文件日志与输出耗时
  • 逐文件执行“解析 -> outline -> cases -> 导出”
  • 在“收集阶段”实现唯一性去重与兜底停止条件
  • 根据错误类型(长度/网络/JSON)给出更可操作的中文提示

关键实现点(理解用):

(1) 主循环结构

for i, path in enumerate(files, 1):
    parsed = parse_document(path, encoding=args.encoding)
    outline = generate_outline(...)
    while len(all_cases) < args.max_cases:
        batch = generate_cases_batch(...)
        # 边收集边去重,避免最后再去重导致数量偏差

(2) 去重键与兜底

  • 优先用 tc.title 去重
  • title 为空,用 ID:{tc.id} 兜底,避免丢失造成“永远凑不满”的死循环
  • 兜底停止:
    • max_batches_limit:最大批次上限
    • max_consecutive_zero_new:连续多批“新增为 0”停止

(3) 任务耗时

  • 单文件耗时:写入日志 + 控制台显示
  • 总耗时:控制台显示,并写入日志

9.2 src/config.py(环境变量配置加载)

主要职责:

  • 读取 .env / 环境变量
  • DEEPSEEK_BASE_URL 做兼容处理:
    • 支持用户填写 https://api.deepseek.com/v1,会自动去掉末尾 /v1
  • 提供统一的 AppConfig 数据结构给 LLM 层使用

关键字段:

  • deepseek_api_key
  • deepseek_base_url
  • deepseek_model
  • deepseek_timeout
  • deepseek_max_tokens

9.3 src/parsers.py(文档解析与文本归一化)

主要职责:

  • 根据后缀选择解析策略
    • .docxpython-docx 提取段落与表格
    • .pdfPyMuPDF(fitz) 逐页提取文本
    • .md/.markdown/.txt:直接按编码读取
  • 对提取文本做归一化:
    • 统一换行符
    • 压缩连续空行(让 LLM 更易读)

关键函数:

  • iter_input_files(input_dir):遍历输入目录,过滤支持文件类型
  • parse_document(path, encoding):返回 ParsedDocument(path, text)

9.4 src/prompts.py(提示模板)

主要职责:

  • 提供系统提示与用户提示模板
  • 强制模型输出满足 JSON schema

内容分为:

  • SYSTEM_ZH:中文系统提示(严格要求 JSON)
  • SYSTEM_EN:英文系统提示(同样严格要求 JSON)
  • USER_TEMPLATE:完整生成(旧接口,保留兼容)
  • OUTLINE_USER_TEMPLATE:大纲生成 prompt
  • CASES_BATCH_USER_TEMPLATE:按测试点分批生成用例 prompt

注意:本项目的“严格 JSON + schema 对齐”是解析成功的核心原因之一。


9.5 src/models.py(Pydantic 数据模型)

主要职责:

  • 定义结构化数据的强校验模型
  • 在解析 JSON 后,使用 TypeAdapter(...).validate_python(...) 确保字段类型正确

关键模型:

  • TestCase
    • id/priority/module/title/summary/.../steps/expected/...
  • GenerationResult
    • source_name/language/context_summary/mindmap_mermaid/test_points/test_cases/...

9.6 src/llm.py(LLM 调用、JSON 解析与容错恢复)

主要职责:

  • 构建 LangChain 的 ChatOpenAI 客户端(通过 OpenAI 兼容接口访问 DeepSeek 等)
  • 进行两阶段 LLM 调用:
    • generate_outline()
    • generate_cases_batch()
  • 对 LLM 输出进行严格 JSON 解析,并增强容错:
    • _safe_json_loads():提取 JSON 片段并修复常见逗号错误
    • _recover_cases_batch_from_raw()(P0-1):批量场景解析失败时尽量“部分恢复”
  • 将 LLM 调用错误分类(网络/长度/JSON),返回给上层更明确的提示

关键组件:

(1) 错误分类异常

  • LLMConnectionError:网络/网关问题
  • LLMLengthLimitError:长度上限导致截断
  • LLMJSONParseError:JSON 格式与 schema 不匹配

(2) 批量解析部分恢复(P0-1)

cases_* 场景整体 JSON 解析失败时:

  • 从原始返回中尽量定位 "test_cases": [ ... ] 数组片段
  • 再尝试把其中的多个 {...} 对象逐个解析为 TestCase
  • 能解析成功的条目会被保留并继续生成流程

这样可以避免“一个坏 JSON 导致整批作废”的体验问题。


9.7 src/writers.py(结果导出:XMind / Markdown / Excel / Meta)

主要职责:

  • GenerationResult 写入磁盘
  • 输出前先做质量校验,并把告警写入 meta

关键功能:

  1. _validate_result_quality(result)
    • 重复的 id
    • 空标题
    • steps/expected 为空
    • 标题过短
  2. write_outputs(result, output_dir)
    • 写 XMind:py-xmind16
    • 写 Markdown:*.testcases.md
    • 写 Excel:pandas + openpyxl
    • 写 meta:*.meta.md(包含质量告警)

9.8 tools/check_readme_defaults.py(文档默认值一致性校验脚本)

主要职责:

  • 解析 src/config.pyDEEPSEEK_TIMEOUT / DEEPSEEK_MAX_TOKENS 的默认值
  • 解析 README.md 的“默认值表格”
  • 若不一致,会打印错误并返回非 0

用于降低“文档与代码默认值不一致”带来的误导风险。


10. 建议的新增方向(可作为下一步文档扩展)

如果你要继续扩展功能,建议优先考虑:

  • 进一步强化 JSON “逐对象恢复”覆盖更多脏输出形态
  • 提供 --dedup-key / --max-unique-cases 参数,让唯一性策略更可控
  • 输出适配更多 TMS(如 TestRail/禅道列映射配置化)
  • 增加 --dry-run/--outline-only 节省 token 成本

11. 附录:逐文件关键代码解读(更细粒度)

本附录重点解释“每个文件里关键函数/类在干什么、输入输出是什么、为什么要这么写”。建议你配合源码逐段对照阅读。


11.1 src/main.py(主流程编排)

关键点:职责拆分
  • main():把整个端到端流程串起来(解析 -> outline -> cases -> 写出)
  • _parse_args():定义命令行参数并把 help 文案做中文化
main() 的关键步骤(输入/输出/关键逻辑)
  1. 计时:start_ts = time.time() 用于算任务总耗时
  2. 日志初始化:
    • 创建 log/
    • 日志文件名:log/YYYMMDD_HHMMSS.log
    • logging.basicConfig(...FileHandler...)
  3. 输入目录处理:
    • input_dir = Path(args.input).resolve()
    • 不存在则自动创建并提示“放入文档后重跑”
    • 存在但不是目录则抛异常
  4. 扫描输入文件:
    • files = list(iter_input_files(input_dir))
  5. 加载配置与 LLM:
    • cfg = load_config(override_language=args.language)
    • llm = build_llm(cfg)
  6. 遍历每个文档文件:
    • parse_document(path, encoding=args.encoding):抽取文本
    • 超长截断:if len(text) > args.max_chars: text = text[:args.max_chars]
  7. 生成测试大纲:
    • outline = generate_outline(cfg, llm, source_name, document_text)
  8. 按测试点分批生成用例(核心循环):
    • 初始化收集容器:all_cases、existing_titles、seen_titles
    • 兜底控制:
      • max_batches_limit:最大批次数
      • max_consecutive_zero_new:连续多批“新增=0”停止
    • 每次循环:
      • 选择测试点:test_point = outline.test_points[tp_idx % len(...)]
      • 算 batch_size:min(args.batch_size, remaining)
      • 调模型:batch = generate_cases_batch(...)
      • 边收集边去重:
        • 去重键:title;title 为空则用 ID:{tc.id}
        • 新增的用例才会加入 all_cases,因此最终输出更贴近 --max-cases
  9. 组装输出数据:
    • GenerationResult.model_validate({... "test_cases": [tc.model_dump() for tc in all_cases] ...})
  10. 写出结果:
  • write_outputs(result, output_dir)
  1. 打印耗时:
  • 单文件:file_elapsed_min
  • 全任务:total_elapsed_min
异常处理(为什么要分类型)
  • 通过 isinstance(e, LLMLengthLimitError / LLMConnectionError / LLMJSONParseError) 给出不同中文提示,便于你决定下一步操作(减小 batch、检查网络、看 debug 文件等)。

11.2 src/config.py(配置加载)

AppConfig
  • @dataclass(frozen=True) 固定配置,不让运行时意外修改
  • 保存 LLM 所需关键参数(api_key、base_url、model、timeout、max_tokens)
_normalize_base_url(base_url)
  • 去掉末尾 /,并兼容用户填写 .../v1
  • 避免 build_llm 再拼 /v1 导致 .../v1/v1
load_config(override_language)
  • load_dotenv(override=True):优先使用项目根目录 .env
  • 强校验:
    • 缺少 DEEPSEEK_API_KEY 直接抛中文异常
  • 解析数值:
    • timeout / max_tokens 转 int,解析失败用默认值回退

11.3 src/parsers.py(文档解析)

ParsedDocument
  • path:源文件路径
  • text:归一化后的纯文本内容
SUPPORTED_SUFFIXES
  • 定义系统支持的文件后缀集合
iter_input_files(input_dir)
  • 遍历目录下文件
  • 过滤 suffix in SUPPORTED_SUFFIXES
  • 按文件名排序输出,保证输入顺序可控
parse_document(path, encoding)
  • 根据后缀分支调用:
    • _parse_docx
    • _parse_pdf
    • _parse_text
  • 最后统一 _normalize_text()
    • 统一换行符
    • 压缩多余空行(让 LLM 更容易处理)

11.4 src/prompts.py(提示模板)

这一文件的核心价值是:让 LLM 输出尽量“结构化且可解析”

  • SYSTEM_ZH / SYSTEM_EN
    • 强制“严格 JSON”
    • 强制字段与 schema 对齐
    • 为 mindmap_mermaid / steps / priority 提供格式约束
  • USER_TEMPLATE
    • 完整生成(兼容旧接口)
    • 把 document_text + schema 拼成最终 prompt
  • OUTLINE_USER_TEMPLATE
    • outline 阶段控制长度(context_summary <= 600 字)、测试点数量限制等
  • CASES_BATCH_USER_TEMPLATE
    • cases 阶段控制 batch_size、每条 steps/expected 数量、避免标题重复等

11.5 src/models.py(Pydantic 强校验模型)

TestCase
  • 通过 Pydantic 把每个字段“类型/是否缺失”变成运行时校验
  • 你在输出到 Excel/Markdown 前,数据都已通过 schema 校验,减少写出错误。
GenerationResult
  • 描述一份输入文档对应的完整生成产物:
    • test_points + test_cases + assumptions/risks/out_of_scope

11.6 src/llm.py(LLM 调用、JSON 解析、容错恢复)

错误分类(P0-3)
  • LLMConnectionError:网络连接异常
  • LLMLengthLimitError:模型输出长度上限(导致截断)
  • LLMJSONParseError:JSON 解析失败/字段不匹配
build_llm(cfg)
  • 初始化 ChatOpenAI
  • 关键参数:
    • base_url=f"{cfg.deepseek_base_url}/v1"
    • max_tokens=cfg.deepseek_max_tokens
    • model_kwargs={"response_format": {"type": "json_object"}}(支持的 provider 下强约束 JSON)
generate_outline(...)
  • outline 阶段调用:
    • 使用 _outline_schema_json() 作为 schema 强约束
    • 调用 _invoke_llm_with_classification() 获得 raw content
    • _parse_or_debug() 解析并校验 OutlineResult
generate_cases_batch(...)
  • cases 阶段调用:
    • schema 来自 _cases_batch_schema_json()
    • 最多重试 3 次(重试对象主要是 LLMJSONParseError
_invoke_llm_with_classification(...)
  • try/catch 包装 llm.invoke
  • 根据异常文本关键词做类型映射并抛出对应错误类
_parse_or_debug(raw, debug_stem)
  • 通用入口:先 _safe_json_loads
  • 失败逻辑:
    • 写入 debug/debug_{debug_stem}_{ts}.txt
    • 抛出 LLMJSONParseError
  • P0-1 部分恢复(批量 cases 场景):
    • debug_stemcases_ 开头时,尝试从 raw 文本中恢复 test_cases 数组
    • 可恢复到条目就返回继续执行,减少整批作废
_safe_json_loads(s)
  • 从一段文本中提取 JSON:
    • 去 BOM/空白
    • 若是 markdown 代码块,剥离 ``` 包裹
    • 识别最外层 {...},否则截取最外层大括号范围
    • json.loads 失败后尝试 _repair_json
_repair_json(json_str)
  • 当前修复策略偏“轻量”:
    • 修尾逗号
    • }{ 之间缺逗号
schema 构造器
  • _generation_schema_json():完整生成 schema(旧接口)
  • _outline_schema_json():outline schema
  • _cases_batch_schema_json():cases schema

11.7 src/writers.py(输出写出 + 质量校验)

write_outputs(result, output_dir)
  • 执行流程:
    1. output_dir.mkdir(...)
    2. _validate_result_quality(result) 得到告警列表
    3. 写 XMind、写 Markdown、写 Excel、写 meta
_validate_result_quality(result)(P1-2)
  • 质量校验项:
    • 重复用例编号 id
    • 空测试标题
    • 标题过短
    • 空 steps / 空 expected
  • 返回告警列表字符串,既写到日志也写到 meta.md
_render_testcases_md
  • Markdown 表格生成
  • 表头字段使用中文列名
_write_testcases_xlsx
  • 使用 pandas -> ExcelWriter -> sheet_name="测试用例"
_write_xmind
  • 通过 py-xmind16 生成思维导图:
    • 测试点分支
    • 测试用例分支(含前置条件/步骤/期望层级)
_render_meta_md
  • meta 内容包含:
    • 生成信息
    • 测试点、假设、风险、不在范围
    • 质量检查告警(如果有)

11.8 tools/check_readme_defaults.py

main()
  • 从:
    • src/config.py 提取 DEEPSEEK_TIMEOUT / DEEPSEEK_MAX_TOKENS 默认值
    • README.md 提取配置表里的默认值
  • 若存在不一致:
    • 输出差异
    • 返回 1
  • 若一致:
    • 输出通过信息
    • 返回 0

使用建议(读代码时的顺序)

  1. 先看 src/main.py:它串起来所有流程
  2. 再看 src/llm.py:理解 JSON schema、容错与错误分类
  3. 再看 src/parsers.pysrc/writers.py:理解输入/输出如何落地
  4. 最后看 src/models.py:理解 schema 对齐与字段校验