项目由来与对测试工程师的价值(Origin & Impact)
本文档用于说明本项目为什么被创建、解决了测试工程里哪些痛点,以及能给测试工程师带来什么效果与提效方式。
1. 项目由来(为什么要做这个)
在需求驱动的研发流程中,测试用例的产出往往面临以下共性问题:
-
用例产出成本高、节奏快
- 需求文档到用例编写之间的时间通常很短;
- 大量用例属于“重复性/模板化/覆盖规则明确”的工作。
-
覆盖不稳定、质量受个人经验影响
- 不同测试工程师对“正常/异常/边界/权限/数据校验”等覆盖维度的理解与粒度不同;
- 容易出现遗漏或重复,导致评审返工。
-
文档与用例难以标准化、难以导入 TMS
- 手工整理导致格式不统一;
- 导入禅道/TestLink/TestRail 时需要二次加工。
-
需求变更带来的维护成本
- 需求细节变化后,用例需要重新梳理;
- 人工更新成本高且容易遗漏关联点。
基于以上痛点,本项目选择把“大模型擅长的结构化抽取与文本生成能力”用于测试设计:从需求文档自动生成测试点与结构化测试用例,并输出可导入、可审查的格式资产。
2. 设计理念(本项目如何更适合测试场景)
为了让生成结果更“可落地”,本项目采取了几项关键策略:
-
两阶段生成
- 先生成“测试大纲”(摘要 + 测试点 + 思维导图结构);
- 再围绕测试点分批生成大量测试用例。
-
结构化输出 + 强校验
- 用 schema 强约束输出结构;
- 解析失败时把原始模型返回写入
debug/,便于定位问题; - 最终产物统一封装为 Pydantic 模型,减少“格式不一致”的问题。
-
工程化可观测性
- 全链路日志落盘到
log/; - 统计任务耗时与单文档耗时;
- 把失败原因分类型输出(网络/长度/JSON 解析),让排障更快。
- 全链路日志落盘到
-
去重与兜底机制
- 在收集阶段边收集边去重,尽量贴近
--max-cases的目标; - 增加兜底停止条件,避免模型持续重复导致的无限循环。
- 在收集阶段边收集边去重,尽量贴近
3. 对测试工程师的效果(能带来什么)
3.1 提效:从“手写”到“生成初稿 + 快速审校”
测试工程师通常需要先形成“初始用例骨架”,再做评审与补全。 本项目可以把这一步自动化:
- 生成测试点与结构化用例初稿;
- 测试工程师把精力从“写第一版”转移到“审查覆盖、补充关键业务细节、确认预期结果”。
结果是:需求评审与用例产出节奏更快,整体产出周期更短。
3.2 质量:覆盖维度更完整、粒度更一致
通过统一的生成规则与 schema,项目能较稳定地覆盖:
- 正常流程、异常流程、边界条件
- 权限/角色(如需求涉及)
- 数据校验(如需求涉及)
- 幂等/并发与可观测性(如需求涉及)
对团队来说,这意味着测试资产更容易做到“结构一致、评审更高效”。
3.3 标准化:输出格式可导入、可追溯
项目输出:
*.testcases.md:便于在文档/评审会议中直接查看*.testcases.xlsx:便于筛选、导入 TMS*.meta.md:记录测试点、假设、风险与不在范围,提升对齐效率*.xmind:形成测试点到用例的思维导图结构,提升理解成本收益
测试工程师可直接把产物用于流程化管理,而不是把“格式转换”当作额外工作。
3.4 维护:需求变更时更容易更新测试资产
当需求文档更新后,仍可基于新文档重新生成结构化产物,再进行对比与增量审查。 这降低了“维护用例”阶段的人工整理负担。
4. 如何量化提效(建议你在团队落地时做的评估)
如果你希望把效果写进工作汇报/简历,建议用以下指标做一次对比:
- 用例产出周期:需求到可提交用例的时间(小时/天)
- 返工率:评审被要求补充/修改的次数或比例
- 覆盖完整度:异常/边界/权限/数据校验维度的命中率
- 导入成功率:直接导入 TMS 的成功次数 vs 需要二次处理的次数
通常你会发现:第一版更快、更标准,评审集中在业务细节与风险确认,而不是格式与结构本身。
5. 核心功能是什么?
本项目是一个 测试用例自动生成器:
- 从本地
input/读取需求文档(支持:docx / pdf / md / markdown / txt) - 抽取文档文本并归一化
- 调用大模型进行两阶段生成:
- 阶段一:生成 测试大纲
- 提炼可复用摘要
context_summary - 列出
test_points(测试点) - 生成思维导图
mindmap_mermaid - 输出
assumptions / risks / out_of_scope
- 提炼可复用摘要
- 阶段二:按测试点分批生成 结构化测试用例
- 每批输出一个严格 JSON:
{"test_cases": [...]}(字段需与 schema 一致)
- 每批输出一个严格 JSON:
- 阶段一:生成 测试大纲
- 将结果多格式导出到
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 配置环境变量
- 复制并编辑
.env.example -> .env - 至少设置
DEEPSEEK_API_KEY - 可选设置:
DEEPSEEK_BASE_URLDEEPSEEK_MODELDEEPSEEK_TIMEOUTDEEPSEEK_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 表格:列字段为
编号/优先级/模块/测试标题/...
- Markdown 表格:列字段为
xxx.testcases.xlsx- Excel 表格:同样字段映射,方便导入测试管理系统
xxx.meta.md- 生成信息(时间、来源、语言)
- 测试点列表、假设、风险、不在范围
- 质量检查告警(空标题、空步骤、重复编号等)
同时运行时会生成:
log/YYYMMDD_HHMMSS.log:本次任务全链路日志debug/debug_*.txt:当模型返回无法解析 JSON 时,把原始响应落盘以便排查
8. 关键流程总览(主流程链路)
入口在 src/main.py 的 main():
- 初始化日志与耗时计时
- 遍历
input/支持文件,逐个处理:parse_document():抽取文本- 截断文本(
--max-chars) generate_outline():生成测试大纲- 循环
generate_cases_batch():- 每批生成后立即“边收集边去重”
- 达到
--max-cases则停止 - 启用兜底:最大批次上限 + 连续多批新增为 0 时停止
- 组装
GenerationResult并write_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_keydeepseek_base_urldeepseek_modeldeepseek_timeoutdeepseek_max_tokens
9.3 src/parsers.py(文档解析与文本归一化)
主要职责:
- 根据后缀选择解析策略
.docx:python-docx提取段落与表格.pdf:PyMuPDF(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:大纲生成 promptCASES_BATCH_USER_TEMPLATE:按测试点分批生成用例 prompt
注意:本项目的“严格 JSON + schema 对齐”是解析成功的核心原因之一。
9.5 src/models.py(Pydantic 数据模型)
主要职责:
- 定义结构化数据的强校验模型
- 在解析 JSON 后,使用
TypeAdapter(...).validate_python(...)确保字段类型正确
关键模型:
TestCaseid/priority/module/title/summary/.../steps/expected/...
GenerationResultsource_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
关键功能:
_validate_result_quality(result)- 重复的
id - 空标题
- steps/expected 为空
- 标题过短
- 重复的
write_outputs(result, output_dir)- 写 XMind:
py-xmind16 - 写 Markdown:
*.testcases.md - 写 Excel:
pandas + openpyxl - 写 meta:
*.meta.md(包含质量告警)
- 写 XMind:
9.8 tools/check_readme_defaults.py(文档默认值一致性校验脚本)
主要职责:
- 解析
src/config.py中DEEPSEEK_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() 的关键步骤(输入/输出/关键逻辑)
- 计时:
start_ts = time.time()用于算任务总耗时 - 日志初始化:
- 创建
log/ - 日志文件名:
log/YYYMMDD_HHMMSS.log logging.basicConfig(...FileHandler...)
- 创建
- 输入目录处理:
input_dir = Path(args.input).resolve()- 不存在则自动创建并提示“放入文档后重跑”
- 存在但不是目录则抛异常
- 扫描输入文件:
files = list(iter_input_files(input_dir))
- 加载配置与 LLM:
cfg = load_config(override_language=args.language)llm = build_llm(cfg)
- 遍历每个文档文件:
parse_document(path, encoding=args.encoding):抽取文本- 超长截断:
if len(text) > args.max_chars: text = text[:args.max_chars]
- 生成测试大纲:
outline = generate_outline(cfg, llm, source_name, document_text)
- 按测试点分批生成用例(核心循环):
- 初始化收集容器:
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
- 去重键:
- 选择测试点:
- 初始化收集容器:
- 组装输出数据:
GenerationResult.model_validate({... "test_cases": [tc.model_dump() for tc in all_cases] ...})
- 写出结果:
write_outputs(result, output_dir)
- 打印耗时:
- 单文件:
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_tokensmodel_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)
- schema 来自
_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_stem以cases_开头时,尝试从 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)
- 执行流程:
output_dir.mkdir(...)_validate_result_quality(result)得到告警列表- 写 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
使用建议(读代码时的顺序)
- 先看
src/main.py:它串起来所有流程 - 再看
src/llm.py:理解 JSON schema、容错与错误分类 - 再看
src/parsers.py与src/writers.py:理解输入/输出如何落地 - 最后看
src/models.py:理解 schema 对齐与字段校验