提交 09170467 authored 作者: 陈泽健's avatar 陈泽健

feat(testing): 添加功能测试报告自动生成工具

- 创建功能测试报告自动生成工具(AuxiliaryTool/FunctionalTestReportGeneration)
- 添加PRD文档定义功能需求、数据结构和程序架构
- 设计测试用例和BUG列表Excel文件的数据结构
- 实现模块化架构包含配置、数据读取、分析、图表生成和报告生成组件
- 定义命令行和GUI两种交互方式
- 添加功能测试报告模板和数据模板文件
- 更新Claude设置添加mkdir权限
- 创建详细的执行计划和测试计划文档
上级 2d320395
......@@ -123,7 +123,8 @@
"mcp__chrome-devtools__close_page",
"mcp__chrome-devtools__list_pages",
"mcp__chrome-devtools__fill_form",
"Bash(grep:*)"
"Bash(grep:*)",
"Bash(mkdir:*)"
]
}
}
# 功能测试报告自动生成工具
## 功能简介
本工具通过读取测试用例Excel文件和BUG列表Excel文件,自动生成功能测试报告(Word格式)。
### 主要功能
- 自动读取测试用例和BUG列表数据
- 统计用例执行情况、BUG数量和等级分布
- 自动关联用例与BUG
- 生成统计图表(饼图、柱状图)
- 生成Word格式的功能测试报告
## 环境要求
- Python 3.7+
- 依赖包见 `requirements.txt`
## 安装
### 1. 安装依赖
```bash
pip install -r requirements.txt
```
### 2. 准备测试数据
将测试用例Excel文件和BUG列表Excel文件放入 `testcases/` 目录。
## 使用方法
### 方式一:交互式命令行模式
```bash
python run.py
```
按照提示输入文件路径即可。
### 方式二:命令行参数模式
```bash
python run.py --testcase testcases/测试用例.xlsx --buglist testcases/BUG列表.xlsx
```
完整参数说明:
| 参数 | 说明 |
|------|------|
| `--testcase` | 测试用例Excel文件路径 |
| `--buglist` | BUG列表Excel文件路径 |
| `--output` | 输出报告文件路径(可选) |
| `--project` | 项目名称(可选) |
| `--gui` | 使用GUI界面 |
| `--list` | 列出testcases目录下的可用文件 |
### 方式三:GUI界面模式
```bash
python run.py --gui
```
## 文件说明
### 目录结构
```
FunctionalTestReportGeneration/
├── src/ # 源代码目录
│ ├── config.py # 配置模块
│ ├── excel_reader.py # Excel读取模块
│ ├── data_analyzer.py # 数据分析模块
│ ├── chart_generator.py # 图表生成模块
│ ├── report_generator.py # 报告生成模块
│ ├── cli.py # 命令行交互模块
│ ├── gui.py # GUI界面模块
│ └── main.py # 主入口模块
├── config/ # 配置文件目录
│ ├── 功能测试报告模板.md
│ ├── BUG列表模板数据.xlsx
│ └── 功能测试用例模板.xlsx
├── testcases/ # 测试数据目录
├── reports/ # 报告输出目录
├── temp/ # 临时文件目录(图表等)
├── run.py # 入口脚本
├── requirements.txt # 依赖包列表
└── README.md # 本文档
```
## 数据格式说明
### 测试用例Excel文件格式
| 列号 | 列名 | 必填 | 说明 |
|------|------|------|------|
| 1 | 序列号 | ✅ | 用例序号 |
| 2 | 功能模块 | ✅ | 功能模块名称 |
| 3 | 功能类别 | ✅ | 功能类别 |
| 4 | 用例编号 | ✅ | 用例编号(如:XTY-001) |
| 5 | 功能描述 | ✅ | 功能描述 |
| 6 | 用例等级 | ✅ | 用例等级(1-4级) |
| 7 | 功能编号 | ✅ | 功能编号 |
| 8 | 用例名称 | ✅ | 用例名称 |
| 9 | 预置条件 | ✅ | 预置条件 |
| 10 | STEP | ✅ | 测试步骤 |
| 13 | 测试结果 | ✅ | 测试结果(通过/失败/未验证/未开发) |
**注意:** 表头在第3行,数据从第4行开始。
### BUG列表Excel文件格式
| 列号 | 列名 | 必填 | 说明 |
|------|------|------|------|
| 1 | 项目名称 | ✅ | 项目名称 |
| 2 | BUG类型 | ✅ | BUG类型 |
| 3 | BUG等级 | ✅ | BUG等级(1级/2级/3级/4级) |
| 4 | BUG状态 | ✅ | BUG状态(激活/已解决/已关闭) |
| 5 | BUG名称 | ✅ | BUG名称 |
| 6 | 提单人员 | ✅ | 提单人员 |
| 7 | 主负责人员 | ✅ | 主负责人员 |
| 8 | 截止日期 | ✅ | 截止日期 |
| 9 | 复现步骤 | ✅ | 复现步骤(包含用例编号) |
**注意:** 表头在第1行,数据从第2行开始。
## 输出报告
报告将保存到 `reports/` 目录,文件名格式为:
```
{项目名称}_功能测试报告_{日期}.docx
```
报告包含以下内容:
1. 报告基本信息
2. 测试执行摘要
3. 测试范围
4. 测试用例执行详情
5. BUG详细列表
6. 图表分析
## 常见问题
### Q1: 提示"未安装python-docx库"
**解决方案:**
```bash
pip install python-docx
```
### Q2: 提示"未安装matplotlib库"
**解决方案:**
```bash
pip install matplotlib
```
### Q3: 中文显示乱码
**解决方案:**
- Windows系统:确保已安装微软雅黑字体
- Linux系统:安装中文字体包 `fonts-wqy-microhei``fonts-wqy-zenhei`
### Q4: 图表无法显示
**解决方案:**
检查matplotlib是否正确安装,并确保系统有可用的中文字体。
## 版本历史
| 版本 | 日期 | 说明 |
|------|------|------|
| v1.0.0 | 2026-03-09 | 初始版本 |
## 联系方式
如有问题或建议,请联系开发团队。
......@@ -14,18 +14,20 @@
### 1.2 测试概览
| 项目 | 内容 |
|------|------|
| 项目名称 | {项目名称} |
| 测试周期 | {开始日期} ~ {结束日期} |
| 测试负责人 | {测试负责人} |
| 测试人员 | {测试人员列表} |
| 测试环境 | {测试环境名称} |
| 测试类型 | 功能测试 |
| hytest版本 | {hytest版本号} |
| 测试版本 | {测试版本号} |
### 1.3 被测系统信息
| 项目 | 内容 |
|------|------|
| 系统名称 | {系统名称} |
| 系统版本 | {系统版本号} |
| 测试版本 | {测试版本号} |
| 服务器地址 | {服务器IP} |
| 部署环境 | {部署环境描述} |
---
......@@ -36,25 +38,26 @@
| 统计项 | 数量 | 占比 |
|--------|------|------|
| 预备执行用例数量 | {预备执行用例数} | - |
| 实际执行用例数量 | {实际执行用例数} | 100% |
| 测试用例总数 | {用例总数} | 100% |
| 执行用例数量 | {执行用例数} | {执行率}% |
| 通过用例数量 | {通过用例数} | {通过率}% |
| 失败用例数量 | {失败用例数} | {失败率}% |
| 异常用例数量 | {异常用例数} | {异常率}% |
| 阻塞用例数量 | {阻塞用例数} | {阻塞率}% |
| 未执行用例数量 | {未执行用例数} | {未执行率}% |
### 2.2 用例执行结果
### 2.2 BUG统计汇总
```
用例通过率: {通过率}%
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
通过: {通过用例数} | 失败: {失败用例数} | 异常: {异常用例数} | 阻塞: {阻塞用例数}
```
| BUG等级 | 数量 | 占比 | 已关闭 | 未关闭 |
|---------|------|------|--------|--------|
| 1级(致命) | {1级数量} | {占比}% | {已关闭数} | {未关闭数} |
| 2级(严重) | {2级数量} | {占比}% | {已关闭数} | {未关闭数} |
| 3级(一般) | {3级数量} | {占比}% | {已关闭数} | {未关闭数} |
| 4级(轻微) | {4级数量} | {占比}% | {已关闭数} | {未关闭数} |
| **合计** | **{BUG总数}** | **100%** | **{已关闭总数}** | **{未关闭总数}** |
### 2.3 测试结论
- [ ] 测试通过 - 系统功能正常,可以发布
- [ ] 测试有条件通过 - 存在非阻塞性缺陷,经评估可以发布
- [ ] 测试不通过 - 存在阻塞性缺陷,不建议发布
- [ ] **测试通过** - 系统功能正常,无致命/严重缺陷,可以发布
- [ ] **有条件通过** - 存在非阻塞性缺陷,经评估可以发布
- [ ] **测试不通过** - 存在阻塞性缺陷,不建议发布
### 2.4 测试建议
{测试建议和风险评估内容}
......@@ -63,102 +66,103 @@
## 三、测试范围
### 3.1 测试模块
| 序号 | 模块名称 | 测试用例数 | 测试状态 | 备注 |
|------|----------|------------|----------|------|
| 1 | {模块名称1} | {用例数} | [已测试/部分测试/未测试] | {备注} |
| 2 | {模块名称2} | {用例数} | [已测试/部分测试/未测试] | {备注} |
| ... | ... | ... | ... | ... |
### 3.1 测试模块覆盖
| 序号 | 功能模块 | 用例数量 | 通过 | 失败 | 未执行 | 覆盖率 |
|------|----------|----------|------|------|--------|--------|
| 1 | {模块名称1} | {用例数} | {通过数} | {失败数} | {未执行数} | {百分比}% |
| 2 | {模块名称2} | {用例数} | {通过数} | {失败数} | {未执行数} | {百分比}% |
| ... | ... | ... | ... | ... | ... | ... |
### 3.2 功能覆盖情况
| 功能类别 | 覆盖功能点 | 未覆盖功能点 | 覆盖率 |
|----------|------------|--------------|--------|
| {类别1} | {数量} | {数量} | {百分比}% |
| {类别2} | {数量} | {数量} | {百分比}% |
### 3.3 未测试功能说明
- [ ] {未测试功能1} - {原因说明}
- [ ] {未测试功能2} - {原因说明}
### 3.2 功能类别分布
| 功能类别 | 用例数 | 占比 |
|----------|--------|------|
| {类别1} | {数量} | {百分比}% |
| {类别2} | {数量} | {百分比}% |
---
## 四、详细测试结果
## 四、测试用例执行详情
### 4.1 通过用例列表
| 序号 | 用例编号 | 用例名称 | 所属模块 | 执行时间 |
|------|----------|----------|----------|----------|
| 1 | {用例编号} | {用例名称} | {模块名称} | {执行时间} |
| 2 | {用例编号} | {用例名称} | {模块名称} | {执行时间} |
| 序号 | 用例编号 | 功能模块 | 用例名称 | 用例等级 | 测试结果 |
|------|----------|----------|----------|----------|----------|
| 1 | {用例编号} | {功能模块} | {用例名称} | {等级} | 通过 |
| 2 | {用例编号} | {功能模块} | {用例名称} | {等级} | 通过 |
### 4.2 失败用例列表
| 序号 | 用例编号 | 用例名称 | 所属模块 | 失败原因 | 严重程度 | 缺陷编号 |
|------|----------|----------|----------|----------|----------|----------|
| 1 | {用例编号} | {用例名称} | {模块名称} | {失败原因} | [致命/严重/一般/轻微] | {缺陷ID} |
| 2 | {用例编号} | {用例名称} | {模块名称} | {失败原因} | [致命/严重/一般/轻微] | {缺陷ID} |
### 4.3 异常用例列表
| 序号 | 用例编号 | 用例名称 | 所属模块 | 异常信息 | 堆栈跟踪 |
|------|----------|----------|----------|----------|----------|
| 1 | {用例编号} | {用例名称} | {模块名称} | {异常信息} | {堆栈跟踪摘要} |
| 序号 | 用例编号 | 功能模块 | 用例名称 | 用例等级 | 失败原因 | 关联BUG |
|------|----------|----------|----------|----------|----------|---------|
| 1 | {用例编号} | {功能模块} | {用例名称} | {等级} | {失败原因} | {BUG编号} |
| 2 | {用例编号} | {功能模块} | {用例名称} | {等级} | {失败原因} | {BUG编号} |
### 4.4 阻塞用例列表
| 序号 | 用例编号 | 用例名称 | 所属模块 | 阻塞原因 | 依赖内容 |
|------|----------|----------|----------|----------|----------|
| 1 | {用例编号} | {用例名称} | {模块名称} | {阻塞原因} | {依赖的用例或功能} |
### 4.3 未执行用例列表
| 序号 | 用例编号 | 功能模块 | 用例名称 | 用例等级 | 未执行原因 |
|------|----------|----------|----------|----------|------------|
| 1 | {用例编号} | {功能模块} | {用例名称} | {等级} | {原因} |
| 2 | {用例编号} | {功能模块} | {用例名称} | {等级} | {原因} |
---
## 五、缺陷分析
## 五、BUG详细列表
### 5.1 缺陷统计
| 严重程度 | 数量 | 占比 | 状态分布 |
|----------|------|------|----------|
| 致命 | {数量} | {占比}% | 新建:{数} 已解决:{数} 已关闭:{数} |
| 严重 | {数量} | {占比}% | 新建:{数} 已解决:{数} 已关闭:{数} |
| 一般 | {数量} | {占比}% | 新建:{数} 已解决:{数} 已关闭:{数} |
| 轻微 | {数量} | {占比}% | 新建:{数} 已解决:{数} 已关闭:{数} |
| **合计** | **{总数}** | **100%** | - |
### 5.1 BUG分类统计
| BUG类型 | 数量 | 占比 |
|---------|------|------|
| {类型1} | {数量} | {占比}% |
| {类型2} | {数量} | {占比}% |
### 5.2 缺陷分布
| 模块 | 致命 | 严重 | 一般 | 轻微 | 合计 |
|------|------|------|------|------|------|
### 5.2 按模块分布BUG
| 功能模块 | 1级 | 2级 | 3级 | 4级 | 合计 |
|----------|-----|-----|-----|-----|------|
| {模块1} | {数} | {数} | {数} | {数} | {合计} |
| {模块2} | {数} | {数} | {数} | {数} | {合计} |
| **合计** | {合计} | {合计} | {合计} | {合计} | {总合计} |
### 5.3 缺陷趋势分析
{缺陷修复趋势图表或描述}
### 5.3 BUG详细记录
| 序号 | BUG编号 | BUG等级 | BUG状态 | BUG名称 | BUG类型 | 提单人员 | 负责人员 | 截止日期 |
|------|----------|----------|----------|----------|----------|----------|----------|----------|
| 1 | {编号} | {等级} | {状态} | {名称} | {类型} | {人员} | {人员} | {日期} |
| 2 | {编号} | {等级} | {状态} | {名称} | {类型} | {人员} | {人员} | {日期} |
### 5.4 遗留问题说明
| 缺陷编号 | 问题描述 | 严重程度 | 遗留原因 | 后续计划 |
|----------|----------|----------|----------|----------|
| {缺陷ID} | {问题描述} | [致命/严重/一般/轻微] | {遗留原因} | {后续计划} |
### 5.4 遗留BUG说明
| BUG编号 | BUG等级 | 问题描述 | 遗留原因 | 后续计划 |
|---------|----------|----------|----------|----------|
| {编号} | {等级} | {描述} | {原因} | {计划} |
### 5.5 BUG复现步骤示例
**BUG编号:** {BUG编号}
**BUG名称:** {BUG名称}
**复现步骤:**
```
{复现步骤内容}
```
---
## 六、测试环境
### 6.1 硬件环境
| 设备类型 | 配置信息 | 数量 |
|----------|----------|------|
| 服务器 | CPU:{CPU}<br>内存:{内存}<br>磁盘:{磁盘} | {数量} |
| 客户端 | 操作系统:{OS}<br>浏览器:{浏览器} | {数量} |
| 移动设备 | 设备型号:{型号}<br>操作系统:{OS版本} | {数量} |
### 6.2 软件环境
| 软件类型 | 名称及版本 | 备注 |
|----------|------------|------|
| 操作系统 | {OS名称} {版本} | - |
| 数据库 | {数据库类型} {版本} | - |
| 中间件 | {中间件名称} {版本} | - |
| 浏览器 | {浏览器名称} {版本} | - |
### 6.3 网络环境
| 网络类型 | 网络配置 | 备注 |
|----------|----------|------|
| 内网 | {IP段}/{网段} | - |
| 外网 | {IP地址}/{域名} | - |
| VPN | {VPN配置} | - |
### 6.1 服务器环境
| 项目 | 配置信息 |
|------|----------|
| 服务器IP | {服务器IP} |
| 操作系统 | {OS名称} {版本} |
| 数据库 | {数据库类型} {版本} |
| 中间件 | {中间件名称} {版本} |
| 其他服务 | {服务列表} |
### 6.2 客户端环境
| 项目 | 配置信息 |
|------|----------|
| 测试终端 | {终端类型/型号} |
| 操作系统 | {OS名称} {版本} |
| 浏览器 | {浏览器名称} {版本} |
| 网络环境 | {网络描述} |
### 6.3 测试账号
| 角色 | 账号 | 密码 | 权限说明 |
|------|------|------|----------|
| {角色1} | {账号} | {密码} | {权限说明} |
| {角色2} | {账号} | {密码} | {权限说明} |
---
......@@ -169,52 +173,55 @@
|--------|------|
| 测试开始时间 | {开始时间} |
| 测试结束时间 | {结束时间} |
| 测试总耗时 | {总耗时} |
| 平均用例执行时间 | {平均时间} 秒/用例 |
| 测试总耗时 | {总耗时} |
| 平均用例执行时间 | {平均时间} |
### 7.2 初始化/清除执行情况
| 初始化/清除类型 | 失败次数 | 错误信息 |
|-----------------|----------|----------|
| 套件初始化 | {数量} | {错误信息} |
| 套件清除 | {数量} | {错误信息} |
| 用例初始化 | {数量} | {错误信息} |
| 用例清除 | {数量} | {错误信息} |
### 7.2 执行人员分工
| 测试人员 | 负责模块 | 执行用例数 | 发现BUG数 |
|----------|----------|------------|-----------|
| {人员1} | {模块列表} | {数量} | {数量} |
| {人员2} | {模块列表} | {数量} | {数量} |
---
## 八、测试截图/日志
## 八、测试附件
### 8.1 关键测试截图
### 8.1 测试截图
| 用例编号 | 截图描述 | 截图路径 |
|----------|----------|----------|
| {用例编号} | {描述} | {相对路径} |
### 8.2 错误日志摘要
| 错误编号 | 发生时间 | 错误级别 | 日志文件路径 |
|----------|----------|----------|--------------|
| {错误ID} | {时间} | [ERROR/WARN/INFO] | {日志路径} |
### 8.2 测试日志
| 日志类型 | 日志文件路径 | 说明 |
|----------|--------------|------|
| {类型} | {路径} | {说明} |
---
## 九、风险评估
## 九、风险评估与建议
### 9.1 质量风险
| 风险项 | 风险等级 | 风险描述 | 缓解措施 |
### 9.1 质量风险分析
| 风险项 | 风险等级 | 影响范围 | 缓解措施 |
|--------|----------|----------|----------|
| {风险1} | [高/中/低] | {描述} | {措施} |
| {风险2} | [高/中/低] | {描述} | {措施} |
| {风险1} | [高/中/低] | {范围} | {措施} |
| {风险2} | [高/中/低] | {范围} | {措施} |
### 9.2 发布建议
- [ ] 建议发布 - 质量达标,风险可控
- [ ] 建议延期 - 存在重大缺陷,需要修复
- [ ] 建议有条件发布 - 存在已知问题,需要制定规避方案
- [ ] **建议发布** - 质量达标,风险可控
- [ ] **建议延期** - 存在重大缺陷,需要修复
- [ ] **有条件发布** - 存在已知问题,需制定规避方案
### 9.3 改进建议
{对开发、测试流程的改进建议}
---
## 十、附录
### 10.1 用例执行明细
{详细的用例执行记录,可链接到HTML测试报告}
### 10.1 用例与BUG关联表
| 用例编号 | 用例名称 | 关联BUG编号 | BUG状态 |
|----------|----------|-------------|---------|
| {用例编号} | {用例名称} | {BUG编号} | {状态} |
### 10.2 术语表
| 术语 | 定义 |
......@@ -238,7 +245,10 @@
## 优化功能回填
- [ ] 添加图表可视化功能
- [ ] 添加图表可视化功能(饼图、柱状图)
- [ ] 增加导出为PDF功能
- [ ] 支持自定义报告模板
- [ ] 增加邮件通知功能
- [ ] 支持多语言(中英文)切换
- [ ] 增加用例与BUG的自动关联功能
- [ ] 支持测试报告对比(版本间对比)
# 功能测试报告自动生成工具 - 依赖包列表
# Excel文件读取
openpyxl>=3.1.0
# Word文档操作
python-docx>=1.0.0
# 图表生成
matplotlib>=3.7.0
# 进度条显示(可选)
tqdm>=4.65.0
# -*- coding: utf-8 -*-
"""
功能测试报告自动生成工具 - 入口脚本
本脚本是程序的主入口,位于项目根目录
"""
import sys
import os
# 添加src目录到Python路径
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
SRC_DIR = os.path.join(ROOT_DIR, "src")
sys.path.insert(0, SRC_DIR)
sys.path.insert(0, ROOT_DIR)
from src.main import main
if __name__ == "__main__":
main()
# -*- coding: utf-8 -*-
"""
功能测试报告自动生成工具
本工具通过读取测试用例Excel文件和BUG列表Excel文件,
自动生成功能测试报告(Word格式)
"""
__version__ = "1.0.0"
from src.config import *
# -*- coding: utf-8 -*-
"""
功能测试报告自动生成工具 - 图表生成模块
本模块使用matplotlib生成统计图表,
包括BUG等级分布图、用例执行结果分布图、用例通过率图
"""
import matplotlib
matplotlib.use('Agg') # 使用非交互式后端
import matplotlib.pyplot as plt
from matplotlib import font_manager
from pathlib import Path
from typing import Dict, List, Tuple
import os
from src.data_analyzer import TestCaseAnalysis, BugAnalysis
from src.config import (
TEMP_DIR,
CHART_FORMAT,
CHART_DPI,
CHART_FIGURE_SIZE,
CHINESE_FONT,
CHART_COLORS,
# 测试结果枚举
TEST_RESULT_PASS,
TEST_RESULT_FAIL,
TEST_RESULT_UNVERIFIED,
TEST_RESULT_UNDEVELOPED,
# BUG等级枚举
BUG_LEVEL_1,
BUG_LEVEL_2,
BUG_LEVEL_3,
BUG_LEVEL_4,
# BUG等级描述
BUG_LEVEL_DESCRIPTION,
)
def _setup_chinese_font() -> None:
"""
配置中文字体
"""
# Windows系统字体
windows_fonts = [
"Microsoft YaHei",
"SimHei",
"SimSun",
"KaiTi",
]
# Linux系统字体
linux_fonts = [
"WenQuanYi Micro Hei",
"WenQuanYi Zen Hei",
"Droid Sans Fallback",
]
# macOS系统字体
mac_fonts = [
"PingFang SC",
"Heiti SC",
"STHeiti",
]
# 根据操作系统选择字体
system = os.name
if system == 'nt': # Windows
fonts = windows_fonts
elif system == 'posix': # Linux/macOS
import platform
if platform.system() == 'Darwin': # macOS
fonts = mac_fonts
else: # Linux
fonts = linux_fonts
else:
fonts = windows_fonts
# 尝试设置中文字体
font_set = False
for font_name in fonts:
try:
plt.rcParams['font.sans-serif'] = [font_name]
# 测试是否能正确显示中文
fig, ax = plt.subplots(figsize=(1, 1))
ax.text(0.5, 0.5, '测试', fontsize=12)
plt.close(fig)
font_set = True
break
except:
continue
if not font_set:
# 如果所有字体都失败,使用默认配置
plt.rcParams['font.sans-serif'] = ['DejaVu Sans']
# 解决负号显示问题
plt.rcParams['axes.unicode_minus'] = False
def _get_value_with_percentage(value: int, total: int) -> str:
"""
获取带百分比的值标签
Args:
value: 数值
total: 总数
Returns:
带百分比的字符串(如:"10 (20%)")
"""
if total == 0:
return f"{value} (0%)"
percentage = round(value / total * 100, 1)
return f"{value} ({percentage}%)"
def generate_bug_level_chart(
analysis: BugAnalysis,
output_path: str = None
) -> str:
"""
生成BUG等级分布图(饼图)
Args:
analysis: BUG分析结果
output_path: 输出文件路径(可选,默认使用临时目录)
Returns:
图片文件路径
"""
# 配置中文字体
_setup_chinese_font()
# 准备数据
levels = [BUG_LEVEL_1, BUG_LEVEL_2, BUG_LEVEL_3, BUG_LEVEL_4]
values = [
analysis.level_distribution.get(BUG_LEVEL_1, 0),
analysis.level_distribution.get(BUG_LEVEL_2, 0),
analysis.level_distribution.get(BUG_LEVEL_3, 0),
analysis.level_distribution.get(BUG_LEVEL_4, 0),
]
labels = [
f"{BUG_LEVEL_DESCRIPTION[BUG_LEVEL_1]} ({BUG_LEVEL_1})",
f"{BUG_LEVEL_DESCRIPTION[BUG_LEVEL_2]} ({BUG_LEVEL_2})",
f"{BUG_LEVEL_DESCRIPTION[BUG_LEVEL_3]} ({BUG_LEVEL_3})",
f"{BUG_LEVEL_DESCRIPTION[BUG_LEVEL_4]} ({BUG_LEVEL_4})",
]
# 过滤掉值为0的数据
filtered_data = [(label, value) for label, value in zip(labels, values) if value > 0]
if not filtered_data:
labels = ["无BUG"]
values = [1]
else:
labels, values = zip(*filtered_data)
# 创建图表
fig, ax = plt.subplots(figsize=CHART_FIGURE_SIZE)
# 颜色
colors = [
CHART_COLORS["bug_1"],
CHART_COLORS["bug_2"],
CHART_COLORS["bug_3"],
CHART_COLORS["bug_4"],
]
# 绘制饼图
wedges, texts, autotexts = ax.pie(
values,
labels=labels,
colors=colors[:len(values)],
autopct='%1.1f%%',
startangle=90,
textprops={'fontsize': 12}
)
# 设置百分比文字样式
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontweight('bold')
# 设置标题
ax.set_title('BUG等级分布', fontsize=16, fontweight='bold', pad=20)
# 确保饼图是圆形
ax.axis('equal')
# 保存图片
if output_path is None:
output_path = str(TEMP_DIR / f"bug_level_chart{CHART_FORMAT}")
plt.savefig(output_path, dpi=CHART_DPI, bbox_inches='tight')
plt.close()
return output_path
def generate_test_result_chart(
analysis: TestCaseAnalysis,
output_path: str = None
) -> str:
"""
生成用例执行结果分布图(饼图)
Args:
analysis: 用例分析结果
output_path: 输出文件路径(可选,默认使用临时目录)
Returns:
图片文件路径
"""
# 配置中文字体
_setup_chinese_font()
# 准备数据
results = [
TEST_RESULT_PASS,
TEST_RESULT_FAIL,
TEST_RESULT_UNVERIFIED,
TEST_RESULT_UNDEVELOPED,
]
values = [
analysis.passed_cases,
analysis.failed_cases,
analysis.unverified_cases,
analysis.undeveloped_cases,
]
labels = [
f"{TEST_RESULT_PASS}",
f"{TEST_RESULT_FAIL}",
f"{TEST_RESULT_UNVERIFIED}",
f"{TEST_RESULT_UNDEVELOPED}",
]
# 过滤掉值为0的数据
filtered_data = [(label, value) for label, value in zip(labels, values) if value > 0]
if not filtered_data:
labels = ["无测试数据"]
values = [1]
else:
labels, values = zip(*filtered_data)
# 创建图表
fig, ax = plt.subplots(figsize=CHART_FIGURE_SIZE)
# 颜色
colors = [
CHART_COLORS["pass"],
CHART_COLORS["fail"],
CHART_COLORS["unverified"],
CHART_COLORS["undeveloped"],
]
# 绘制饼图
wedges, texts, autotexts = ax.pie(
values,
labels=labels,
colors=colors[:len(values)],
autopct='%1.1f%%',
startangle=90,
textprops={'fontsize': 12}
)
# 设置百分比文字样式
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontweight('bold')
# 设置标题
ax.set_title('用例执行结果分布', fontsize=16, fontweight='bold', pad=20)
# 确保饼图是圆形
ax.axis('equal')
# 保存图片
if output_path is None:
output_path = str(TEMP_DIR / f"test_result_chart{CHART_FORMAT}")
plt.savefig(output_path, dpi=CHART_DPI, bbox_inches='tight')
plt.close()
return output_path
def generate_pass_rate_chart(
analysis: TestCaseAnalysis,
output_path: str = None
) -> str:
"""
生成用例通过率图(柱状图)
Args:
analysis: 用例分析结果
output_path: 输出文件路径(可选,默认使用临时目录)
Returns:
图片文件路径
"""
# 配置中文字体
_setup_chinese_font()
# 准备数据
categories = ['通过', '失败', '未验证', '未开发']
values = [
analysis.passed_cases,
analysis.failed_cases,
analysis.unverified_cases,
analysis.undeveloped_cases,
]
# 创建图表
fig, ax = plt.subplots(figsize=CHART_FIGURE_SIZE)
# 颜色
colors = [
CHART_COLORS["pass"],
CHART_COLORS["fail"],
CHART_COLORS["unverified"],
CHART_COLORS["undeveloped"],
]
# 绘制柱状图
bars = ax.bar(categories, values, color=colors, edgecolor='black', linewidth=1.5)
# 在柱子上显示数值
for bar in bars:
height = bar.get_height()
if height > 0:
ax.text(
bar.get_x() + bar.get_width() / 2.,
height,
f'{int(height)}',
ha='center',
va='bottom',
fontsize=12,
fontweight='bold'
)
# 设置标题和标签
ax.set_title('用例执行情况统计', fontsize=16, fontweight='bold', pad=20)
ax.set_xlabel('测试结果', fontsize=12, fontweight='bold')
ax.set_ylabel('用例数量', fontsize=12, fontweight='bold')
# 显示网格
ax.grid(axis='y', alpha=0.3, linestyle='--')
# 添加通过率标注
total = sum(values)
if total > 0:
pass_rate = analysis.pass_rate
ax.text(
0.98, 0.95,
f'通过率: {pass_rate}%',
transform=ax.transAxes,
ha='right',
va='top',
fontsize=14,
fontweight='bold',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)
)
# 保存图片
if output_path is None:
output_path = str(TEMP_DIR / f"pass_rate_chart{CHART_FORMAT}")
plt.savefig(output_path, dpi=CHART_DPI, bbox_inches='tight')
plt.close()
return output_path
def generate_bug_by_module_chart(
analysis: BugAnalysis,
output_path: str = None
) -> str:
"""
生成按模块分布的BUG图(堆叠柱状图)
Args:
analysis: BUG分析结果
output_path: 输出文件路径(可选,默认使用临时目录)
Returns:
图片文件路径
"""
# 配置中文字体
_setup_chinese_font()
# 准备数据
modules = list(analysis.module_distribution.keys())
# 获取各模块的BUG数据
level_1_data = []
level_2_data = []
level_3_data = []
level_4_data = []
for module in modules:
dist = analysis.module_distribution.get(module, {})
level_1_data.append(dist.get(BUG_LEVEL_1, 0))
level_2_data.append(dist.get(BUG_LEVEL_2, 0))
level_3_data.append(dist.get(BUG_LEVEL_3, 0))
level_4_data.append(dist.get(BUG_LEVEL_4, 0))
# 创建图表
fig, ax = plt.subplots(figsize=(12, 6))
# 绘制堆叠柱状图
width = 0.6
x = range(len(modules))
ax.bar(x, level_1_data, width, label=f'{BUG_LEVEL_DESCRIPTION[BUG_LEVEL_1]}', color=CHART_COLORS["bug_1"])
ax.bar(x, level_2_data, width, bottom=level_1_data, label=f'{BUG_LEVEL_DESCRIPTION[BUG_LEVEL_2]}', color=CHART_COLORS["bug_2"])
# 计算第三层的底部(1级+2级)
level_3_bottom = [level_1_data[i] + level_2_data[i] for i in range(len(modules))]
ax.bar(x, level_3_data, width, bottom=level_3_bottom, label=f'{BUG_LEVEL_DESCRIPTION[BUG_LEVEL_3]}', color=CHART_COLORS["bug_3"])
# 计算第四层的底部(1级+2级+3级)
level_4_bottom = [level_3_bottom[i] + level_3_data[i] for i in range(len(modules))]
ax.bar(x, level_4_data, width, bottom=level_4_bottom, label=f'{BUG_LEVEL_DESCRIPTION[BUG_LEVEL_4]}', color=CHART_COLORS["bug_4"])
# 设置标题和标签
ax.set_title('各模块BUG分布(按等级)', fontsize=16, fontweight='bold', pad=20)
ax.set_xlabel('模块', fontsize=12, fontweight='bold')
ax.set_ylabel('BUG数量', fontsize=12, fontweight='bold')
# 设置x轴标签
ax.set_xticks(x)
ax.set_xticklabels(modules, rotation=45, ha='right')
# 添加图例
ax.legend(loc='upper right')
# 显示网格
ax.grid(axis='y', alpha=0.3, linestyle='--')
plt.tight_layout()
# 保存图片
if output_path is None:
output_path = str(TEMP_DIR / f"bug_by_module_chart{CHART_FORMAT}")
plt.savefig(output_path, dpi=CHART_DPI, bbox_inches='tight')
plt.close()
return output_path
def generate_all_charts(
case_analysis: TestCaseAnalysis,
bug_analysis: BugAnalysis
) -> Dict[str, str]:
"""
生成所有图表
Args:
case_analysis: 用例分析结果
bug_analysis: BUG分析结果
Returns:
图表文件路径字典 {图表类型: 文件路径}
"""
charts = {}
try:
charts["bug_level"] = generate_bug_level_chart(bug_analysis)
except Exception as e:
print(f"生成BUG等级分布图失败: {str(e)}")
try:
charts["test_result"] = generate_test_result_chart(case_analysis)
except Exception as e:
print(f"生成用例执行结果分布图失败: {str(e)}")
try:
charts["pass_rate"] = generate_pass_rate_chart(case_analysis)
except Exception as e:
print(f"生成用例通过率图失败: {str(e)}")
try:
charts["bug_by_module"] = generate_bug_by_module_chart(bug_analysis)
except Exception as e:
print(f"生成各模块BUG分布图失败: {str(e)}")
return charts
# -*- coding: utf-8 -*-
"""
功能测试报告自动生成工具 - 命令行交互模块
本模块负责命令行参数解析、用户输入获取、进度显示
"""
import argparse
import sys
from pathlib import Path
from typing import Dict, Optional
from src.excel_reader import read_test_cases, read_bug_list
from src.data_analyzer import analyze_test_cases, analyze_bugs, link_bugs_to_cases
from src.chart_generator import generate_all_charts
from src.report_generator import generate_report
from src.config import (
TESTCASES_DIR,
REPORTS_DIR,
get_report_filename,
)
def parse_arguments() -> argparse.Namespace:
"""
解析命令行参数
Returns:
参数对象
"""
parser = argparse.ArgumentParser(
prog='功能测试报告生成工具',
description='通过测试用例和BUG列表自动生成功能测试报告(Word格式)'
)
parser.add_argument(
'--testcase',
type=str,
help='测试用例Excel文件路径'
)
parser.add_argument(
'--buglist',
type=str,
help='BUG列表Excel文件路径'
)
parser.add_argument(
'--output',
type=str,
help='输出报告文件路径(可选,默认为reports目录)'
)
parser.add_argument(
'--project',
type=str,
help='项目名称(可选,默认从BUG列表中读取)'
)
parser.add_argument(
'--gui',
action='store_true',
help='使用GUI界面'
)
parser.add_argument(
'--list',
action='store_true',
help='列出testcases目录下的可用文件'
)
return parser.parse_args()
def list_available_files() -> None:
"""
列出testcases目录下的可用文件
"""
print("\n=== 测试用例文件 ===")
case_files = list(TESTCASES_DIR.glob("*测试用例*.xlsx"))
if case_files:
for i, f in enumerate(case_files, 1):
print(f" {i}. {f.name}")
else:
print(" 未找到测试用例文件")
print("\n=== BUG列表文件 ===")
bug_files = list(TESTCASES_DIR.glob("*BUG*.xlsx"))
if bug_files:
for i, f in enumerate(bug_files, 1):
print(f" {i}. {f.name}")
else:
print(" 未找到BUG列表文件")
print()
def interactive_input() -> Dict[str, str]:
"""
交互式输入
Returns:
输入数据字典
"""
print("\n=== 功能测试报告生成工具 ===\n")
# 列出可用文件
list_available_files()
result = {}
# 获取测试用例文件路径
while True:
testcase_path = input("请输入测试用例Excel文件路径(或直接回车使用默认): ").strip()
if not testcase_path:
# 尝试使用默认文件
case_files = list(TESTCASES_DIR.glob("*测试用例*.xlsx"))
if case_files:
testcase_path = str(case_files[0])
print(f"使用默认文件: {testcase_path}")
break
else:
print("未找到默认测试用例文件,请输入有效路径")
elif Path(testcase_path).exists():
break
else:
print(f"文件不存在: {testcase_path}")
result["testcase"] = testcase_path
# 获取BUG列表文件路径
while True:
buglist_path = input("请输入BUG列表Excel文件路径(或直接回车使用默认): ").strip()
if not buglist_path:
# 尝试使用默认文件
bug_files = list(TESTCASES_DIR.glob("*BUG*.xlsx"))
if bug_files:
buglist_path = str(bug_files[0])
print(f"使用默认文件: {buglist_path}")
break
else:
print("未找到默认BUG列表文件,请输入有效路径")
elif Path(buglist_path).exists():
break
else:
print(f"文件不存在: {buglist_path}")
result["buglist"] = buglist_path
# 获取输出路径
output_path = input(f"请输入输出报告路径(或直接回车保存到{REPORTS_DIR}目录): ").strip()
if not output_path:
result["output"] = None # 使用默认路径
else:
result["output"] = output_path
# 获取项目名称
project_name = input("请输入项目名称(或直接回车自动获取): ").strip()
result["project"] = project_name if project_name else None
return result
def show_progress(current: int, total: int, message: str = "") -> None:
"""
显示进度
Args:
current: 当前进度
total: 总数
message: 进度消息
"""
if total == 0:
percent = 100
else:
percent = int(current / total * 100)
bar_length = 50
filled = int(bar_length * current / total) if total > 0 else bar_length
bar = '█' * filled + '░' * (bar_length - filled)
print(f"\r[{bar}] {percent}% {message}", end='', flush=True)
if current >= total:
print() # 换行
def generate_report_main(
testcase_path: str,
buglist_path: str,
output_path: Optional[str] = None,
project_name: Optional[str] = None
) -> str:
"""
生成报告的主函数
Args:
testcase_path: 测试用例Excel文件路径
buglist_path: BUG列表Excel文件路径
output_path: 输出报告文件路径(可选)
project_name: 项目名称(可选)
Returns:
生成的报告文件路径
"""
print("\n=== 开始生成功能测试报告 ===\n")
# 步骤1:读取Excel数据
print("步骤1/5: 读取测试数据...")
try:
test_cases = read_test_cases(testcase_path)
print(f" ✓ 读取到 {len(test_cases)} 条测试用例")
except Exception as e:
print(f" ✗ 读取测试用例失败: {str(e)}")
sys.exit(1)
try:
bugs = read_bug_list(buglist_path)
print(f" ✓ 读取到 {len(bugs)} 条BUG记录")
except Exception as e:
print(f" ✗ 读取BUG列表失败: {str(e)}")
sys.exit(1)
# 获取项目名称
if not project_name and bugs:
project_name = bugs[0].project_name
if not project_name:
project_name = "未命名项目"
print(f" 项目名称: {project_name}")
# 步骤2:分析数据
print("\n步骤2/5: 分析测试数据...")
case_analysis = analyze_test_cases(test_cases)
print(f" ✓ 总用例数: {case_analysis.total_cases}")
print(f" ✓ 通过率: {case_analysis.pass_rate}%")
bug_analysis = analyze_bugs(bugs)
print(f" ✓ BUG总数: {bug_analysis.total_bugs}")
print(f" ✓ 遗留BUG: {bug_analysis.remaining_bugs}")
case_bug_link = link_bugs_to_cases(bugs, test_cases)
print(f" ✓ 关联了 {len(case_bug_link)} 个用例到BUG")
# 步骤3:生成图表
print("\n步骤3/5: 生成统计图表...")
try:
from src.chart_generator import generate_all_charts
chart_paths = generate_all_charts(case_analysis, bug_analysis)
print(f" ✓ 生成了 {len(chart_paths)} 个图表")
except Exception as e:
print(f" ⚠ 生成图表失败: {str(e)}")
chart_paths = {}
# 步骤4:生成报告
print("\n步骤4/5: 生成Word报告...")
if output_path is None:
output_path = str(REPORTS_DIR / get_report_filename(project_name))
try:
report_path = generate_report(
template_path="", # 暂不使用模板
case_analysis=case_analysis,
bug_analysis=bug_analysis,
case_bug_link=case_bug_link,
chart_paths=chart_paths,
output_path=output_path,
project_name=project_name,
cases=test_cases,
bugs=bugs
)
print(f" ✓ 报告已生成: {report_path}")
except Exception as e:
print(f" ✗ 生成报告失败: {str(e)}")
import traceback
traceback.print_exc()
sys.exit(1)
# 步骤5:完成
print("\n步骤5/5: 完成!")
print(f"\n报告已保存到: {report_path}")
return report_path
def main_cli() -> None:
"""
命令行主函数
"""
# 解析参数
args = parse_arguments()
# 处理--list参数
if args.list:
list_available_files()
return
# 处理--gui参数
if args.gui:
from src.gui import main_gui
main_gui()
return
# 获取输入参数
if args.testcase and args.buglist:
# 使用命令行参数
testcase_path = args.testcase
buglist_path = args.buglist
output_path = args.output
project_name = args.project
else:
# 交互式输入
inputs = interactive_input()
testcase_path = inputs["testcase"]
buglist_path = inputs["buglist"]
output_path = inputs.get("output")
project_name = inputs.get("project")
# 生成报告
try:
generate_report_main(
testcase_path=testcase_path,
buglist_path=buglist_path,
output_path=output_path,
project_name=project_name
)
except KeyboardInterrupt:
print("\n\n操作已取消")
sys.exit(0)
except Exception as e:
print(f"\n错误: {str(e)}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main_cli()
# -*- coding: utf-8 -*-
"""
功能测试报告自动生成工具 - 配置模块
本模块定义程序的所有常量、路径配置和数据枚举值
"""
from pathlib import Path
from typing import List
# ================================
# 路径配置
# ================================
# 项目根目录
BASE_DIR = Path(__file__).parent.parent
# 源代码目录
SRC_DIR = BASE_DIR / "src"
# 配置文件目录
CONFIG_DIR = BASE_DIR / "config"
# 测试用例数据目录
TESTCASES_DIR = BASE_DIR / "testcases"
# 报告输出目录
REPORTS_DIR = BASE_DIR / "reports"
# 临时文件目录(存放图表等)
TEMP_DIR = BASE_DIR / "temp"
# 模板文件路径
TEMPLATE_FILE = CONFIG_DIR / "功能测试报告模板.md"
BUG_TEMPLATE_FILE = CONFIG_DIR / "BUG列表模板数据.xlsx"
CASE_TEMPLATE_FILE = CONFIG_DIR / "功能测试用例模板.xlsx"
# ================================
# 测试结果枚举值
# ================================
TEST_RESULT_PASS = "通过"
TEST_RESULT_FAIL = "失败"
TEST_RESULT_UNVERIFIED = "未验证"
TEST_RESULT_UNDEVELOPED = "未开发"
# 所有测试结果枚举
TEST_RESULT_VALUES = [
TEST_RESULT_PASS,
TEST_RESULT_FAIL,
TEST_RESULT_UNVERIFIED,
TEST_RESULT_UNDEVELOPED
]
# ================================
# BUG状态枚举值
# ================================
BUG_STATUS_ACTIVE = "激活"
BUG_STATUS_RESOLVED = "已解决"
BUG_STATUS_CLOSED = "已关闭"
# 所有BUG状态枚举
BUG_STATUS_VALUES = [
BUG_STATUS_ACTIVE,
BUG_STATUS_RESOLVED,
BUG_STATUS_CLOSED
]
# 遗留BUG状态(未关闭的BUG)
REMAINING_BUG_STATUS: List[str] = [
BUG_STATUS_ACTIVE,
BUG_STATUS_RESOLVED
]
# ================================
# BUG等级枚举值
# ================================
BUG_LEVEL_1 = "1级" # 致命
BUG_LEVEL_2 = "2级" # 严重
BUG_LEVEL_3 = "3级" # 一般
BUG_LEVEL_4 = "4级" # 轻微
# 所有BUG等级枚举
BUG_LEVEL_VALUES = [
BUG_LEVEL_1,
BUG_LEVEL_2,
BUG_LEVEL_3,
BUG_LEVEL_4
]
# BUG等级描述映射
BUG_LEVEL_DESCRIPTION = {
BUG_LEVEL_1: "致命",
BUG_LEVEL_2: "严重",
BUG_LEVEL_3: "一般",
BUG_LEVEL_4: "轻微"
}
# ================================
# 日期格式配置
# ================================
# 日期格式(用于报告生成)
DATE_FORMAT = "%Y年%m月%d日"
# 报告文件名中的日期格式
FILENAME_DATE_FORMAT = "%Y年%m月%d日"
# ================================
# Excel数据读取配置
# ================================
# 测试用例Excel数据起始行(表头在第3行,数据从第4行开始)
CASE_DATA_START_ROW = 4
# BUG列表Excel数据起始行(表头在第1行,数据从第2行开始)
BUG_DATA_START_ROW = 2
# 测试用例Excel列索引(从1开始)
CASE_COL_SERIAL_NUMBER = 1 # 序列号
CASE_COL_MODULE = 2 # 功能模块
CASE_COL_CATEGORY = 3 # 功能类别
CASE_COL_CASE_NUMBER = 4 # 用例编号
CASE_COL_DESCRIPTION = 5 # 功能描述
CASE_COL_LEVEL = 6 # 用例等级
CASE_COL_FUNCTION_NUMBER = 7 # 功能编号
CASE_COL_NAME = 8 # 用例名称
CASE_COL_PRE_CONDITION = 9 # 预置条件
CASE_COL_STEPS = 10 # STEP(测试步骤)
CASE_COL_JSON = 11 # JSON配置
CASE_COL_EXPECTED_RESULT = 12 # 预期结果
CASE_COL_TEST_RESULT = 13 # 测试结果
CASE_COL_TEST_FREQUENCY = 14 # 测试频次
CASE_COL_LOGS_SCREENSHOTS = 15 # 日志/截图/照片
CASE_COL_SCREENSHOT2 = 16 # 截图二
CASE_COL_REMARKS = 17 # 备注
# BUG列表Excel列索引(从1开始)
BUG_COL_PROJECT_NAME = 1 # 项目名称
BUG_COL_BUG_TYPE = 2 # BUG类型
BUG_COL_BUG_LEVEL = 3 # BUG等级
BUG_COL_BUG_STATUS = 4 # BUG状态
BUG_COL_BUG_NAME = 5 # BUG名称
BUG_COL_REPORTER = 6 # 提单人员
BUG_COL_OWNER = 7 # 主负责人员
BUG_COL_DEADLINE = 8 # 截止日期
BUG_COL_REPRODUCTION_STEPS = 9 # 复现步骤
# ================================
# 报告生成配置
# ================================
# 报告输出格式
REPORT_FORMAT = ".docx"
# 报告命名规则:{项目名称}_功能测试报告_{日期}.docx
REPORT_FILENAME_TEMPLATE = "{project_name}_功能测试报告_{date}{format}"
# 报告编号规则:{项目名称}-{日期}
REPORT_NUMBER_TEMPLATE = "{project_name}-{date}"
# Word文档样式配置
WORD_FONT_NAME = "微软雅黑"
WORD_FONT_SIZE = 11
WORD_TITLE_FONT_SIZE = 16
# ================================
# 图表生成配置
# ================================
# 图表输出格式
CHART_FORMAT = ".png"
# 图表DPI(分辨率)
CHART_DPI = 300
# 图表尺寸(英寸)
CHART_FIGURE_SIZE = (10, 6)
# 中文字体配置
CHINESE_FONT = "Microsoft YaHei"
# 图表颜色配置
CHART_COLORS = {
"pass": "#04AA6D", # 绿色 - 通过
"fail": "#bb4069", # 红色 - 失败
"unverified": "#f0c040",# 黄色 - 未验证
"undeveloped": "#dcbdbd",# 灰色 - 未开发
"bug_1": "#d32f2f", # 深红 - 1级BUG
"bug_2": "#f57c00", # 橙色 - 2级BUG
"bug_3": "#fbc02d", # 黄色 - 3级BUG
"bug_4": "#388e3c", # 绿色 - 4级BUG
}
# ================================
# 交互配置
# ================================
# 默认超时时间(秒)
DEFAULT_TIMEOUT = 30
# 进度条显示配置
SHOW_PROGRESS_BAR = True
# ================================
# 日志配置
# ================================
# 日志级别
LOG_LEVEL = "INFO"
# 日志格式
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
# ================================
# 版本信息
# ================================
__version__ = "1.0.0"
__author__ = "Auto Report Generator"
__description__ = "功能测试报告自动生成工具"
def get_report_filename(project_name: str, date: str = None) -> str:
"""
获取报告文件名
Args:
project_name: 项目名称
date: 日期字符串(可选,默认使用当前日期)
Returns:
报告文件名
"""
if date is None:
from datetime import datetime
date = datetime.now().strftime(FILENAME_DATE_FORMAT)
return REPORT_FILENAME_TEMPLATE.format(
project_name=project_name,
date=date,
format=REPORT_FORMAT
)
def get_report_number(project_name: str, date: str = None) -> str:
"""
获取报告编号
Args:
project_name: 项目名称
date: 日期字符串(可选,默认使用当前日期)
Returns:
报告编号
"""
if date is None:
from datetime import datetime
date = datetime.now().strftime(FILENAME_DATE_FORMAT)
return REPORT_NUMBER_TEMPLATE.format(
project_name=project_name,
date=date
)
def ensure_directories() -> None:
"""
确保所有必要的目录存在
"""
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
TEMP_DIR.mkdir(parents=True, exist_ok=True)
# 模块导入时自动创建目录
ensure_directories()
# -*- coding: utf-8 -*-
"""
功能测试报告自动生成工具 - 数据分析模块
本模块负责统计测试用例执行情况、BUG数量和等级分布,
并进行用例-BUG关联分析
"""
from dataclasses import dataclass, field
from typing import List, Dict, Tuple
from collections import Counter, defaultdict
from src.excel_reader import TestCase, Bug
from src.config import (
# 测试结果枚举
TEST_RESULT_PASS,
TEST_RESULT_FAIL,
TEST_RESULT_UNVERIFIED,
TEST_RESULT_UNDEVELOPED,
# BUG状态枚举
BUG_STATUS_ACTIVE,
BUG_STATUS_RESOLVED,
BUG_STATUS_CLOSED,
REMAINING_BUG_STATUS,
# BUG等级枚举
BUG_LEVEL_1,
BUG_LEVEL_2,
BUG_LEVEL_3,
BUG_LEVEL_4,
)
@dataclass
class ModuleStat:
"""
模块统计数据
Attributes:
module_name: 模块名称
total_cases: 总用例数
passed_cases: 通过用例数
failed_cases: 失败用例数
unverified_cases: 未验证用例数
undeveloped_cases: 未开发用例数
pass_rate: 通过率(百分比)
"""
module_name: str
total_cases: int = 0
passed_cases: int = 0
failed_cases: int = 0
unverified_cases: int = 0
undeveloped_cases: int = 0
pass_rate: float = 0.0
@dataclass
class TestCaseAnalysis:
"""
测试用例分析结果
Attributes:
total_cases: 总用例数
executed_cases: 已执行用例数(通过+失败)
passed_cases: 通过用例数
failed_cases: 失败用例数
unverified_cases: 未验证用例数
undeveloped_cases: 未开发用例数
pass_rate: 通过率(百分比,保留两位小数)
execution_rate: 执行率(百分比,保留两位小数)
module_stats: 按模块统计的字典
"""
total_cases: int = 0
executed_cases: int = 0
passed_cases: int = 0
failed_cases: int = 0
unverified_cases: int = 0
undeveloped_cases: int = 0
pass_rate: float = 0.0
execution_rate: float = 0.0
module_stats: Dict[str, ModuleStat] = field(default_factory=dict)
@dataclass
class BugAnalysis:
"""
BUG分析结果
Attributes:
total_bugs: BUG总数
remaining_bugs: 遗留BUG数(激活+已解决)
closed_bugs: 已关闭BUG数
level_distribution: 等级分布字典 {等级: 数量}
status_distribution: 状态分布字典 {状态: 数量}
type_distribution: 类型分布字典 {类型: 数量}
module_distribution: 按模块分布字典 {模块: {等级: 数量}}
remaining_by_level: 遗留BUG按等级分布 {等级: 数量}
"""
total_bugs: int = 0
remaining_bugs: int = 0
closed_bugs: int = 0
level_distribution: Dict[str, int] = field(default_factory=dict)
status_distribution: Dict[str, int] = field(default_factory=dict)
type_distribution: Dict[str, int] = field(default_factory=dict)
module_distribution: Dict[str, Dict[str, int]] = field(default_factory=dict)
remaining_by_level: Dict[str, int] = field(default_factory=dict)
def analyze_test_cases(cases: List[TestCase]) -> TestCaseAnalysis:
"""
分析测试用例执行情况
Args:
cases: 测试用例列表
Returns:
用例分析结果
"""
if not cases:
return TestCaseAnalysis()
# 初始化统计
total = len(cases)
passed = 0
failed = 0
unverified = 0
undeveloped = 0
# 按模块分组统计
module_cases: Dict[str, List[TestCase]] = defaultdict(list)
for case in cases:
module_cases[case.module].append(case)
# 统计各状态数量
if case.test_result == TEST_RESULT_PASS:
passed += 1
elif case.test_result == TEST_RESULT_FAIL:
failed += 1
elif case.test_result == TEST_RESULT_UNVERIFIED:
unverified += 1
elif case.test_result == TEST_RESULT_UNDEVELOPED:
undeveloped += 1
else:
# 未填写或无效状态,计入未验证
unverified += 1
# 计算通过率和执行率
executed = passed + failed
pass_rate = round(passed / total * 100, 2) if total > 0 else 0.0
execution_rate = round(executed / total * 100, 2) if total > 0 else 0.0
# 按模块统计
module_stats: Dict[str, ModuleStat] = {}
for module_name, module_case_list in module_cases.items():
module_total = len(module_case_list)
module_passed = sum(1 for c in module_case_list if c.test_result == TEST_RESULT_PASS)
module_failed = sum(1 for c in module_case_list if c.test_result == TEST_RESULT_FAIL)
module_unverified = sum(1 for c in module_case_list if c.test_result == TEST_RESULT_UNVERIFIED)
module_undeveloped = sum(1 for c in module_case_list if c.test_result == TEST_RESULT_UNDEVELOPED)
module_pass_rate = round(module_passed / module_total * 100, 2) if module_total > 0 else 0.0
module_stats[module_name] = ModuleStat(
module_name=module_name,
total_cases=module_total,
passed_cases=module_passed,
failed_cases=module_failed,
unverified_cases=module_unverified,
undeveloped_cases=module_undeveloped,
pass_rate=module_pass_rate,
)
return TestCaseAnalysis(
total_cases=total,
executed_cases=executed,
passed_cases=passed,
failed_cases=failed,
unverified_cases=unverified,
undeveloped_cases=undeveloped,
pass_rate=pass_rate,
execution_rate=execution_rate,
module_stats=module_stats,
)
def analyze_bugs(bugs: List[Bug]) -> BugAnalysis:
"""
分析BUG数据
Args:
bugs: BUG列表
Returns:
BUG分析结果
"""
if not bugs:
return BugAnalysis()
total = len(bugs)
# 初始化计数器
level_counter = Counter()
status_counter = Counter()
type_counter = Counter()
# 遗留BUG计数器
remaining_level_counter = Counter()
# 按模块分布(需要从BUG名称中提取模块信息)
module_distribution: Dict[str, Dict[str, int]] = defaultdict(lambda: defaultdict(int))
for bug in bugs:
# 等级统计
level_counter[bug.bug_level] += 1
# 状态统计
status_counter[bug.bug_status] += 1
# 类型统计
type_counter[bug.bug_type] += 1
# 遗留BUG统计
if bug.bug_status in REMAINING_BUG_STATUS:
remaining_level_counter[bug.bug_level] += 1
# 从BUG名称中提取模块信息
module_name = _extract_module_from_bug_name(bug.bug_name)
module_distribution[module_name][bug.bug_level] += 1
# 计算遗留BUG数
remaining = sum(remaining_level_counter.values())
closed = status_counter.get(BUG_STATUS_CLOSED, 0)
# 转换为普通字典
level_distribution = dict(level_counter)
status_distribution = dict(status_counter)
type_distribution = dict(type_counter)
remaining_by_level = dict(remaining_level_counter)
module_dist = {k: dict(v) for k, v in module_distribution.items()}
return BugAnalysis(
total_bugs=total,
remaining_bugs=remaining,
closed_bugs=closed,
level_distribution=level_distribution,
status_distribution=status_distribution,
type_distribution=type_distribution,
module_distribution=module_dist,
remaining_by_level=remaining_by_level,
)
def group_by_module(cases: List[TestCase]) -> Dict[str, List[TestCase]]:
"""
按模块分组用例
Args:
cases: 测试用例列表
Returns:
模块->用例列表字典
"""
result: Dict[str, List[TestCase]] = defaultdict(list)
for case in cases:
result[case.module].append(case)
return dict(result)
def link_bugs_to_cases(
bugs: List[Bug],
cases: List[TestCase]
) -> Dict[str, List[Bug]]:
"""
关联BUG到用例
Args:
bugs: BUG列表
cases: 测试用例列表
Returns:
用例编号->BUG列表字典
"""
# 创建用例编号到用例的映射
case_map: Dict[str, TestCase] = {case.case_number: case for case in cases}
# 创建用例编号到BUG列表的映射
result: Dict[str, List[Bug]] = defaultdict(list)
for bug in bugs:
# 遍历BUG关联的所有用例编号
for case_number in bug.related_case_numbers:
# 只处理存在的用例编号
if case_number in case_map:
result[case_number].append(bug)
return dict(result)
def get_failed_cases(cases: List[TestCase]) -> List[TestCase]:
"""
获取失败的用例列表
Args:
cases: 测试用例列表
Returns:
失败用例列表
"""
return [case for case in cases if case.test_result == TEST_RESULT_FAIL]
def get_unexecuted_cases(cases: List[TestCase]) -> List[TestCase]:
"""
获取未执行的用例列表(未验证+未开发)
Args:
cases: 测试用例列表
Returns:
未执行用例列表
"""
return [
case for case in cases
if case.test_result in [TEST_RESULT_UNVERIFIED, TEST_RESULT_UNDEVELOPED]
]
def get_remaining_bugs(bugs: List[Bug]) -> List[Bug]:
"""
获取遗留BUG列表(激活+已解决)
Args:
bugs: BUG列表
Returns:
遗留BUG列表
"""
return [bug for bug in bugs if bug.bug_status in REMAINING_BUG_STATUS]
def calculate_pass_rate(cases: List[TestCase]) -> float:
"""
计算通过率
Args:
cases: 测试用例列表
Returns:
通过率(百分比,保留两位小数)
"""
if not cases:
return 0.0
total = len(cases)
passed = sum(1 for case in cases if case.test_result == TEST_RESULT_PASS)
return round(passed / total * 100, 2)
def _extract_module_from_bug_name(bug_name: str) -> str:
"""
从BUG名称中提取模块信息
Args:
bug_name: BUG名称
Returns:
模块名称
Examples:
>>> _extract_module_from_bug_name("【新统一平台-权限管理】权限管理新增数据后接口没有返回数据信息")
"权限管理"
>>> _extract_module_from_bug_name("用户管理-用户列表查询异常")
"用户管理"
"""
import re
if not bug_name:
return "其他"
# 匹配【模块名】格式
match = re.search(r'【.*?-(.*?)】', bug_name)
if match:
return match.group(1)
# 匹配"模块-"格式
match = re.search(r'([^【】-]+)-', bug_name)
if match:
return match.group(1)
return "其他"
def get_test_result_summary(cases: List[TestCase]) -> Dict[str, int]:
"""
获取测试结果汇总
Args:
cases: 测试用例列表
Returns:
测试结果字典 {结果: 数量}
"""
summary = {
TEST_RESULT_PASS: 0,
TEST_RESULT_FAIL: 0,
TEST_RESULT_UNVERIFIED: 0,
TEST_RESULT_UNDEVELOPED: 0,
}
for case in cases:
if case.test_result in summary:
summary[case.test_result] += 1
return summary
def get_bug_summary(bugs: List[Bug]) -> Dict[str, int]:
"""
获取BUG汇总(按等级)
Args:
bugs: BUG列表
Returns:
BUG等级字典 {等级: 数量}
"""
summary = {
BUG_LEVEL_1: 0,
BUG_LEVEL_2: 0,
BUG_LEVEL_3: 0,
BUG_LEVEL_4: 0,
}
for bug in bugs:
if bug.bug_level in summary:
summary[bug.bug_level] += 1
return summary
# -*- coding: utf-8 -*-
"""
功能测试报告自动生成工具 - Excel读取模块
本模块负责读取测试用例Excel文件和BUG列表Excel文件,
并进行数据格式验证和结构转换
"""
import openpyxl
from dataclasses import dataclass, field
from typing import List, Optional
import re
from pathlib import Path
from src.config import (
# 测试用例相关配置
CASE_DATA_START_ROW,
CASE_COL_SERIAL_NUMBER,
CASE_COL_MODULE,
CASE_COL_CATEGORY,
CASE_COL_CASE_NUMBER,
CASE_COL_DESCRIPTION,
CASE_COL_LEVEL,
CASE_COL_FUNCTION_NUMBER,
CASE_COL_NAME,
CASE_COL_PRE_CONDITION,
CASE_COL_STEPS,
CASE_COL_JSON,
CASE_COL_EXPECTED_RESULT,
CASE_COL_TEST_RESULT,
CASE_COL_TEST_FREQUENCY,
CASE_COL_LOGS_SCREENSHOTS,
CASE_COL_SCREENSHOT2,
CASE_COL_REMARKS,
# BUG列表相关配置
BUG_DATA_START_ROW,
BUG_COL_PROJECT_NAME,
BUG_COL_BUG_TYPE,
BUG_COL_BUG_LEVEL,
BUG_COL_BUG_STATUS,
BUG_COL_BUG_NAME,
BUG_COL_REPORTER,
BUG_COL_OWNER,
BUG_COL_DEADLINE,
BUG_COL_REPRODUCTION_STEPS,
# 枚举值
TEST_RESULT_VALUES,
BUG_STATUS_VALUES,
BUG_LEVEL_VALUES,
)
@dataclass
class TestCase:
"""
测试用例数据结构
Attributes:
serial_number: 序列号
module: 功能模块
category: 功能类别
case_number: 用例编号(如:XTY-001)
description: 功能描述
level: 用例等级(1-4级)
function_number: 功能编号
name: 用例名称
pre_condition: 预置条件
steps: 测试步骤
json_config: JSON配置
expected_result: 预期结果
test_result: 测试结果(通过/失败/未验证/未开发)
test_frequency: 测试频次
logs_screenshots: 日志/截图/照片
screenshot2: 截图二
remarks: 备注
"""
serial_number: int
module: str
category: str
case_number: str
description: str
level: int
function_number: str
name: str
pre_condition: str
steps: str
json_config: Optional[str] = None
expected_result: Optional[str] = None
test_result: Optional[str] = None
test_frequency: Optional[str] = None
logs_screenshots: Optional[str] = None
screenshot2: Optional[str] = None
remarks: Optional[str] = None
@dataclass
class Bug:
"""
BUG数据结构
Attributes:
project_name: 项目名称
bug_type: BUG类型
bug_level: BUG等级(1级/2级/3级/4级)
bug_status: BUG状态(激活/已解决/已关闭)
bug_name: BUG名称
reporter: 提单人员
owner: 主负责人员
deadline: 截止日期
reproduction_steps: 复现步骤
related_case_numbers: 关联的用例编号列表(从复现步骤中提取)
"""
project_name: str
bug_type: str
bug_level: str
bug_status: str
bug_name: str
reporter: str
owner: str
deadline: str
reproduction_steps: str
related_case_numbers: List[str] = field(default_factory=list)
def _extract_case_number_from_steps(steps: str) -> List[str]:
"""
从复现步骤中提取用例编号
Args:
steps: 复现步骤文本
Returns:
用例编号列表(如:['XTY-001', 'XTY-002'])
Examples:
>>> _extract_case_number_from_steps("用例编号:XTY-001")
['XTY-001']
>>> _extract_case_number_from_steps("测试XTY-001和XTY-002")
['XTY-001', 'XTY-002']
"""
if not steps:
return []
# 匹配用例编号模式:字母开头的编号(如:XTY-001, TC-001等)
pattern = r'[A-Z]+-\d{3,}'
matches = re.findall(pattern, steps)
return list(set(matches)) # 去重
def _get_cell_value(row, column_index: int, default: str = "") -> str:
"""
获取单元格值,处理None和空字符串
Args:
row: Excel行对象
column_index: 列索引(从1开始)
default: 默认值
Returns:
单元格值
"""
try:
value = row[column_index - 1].value # openpyxl列索引从0开始
if value is None:
return default
return str(value).strip()
except (IndexError, AttributeError):
return default
def _get_int_cell_value(row, column_index: int, default: int = 0) -> int:
"""
获取整数单元格值
Args:
row: Excel行对象
column_index: 列索引(从1开始)
default: 默认值
Returns:
整数值
"""
try:
value = row[column_index - 1].value
if value is None:
return default
return int(value)
except (ValueError, TypeError, IndexError):
return default
def read_test_cases(file_path: str) -> List[TestCase]:
"""
读取测试用例Excel文件
Args:
file_path: Excel文件路径
Returns:
测试用例列表
Raises:
FileNotFoundError: 文件不存在
Exception: 读取文件失败
"""
file_path_obj = Path(file_path)
if not file_path_obj.exists():
raise FileNotFoundError(f"测试用例文件不存在: {file_path}")
try:
# 打开Excel文件
workbook = openpyxl.load_workbook(file_path_obj, data_only=True)
sheet = workbook.active
test_cases = []
# 从指定行开始读取数据
for row in sheet.iter_rows(min_row=CASE_DATA_START_ROW):
# 检查是否为空行
serial_number = _get_int_cell_value(row, CASE_COL_SERIAL_NUMBER)
if serial_number == 0:
continue
# 创建测试用例对象
test_case = TestCase(
serial_number=serial_number,
module=_get_cell_value(row, CASE_COL_MODULE),
category=_get_cell_value(row, CASE_COL_CATEGORY),
case_number=_get_cell_value(row, CASE_COL_CASE_NUMBER),
description=_get_cell_value(row, CASE_COL_DESCRIPTION),
level=_get_int_cell_value(row, CASE_COL_LEVEL, 1),
function_number=_get_cell_value(row, CASE_COL_FUNCTION_NUMBER),
name=_get_cell_value(row, CASE_COL_NAME),
pre_condition=_get_cell_value(row, CASE_COL_PRE_CONDITION),
steps=_get_cell_value(row, CASE_COL_STEPS),
json_config=_get_cell_value(row, CASE_COL_JSON, None),
expected_result=_get_cell_value(row, CASE_COL_EXPECTED_RESULT, None),
test_result=_get_cell_value(row, CASE_COL_TEST_RESULT, None),
test_frequency=_get_cell_value(row, CASE_COL_TEST_FREQUENCY, None),
logs_screenshots=_get_cell_value(row, CASE_COL_LOGS_SCREENSHOTS, None),
screenshot2=_get_cell_value(row, CASE_COL_SCREENSHOT2, None),
remarks=_get_cell_value(row, CASE_COL_REMARKS, None),
)
# 验证数据有效性
if validate_test_case_data(test_case):
test_cases.append(test_case)
workbook.close()
return test_cases
except Exception as e:
raise Exception(f"读取测试用例文件失败: {str(e)}")
def read_bug_list(file_path: str) -> List[Bug]:
"""
读取BUG列表Excel文件
Args:
file_path: Excel文件路径
Returns:
BUG列表
Raises:
FileNotFoundError: 文件不存在
Exception: 读取文件失败
"""
file_path_obj = Path(file_path)
if not file_path_obj.exists():
raise FileNotFoundError(f"BUG列表文件不存在: {file_path}")
try:
# 打开Excel文件
workbook = openpyxl.load_workbook(file_path_obj, data_only=True)
sheet = workbook.active
bugs = []
# 从指定行开始读取数据
for row in sheet.iter_rows(min_row=BUG_DATA_START_ROW):
# 检查是否为空行
project_name = _get_cell_value(row, BUG_COL_PROJECT_NAME)
if not project_name:
continue
# 获取复现步骤
reproduction_steps = _get_cell_value(row, BUG_COL_REPRODUCTION_STEPS)
# 创建BUG对象
bug = Bug(
project_name=project_name,
bug_type=_get_cell_value(row, BUG_COL_BUG_TYPE),
bug_level=_get_cell_value(row, BUG_COL_BUG_LEVEL),
bug_status=_get_cell_value(row, BUG_COL_BUG_STATUS),
bug_name=_get_cell_value(row, BUG_COL_BUG_NAME),
reporter=_get_cell_value(row, BUG_COL_REPORTER),
owner=_get_cell_value(row, BUG_COL_OWNER),
deadline=_get_cell_value(row, BUG_COL_DEADLINE),
reproduction_steps=reproduction_steps,
related_case_numbers=_extract_case_number_from_steps(reproduction_steps),
)
# 验证数据有效性
if validate_bug_data(bug):
bugs.append(bug)
workbook.close()
return bugs
except Exception as e:
raise Exception(f"读取BUG列表文件失败: {str(e)}")
def validate_test_case_data(case: TestCase) -> bool:
"""
验证测试用例数据完整性
Args:
case: 测试用例对象
Returns:
是否有效
"""
# 必填字段检查
if not case.case_number:
return False
if not case.module:
return False
if not case.name:
return False
# 测试结果枚举值检查(如果有值的话)
if case.test_result and case.test_result not in TEST_RESULT_VALUES:
# 尝试标准化测试结果值
case.test_result = normalize_test_result(case.test_result)
return True
def validate_bug_data(bug: Bug) -> bool:
"""
验证BUG数据完整性
Args:
bug: BUG对象
Returns:
是否有效
"""
# 必填字段检查
if not bug.project_name:
return False
if not bug.bug_name:
return False
# BUG状态枚举值检查
if bug.bug_status not in BUG_STATUS_VALUES:
# 尝试标准化BUG状态值
bug.bug_status = normalize_bug_status(bug.bug_status)
# BUG等级枚举值检查
if bug.bug_level not in BUG_LEVEL_VALUES:
# 尝试标准化BUG等级值
bug.bug_level = normalize_bug_level(bug.bug_level)
return True
def normalize_test_result(value: str) -> Optional[str]:
"""
标准化测试结果值
Args:
value: 原始测试结果值
Returns:
标准化后的测试结果值,如果无法匹配则返回None
"""
if not value:
return None
value = value.strip()
# 通过的各种表达方式
if value in ["通过", "Pass", "PASS", "pass", "成功", "Success"]:
return "通过"
# 失败的各种表达方式
if value in ["失败", "Fail", "FAIL", "fail", "错误", "Error"]:
return "失败"
# 未验证的各种表达方式
if value in ["未验证", "Unverified", "未测试", "未执行", "Pending", "Skip"]:
return "未验证"
# 未开发的各种表达方式
if value in ["未开发", "Undeveloped", "N/A", "Blocked"]:
return "未开发"
return value # 返回原值
def normalize_bug_status(value: str) -> str:
"""
标准化BUG状态值
Args:
value: 原始BUG状态值
Returns:
标准化后的BUG状态值
"""
if not value:
return "激活"
value = value.strip()
# 激活的各种表达方式
if value in ["激活", "Active", "ACTIVE", "active", "新建", "New", "Open", "open"]:
return "激活"
# 已解决的各种表达方式
if value in ["已解决", "Resolved", "RESOLVED", "resolved", "Fixed", "fixed", "修复中"]:
return "已解决"
# 已关闭的各种表达方式
if value in ["已关闭", "Closed", "CLOSED", "closed", "完成"]:
return "已关闭"
return value # 返回原值
def normalize_bug_level(value: str) -> str:
"""
标准化BUG等级值
Args:
value: 原始BUG等级值
Returns:
标准化后的BUG等级值
"""
if not value:
return "3级"
value = value.strip()
# 1级的各种表达方式
if value in ["1级", "1", "一级", "Critical", "致命", "P0"]:
return "1级"
# 2级的各种表达方式
if value in ["2级", "2", "二级", "Major", "严重", "P1"]:
return "2级"
# 3级的各种表达方式
if value in ["3级", "3", "三级", "Normal", "一般", "P2"]:
return "3级"
# 4级的各种表达方式
if value in ["4级", "4", "四级", "Minor", "轻微", "P3", "Trivial"]:
return "4级"
return value # 返回原值
# -*- coding: utf-8 -*-
"""
功能测试报告自动生成工具 - GUI界面模块
本模块提供图形用户界面,支持文件选择和生成进度显示
"""
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
import threading
import sys
from pathlib import Path
from typing import Optional
from src.cli import generate_report_main
from src.config import TESTCASES_DIR, REPORTS_DIR
class ReportGeneratorGUI:
"""
报告生成器GUI类
Attributes:
root: Tk根窗口
testcase_path: 测试用例文件路径
buglist_path: BUG列表文件路径
output_path: 输出文件路径
project_name: 项目名称
"""
def __init__(self):
"""初始化GUI"""
self.root = tk.Tk()
self.root.title("功能测试报告自动生成工具")
self.root.geometry("800x600")
self.root.resizable(True, True)
# 文件路径变量
self.testcase_path = tk.StringVar()
self.buglist_path = tk.StringVar()
self.output_path = tk.StringVar()
self.project_name = tk.StringVar()
# 设置默认输出路径
self.output_path.set(str(REPORTS_DIR))
# 创建界面
self._create_widgets()
# 居中显示窗口
self._center_window()
def _center_window(self):
"""将窗口居中显示"""
self.root.update_idletasks()
width = self.root.winfo_width()
height = self.root.winfo_height()
x = (self.root.winfo_screenwidth() // 2) - (width // 2)
y = (self.root.winfo_screenheight() // 2) - (height // 2)
self.root.geometry(f'{width}x{height}+{x}+{y}')
def _create_widgets(self):
"""创建界面组件"""
# 标题
title_frame = tk.Frame(self.root, bg="#2c3e50", height=60)
title_frame.pack(fill=tk.X)
title_frame.pack_propagate(False)
title_label = tk.Label(
title_frame,
text="功能测试报告自动生成工具",
font=("微软雅黑", 16, "bold"),
bg="#2c3e50",
fg="white"
)
title_label.pack(expand=True)
# 主内容区域
main_frame = tk.Frame(self.root, padx=20, pady=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 文件选择区域
self._create_file_selection(main_frame)
# 执行日志区域
self._create_log_area(main_frame)
# 进度条
self.progress_var = tk.DoubleVar()
self.progress_bar = tk.Progressbar(
main_frame,
variable=self.progress_var,
maximum=100,
length=400,
mode='determinate',
height=20
)
self.progress_bar.pack(pady=10)
# 按钮区域
button_frame = tk.Frame(main_frame)
button_frame.pack(pady=10)
self.generate_button = tk.Button(
button_frame,
text="生成报告",
command=self._on_generate,
font=("微软雅黑", 12),
width=15,
height=2,
bg="#3498db",
fg="white",
activebackground="#2980b9",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2"
)
self.generate_button.pack(side=tk.LEFT, padx=5)
exit_button = tk.Button(
button_frame,
text="退出",
command=self._on_exit,
font=("微软雅黑", 12),
width=15,
height=2,
bg="#e74c3c",
fg="white",
activebackground="#c0392b",
activeforeground="white",
relief=tk.FLAT,
cursor="hand2"
)
exit_button.pack(side=tk.LEFT, padx=5)
def _create_file_selection(self, parent):
"""创建文件选择区域"""
selection_frame = tk.LabelFrame(
parent,
text="文件选择",
font=("微软雅黑", 12, "bold"),
padx=10,
pady=10
)
selection_frame.pack(fill=tk.X, pady=(0, 10))
# 测试用例文件
self._create_file_entry(
selection_frame,
"测试用例文件:",
self.testcase_path,
"选择测试用例Excel文件",
self._browse_testcase,
0
)
# BUG列表文件
self._create_file_entry(
selection_frame,
"BUG列表文件:",
self.buglist_path,
"选择BUG列表Excel文件",
self._browse_buglist,
1
)
# 输出目录
self._create_file_entry(
selection_frame,
"输出目录:",
self.output_path,
"选择报告输出目录",
self._browse_output,
2
)
# 项目名称
project_frame = tk.Frame(selection_frame)
project_frame.grid(row=3, column=0, columnspan=3, sticky="ew", pady=5)
project_label = tk.Label(
project_frame,
text="项目名称:",
font=("微软雅黑", 10),
width=15,
anchor="e"
)
project_label.pack(side=tk.LEFT)
project_entry = tk.Entry(
project_frame,
textvariable=self.project_name,
font=("微软雅黑", 10)
)
project_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
project_hint = tk.Label(
project_frame,
text="(可选,默认从BUG列表读取)",
font=("微软雅黑", 9),
fg="gray"
)
project_hint.pack(side=tk.LEFT)
def _create_file_entry(
self,
parent,
label_text,
text_var,
file_types,
browse_command,
row
):
"""创建文件选择输入框"""
# 标签
label = tk.Label(
parent,
text=label_text,
font=("微软雅黑", 10),
width=15,
anchor="e"
)
label.grid(row=row, column=0, sticky="e", padx=5, pady=5)
# 输入框
entry = tk.Entry(
parent,
textvariable=text_var,
font=("微软雅黑", 10)
)
entry.grid(row=row, column=1, sticky="ew", padx=5, pady=5)
# 浏览按钮
browse_button = tk.Button(
parent,
text="浏览...",
command=browse_command,
font=("微软雅黑", 9),
width=8
)
browse_button.grid(row=row, column=2, padx=5, pady=5)
# 配置列权重
parent.columnconfigure(1, weight=1)
def _create_log_area(self, parent):
"""创建执行日志区域"""
log_frame = tk.LabelFrame(
parent,
text="执行日志",
font=("微软雅黑", 12, "bold"),
padx=10,
pady=10
)
log_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
# 日志文本框
self.log_text = scrolledtext.ScrolledText(
log_frame,
font=("Consolas", 9),
wrap=tk.WORD,
height=15
)
self.log_text.pack(fill=tk.BOTH, expand=True)
# 配置日志颜色标签
self.log_text.tag_configure("info", foreground="black")
self.log_text.tag_configure("success", foreground="green")
self.log_text.tag_configure("error", foreground="red")
self.log_text.tag_configure("warning", foreground="orange")
def _browse_testcase(self):
"""浏览测试用例文件"""
file_path = filedialog.askopenfilename(
title="选择测试用例Excel文件",
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")],
initialdir=str(TESTCASES_DIR)
)
if file_path:
self.testcase_path.set(file_path)
self._log(f"已选择测试用例文件: {Path(file_path).name}", "info")
def _browse_buglist(self):
"""浏览BUG列表文件"""
file_path = filedialog.askopenfilename(
title="选择BUG列表Excel文件",
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")],
initialdir=str(TESTCASES_DIR)
)
if file_path:
self.buglist_path.set(file_path)
self._log(f"已选择BUG列表文件: {Path(file_path).name}", "info")
def _browse_output(self):
"""浏览输出目录"""
dir_path = filedialog.askdirectory(
title="选择报告输出目录",
initialdir=str(REPORTS_DIR)
)
if dir_path:
self.output_path.set(dir_path)
self._log(f"已选择输出目录: {dir_path}", "info")
def _log(self, message: str, tag: str = "info"):
"""
记录日志
Args:
message: 日志消息
tag: 日志标签(info/success/error/warning)
"""
self.log_text.insert(tk.END, message + "\n", tag)
self.log_text.see(tk.END)
self.log_text.update()
def _update_progress(self, value: float):
"""
更新进度条
Args:
value: 进度值(0-100)
"""
self.progress_var.set(value)
self.root.update()
def _on_generate(self):
"""生成报告按钮点击事件"""
# 验证输入
testcase_path = self.testcase_path.get()
buglist_path = self.buglist_path.get()
if not testcase_path:
messagebox.showwarning("警告", "请选择测试用例文件")
return
if not buglist_path:
messagebox.showwarning("警告", "请选择BUG列表文件")
return
if not Path(testcase_path).exists():
messagebox.showerror("错误", f"测试用例文件不存在: {testcase_path}")
return
if not Path(buglist_path).exists():
messagebox.showerror("错误", f"BUG列表文件不存在: {buglist_path}")
return
# 禁用生成按钮
self.generate_button.config(state=tk.DISABLED, text="生成中...")
# 清空日志
self.log_text.delete(1.0, tk.END)
# 在新线程中执行生成任务
thread = threading.Thread(
target=self._generate_report_thread,
args=(testcase_path, buglist_path),
daemon=True
)
thread.start()
def _generate_report_thread(self, testcase_path: str, buglist_path: str):
"""
在后台线程中生成报告
Args:
testcase_path: 测试用例文件路径
buglist_path: BUG列表文件路径
"""
try:
self._log("=== 开始生成功能测试报告 ===\n", "info")
# 获取其他参数
output_path = self.output_path.get()
project_name = self.project_name.get() or None
# 重定向日志输出到GUI
import io
import sys
class TextRedirector:
def __init__(self, widget, tag="info"):
self.widget = widget
self.tag = tag
def write(self, text):
if text.strip():
self.widget.insert(tk.END, text, self.tag)
self.widget.see(tk.END)
self.widget.update()
def flush(self):
pass
# 保存原始stdout
old_stdout = sys.stdout
# 重定向到GUI
sys.stdout = TextRedirector(self.log_text, "info")
# 调用生成函数
result_path = generate_report_main(
testcase_path=testcase_path,
buglist_path=buglist_path,
output_path=output_path if output_path else None,
project_name=project_name
)
# 恢复原始stdout
sys.stdout = old_stdout
# 显示成功消息
self._log(f"\n✓ 报告生成成功: {result_path}", "success")
# 在主线程中显示消息框
self.root.after(0, lambda: messagebox.showinfo(
"成功",
f"报告生成成功!\n\n保存位置:\n{result_path}"
))
except Exception as e:
self._log(f"\n✗ 生成报告失败: {str(e)}", "error")
# 在主线程中显示错误消息框
self.root.after(0, lambda: messagebox.showerror(
"错误",
f"生成报告失败:\n{str(e)}"
))
finally:
# 恢复生成按钮
self.root.after(0, self._enable_generate_button)
def _enable_generate_button(self):
"""启用生成按钮"""
self.generate_button.config(state=tk.NORMAL, text="生成报告")
def _on_exit(self):
"""退出按钮点击事件"""
if messagebox.askyesno("确认", "确定要退出吗?"):
self.root.quit()
self.root.destroy()
def run(self):
"""运行GUI主循环"""
self.root.mainloop()
def main_gui() -> None:
"""
GUI主函数
"""
app = ReportGeneratorGUI()
app.run()
if __name__ == "__main__":
main_gui()
# -*- coding: utf-8 -*-
"""
功能测试报告自动生成工具 - 主入口模块
本模块是程序的主入口,根据参数选择CLI或GUI模式,
协调各模块调用完成报告生成
"""
import sys
import os
# 添加项目根目录到Python路径
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT_DIR)
from src.cli import parse_arguments, main_cli, list_available_files
from src.gui import main_gui
from src.config import __version__, __description__
def print_banner():
"""打印程序欢迎信息"""
banner = f"""
╔═══════════════════════════════════════════════════════════╗
║ ║
║ 功能测试报告自动生成工具 v{__version__} ║
║ ║
║ {__description__:<55} ║
║ ║
╚═══════════════════════════════════════════════════════════╝
"""
print(banner)
def print_usage():
"""打印使用说明"""
print("""
使用方法:
python main.py [选项]
选项:
--testcase FILE 测试用例Excel文件路径
--buglist FILE BUG列表Excel文件路径
--output PATH 输出报告文件路径(可选)
--project NAME 项目名称(可选)
--gui 使用GUI界面
--list 列出testcases目录下的可用文件
-h, --help 显示帮助信息
示例:
# 交互式模式
python main.py
# 命令行模式
python main.py --testcase testcases/测试用例.xlsx --buglist testcases/BUG列表.xlsx
# GUI模式
python main.py --gui
# 列出可用文件
python main.py --list
""")
def main():
"""
主函数
程序入口,根据参数决定运行模式
"""
# 打印欢迎信息
print_banner()
# 解析命令行参数
args = parse_arguments()
# 处理--list参数
if args.list:
list_available_files()
return
# 处理--gui参数
if args.gui:
try:
main_gui()
except ImportError as e:
print(f"错误: 无法启动GUI界面 - {str(e)}")
print("请确保已安装tkinter库")
sys.exit(1)
except Exception as e:
print(f"错误: {str(e)}")
sys.exit(1)
return
# CLI模式
try:
main_cli()
except KeyboardInterrupt:
print("\n\n操作已取消")
sys.exit(0)
except Exception as e:
print(f"\n错误: {str(e)}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()
# -*- coding: utf-8 -*-
"""
功能测试报告自动生成工具 - 报告生成模块
本模块负责读取功能测试报告模板、填充报告数据、插入图表,
并生成Word文档
"""
from pathlib import Path
from typing import Dict, List, Any, Optional
from datetime import datetime
import re
try:
from docx import Document
from docx.shared import Inches, Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
except ImportError:
print("警告: 未安装python-docx库,请运行: pip install python-docx")
raise
from src.data_analyzer import TestCaseAnalysis, BugAnalysis, ModuleStat
from src.excel_reader import TestCase, Bug
from src.config import (
TEMPLATE_FILE,
WORD_FONT_NAME,
WORD_FONT_SIZE,
WORD_TITLE_FONT_SIZE,
REPORTS_DIR,
DATE_FORMAT,
FILENAME_DATE_FORMAT,
get_report_filename,
get_report_number,
# 测试结果枚举
TEST_RESULT_PASS,
TEST_RESULT_FAIL,
TEST_RESULT_UNVERIFIED,
TEST_RESULT_UNDEVELOPED,
# BUG等级枚举
BUG_LEVEL_1,
BUG_LEVEL_2,
BUG_LEVEL_3,
BUG_LEVEL_4,
# BUG状态枚举
BUG_STATUS_ACTIVE,
BUG_STATUS_RESOLVED,
BUG_STATUS_CLOSED,
REMAINING_BUG_STATUS,
# BUG等级描述
BUG_LEVEL_DESCRIPTION,
)
def _set_cell_border(cell):
"""
设置单元格边框
Args:
cell: 单元格对象
"""
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
tcPr = cell._element.get_or_add_tcPr()
tcBorders = OxmlElement('w:tcBorders')
for border_name in ['top', 'left', 'bottom', 'right']:
border = OxmlElement(f'w:{border_name}')
border.set(qn('w:val'), 'single')
border.set(qn('w:sz'), '4')
border.set(qn('w:color'), '000000')
tcBorders.append(border)
tcPr.append(tcBorders)
def _create_table_with_data(doc, headers: List[str], rows: List[List[str]]) -> None:
"""
创建表格并填充数据
Args:
doc: Word文档对象
headers: 表头列表
rows: 数据行列表
"""
if not rows:
return
# 创建表格
table = doc.add_table(rows=1 + len(rows), cols=len(headers))
table.style = 'Light Grid Accent 1'
# 设置表头
header_cells = table.rows[0].cells
for i, header in enumerate(headers):
header_cells[i].text = header
# 设置表头样式
for paragraph in header_cells[i].paragraphs:
for run in paragraph.runs:
run.font.bold = True
run.font.size = Pt(WORD_FONT_SIZE)
run.font.name = WORD_FONT_NAME
run._element.rPr.rFonts.set(qn('w:eastAsia'), WORD_FONT_NAME)
# 填充数据行
for i, row_data in enumerate(rows):
row_cells = table.rows[i + 1].cells
for j, cell_data in enumerate(row_data):
cell_text = str(cell_data) if cell_data is not None else ""
row_cells[j].text = cell_text
# 设置单元格样式
for paragraph in row_cells[j].paragraphs:
for run in paragraph.runs:
run.font.size = Pt(WORD_FONT_SIZE)
run.font.name = WORD_FONT_NAME
run._element.rPr.rFonts.set(qn('w:eastAsia'), WORD_FONT_NAME)
def _add_title(doc, title: str, level: int = 1) -> None:
"""
添加标题
Args:
doc: Word文档对象
title: 标题文本
level: 标题级别(1-3)
"""
heading = doc.add_heading(title, level=level)
for run in heading.runs:
run.font.size = Pt(WORD_TITLE_FONT_SIZE - level * 2)
run.font.name = WORD_FONT_NAME
run._element.rPr.rFonts.set(qn('w:eastAsia'), WORD_FONT_NAME)
def _add_paragraph(doc, text: str, bold: bool = False) -> None:
"""
添加段落
Args:
doc: Word文档对象
text: 段落文本
bold: 是否加粗
"""
paragraph = doc.add_paragraph(text)
for run in paragraph.runs:
run.font.size = Pt(WORD_FONT_SIZE)
run.font.name = WORD_FONT_NAME
run._element.rPr.rFonts.set(qn('w:eastAsia'), WORD_FONT_NAME)
run.font.bold = bold
def _add_image(doc, image_path: str, width: float = 6.0) -> None:
"""
添加图片
Args:
doc: Word文档对象
image_path: 图片文件路径
width: 图片宽度(英寸)
"""
if Path(image_path).exists():
doc.add_picture(image_path, width=Inches(width))
# 设置图片居中
last_paragraph = doc.paragraphs[-1]
last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
else:
doc.add_paragraph(f"[图片文件不存在: {image_path}]")
def generate_report(
template_path: str,
case_analysis: TestCaseAnalysis,
bug_analysis: BugAnalysis,
case_bug_link: Dict[str, List[Bug]],
chart_paths: Dict[str, str],
output_path: str,
project_name: str,
cases: List[TestCase],
bugs: List[Bug]
) -> str:
"""
生成功能测试报告
Args:
template_path: 模板文件路径(暂未使用,使用固定格式生成)
case_analysis: 用例分析结果
bug_analysis: BUG分析结果
case_bug_link: 用例-BUG关联
chart_paths: 图表文件路径字典
output_path: 输出文件路径
project_name: 项目名称
cases: 测试用例列表
bugs: BUG列表
Returns:
生成的报告文件路径
"""
# 创建Word文档
doc = Document()
# 设置文档默认字体
doc.styles['Normal'].font.name = WORD_FONT_NAME
doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), WORD_FONT_NAME)
doc.styles['Normal'].font.size = Pt(WORD_FONT_SIZE)
# 获取当前日期
current_date = datetime.now().strftime(DATE_FORMAT)
report_number = get_report_number(project_name, current_date)
# ==================== 报告标题 ====================
_add_title(doc, f"{project_name}功能测试报告", level=1)
# ==================== 一、报告基本信息 ====================
_add_title(doc, "一、报告基本信息", level=2)
_add_title(doc, "1.1 报告标识", level=3)
headers = ["项目", "内容"]
rows = [
["报告编号", report_number],
["报告标题", f"{project_name}功能测试报告"],
["报告版本", "v1.0"],
["生成日期", current_date],
["报告状态", "已生成"],
]
_create_table_with_data(doc, headers, rows)
_add_title(doc, "1.2 测试概览", level=3)
headers = ["项目", "内容"]
rows = [
["项目名称", project_name],
["测试负责人", "未填写"],
["测试人员", "未填写"],
["测试环境", "未填写"],
["测试类型", "功能测试"],
]
_create_table_with_data(doc, headers, rows)
_add_title(doc, "1.3 被测系统信息", level=3)
headers = ["项目", "内容"]
rows = [
["系统名称", project_name],
["系统版本", "未填写"],
["服务器地址", "未填写"],
["部署环境", "未填写"],
]
_create_table_with_data(doc, headers, rows)
# ==================== 二、测试执行摘要 ====================
_add_title(doc, "二、测试执行摘要", level=2)
_add_title(doc, "2.1 测试结果统计", level=3)
total = case_analysis.total_cases
headers = ["统计项", "数量", "占比"]
rows = [
["测试用例总数", str(total), "100%"],
["执行用例数量", str(case_analysis.executed_cases), f"{case_analysis.execution_rate}%"],
["通过用例数量", str(case_analysis.passed_cases), f"{case_analysis.pass_rate}%"],
["失败用例数量", str(case_analysis.failed_cases), f"{round(case_analysis.failed_cases / total * 100, 2)}%" if total > 0 else "0%"],
["未执行用例数量", str(case_analysis.unverified_cases + case_analysis.unverified_cases),
f"{round((case_analysis.unverified_cases + case_analysis.unverified_cases) / total * 100, 2)}%" if total > 0 else "0%"],
]
_create_table_with_data(doc, headers, rows)
_add_title(doc, "2.2 BUG统计汇总", level=3)
total_bugs = bug_analysis.total_bugs
headers = ["BUG等级", "数量", "占比", "已关闭", "未关闭"]
for level in [BUG_LEVEL_1, BUG_LEVEL_2, BUG_LEVEL_3, BUG_LEVEL_4]:
count = bug_analysis.level_distribution.get(level, 0)
percentage = round(count / total_bugs * 100, 2) if total_bugs > 0 else 0
# 计算该等级的未关闭数量
remaining = sum(
1 for bug in bugs
if bug.bug_level == level and bug.bug_status in REMAINING_BUG_STATUS
)
closed = count - remaining
rows.append([
f"{BUG_LEVEL_DESCRIPTION[level]}({level})",
str(count),
f"{percentage}%",
str(closed),
str(remaining)
])
_create_table_with_data(doc, headers, rows)
_add_title(doc, "2.3 测试结论", level=3)
if bug_analysis.remaining_bugs == 0 and case_analysis.failed_cases == 0:
conclusion = "✓ 测试通过 - 系统功能正常,无致命/严重缺陷,可以发布"
elif case_analysis.failed_cases > 0:
conclusion = "✗ 测试不通过 - 存在失败的测试用例,不建议发布"
elif bug_analysis.remaining_bugs > 0:
critical = bug_analysis.remaining_by_level.get(BUG_LEVEL_1, 0) + \
bug_analysis.remaining_by_level.get(BUG_LEVEL_2, 0)
if critical > 0:
conclusion = "✗ 测试不通过 - 存在阻塞性缺陷,不建议发布"
else:
conclusion = "⚠ 有条件通过 - 存在非阻塞性缺陷,经评估可以发布"
else:
conclusion = "待评估"
_add_paragraph(doc, conclusion)
# ==================== 三、测试范围 ====================
_add_title(doc, "三、测试范围", level=2)
_add_title(doc, "3.1 测试模块覆盖", level=3)
headers = ["序号", "功能模块", "用例数量", "通过", "失败", "未执行", "覆盖率"]
module_rows = []
for i, (module_name, stat) in enumerate(case_analysis.module_stats.items(), 1):
module_rows.append([
str(i),
module_name,
str(stat.total_cases),
str(stat.passed_cases),
str(stat.failed_cases),
str(stat.unverified_cases + stat.undeveloped_cases),
f"{stat.pass_rate}%"
])
_create_table_with_data(doc, headers, module_rows)
# ==================== 四、测试用例执行详情 ====================
_add_title(doc, "四、测试用例执行详情", level=2)
# 通过用例列表
_add_title(doc, "4.1 通过用例列表", level=3)
passed_cases = [c for c in cases if c.test_result == TEST_RESULT_PASS]
if passed_cases:
headers = ["序号", "用例编号", "功能模块", "用例名称", "用例等级", "测试结果"]
rows = []
for i, case in enumerate(passed_cases[:20], 1): # 限制显示20条
rows.append([
str(i),
case.case_number,
case.module,
case.name[:50], # 限制长度
str(case.level),
case.test_result
])
if len(passed_cases) > 20:
rows.append(["...", f"等{len(passed_cases)}条通过用例", "", "", "", ""])
_create_table_with_data(doc, headers, rows)
else:
_add_paragraph(doc, "无通过用例")
# 失败用例列表
_add_title(doc, "4.2 失败用例列表", level=3)
failed_cases = [c for c in cases if c.test_result == TEST_RESULT_FAIL]
if failed_cases:
headers = ["序号", "用例编号", "功能模块", "用例名称", "用例等级", "关联BUG"]
rows = []
for i, case in enumerate(failed_cases, 1):
related_bugs = case_bug_link.get(case.case_number, [])
bug_str = "; ".join([b.bug_name[:30] for b in related_bugs[:3]]) # 限制显示
if len(related_bugs) > 3:
bug_str += f" 等{len(related_bugs)}个"
rows.append([
str(i),
case.case_number,
case.module,
case.name[:50],
str(case.level),
bug_str if bug_str else "无"
])
_create_table_with_data(doc, headers, rows)
else:
_add_paragraph(doc, "无失败用例")
# 未执行用例列表
_add_title(doc, "4.3 未执行用例列表", level=3)
unexecuted_cases = [c for c in cases if c.test_result in [TEST_RESULT_UNVERIFIED, TEST_RESULT_UNDEVELOPED]]
if unexecuted_cases:
headers = ["序号", "用例编号", "功能模块", "用例名称", "用例等级", "未执行原因"]
rows = []
for i, case in enumerate(unexecuted_cases[:10], 1): # 限制显示10条
reason = "未验证" if case.test_result == TEST_RESULT_UNVERIFIED else "未开发"
rows.append([
str(i),
case.case_number,
case.module,
case.name[:50],
str(case.level),
reason
])
if len(unexecuted_cases) > 10:
rows.append(["...", f"等{len(unexecuted_cases)}条未执行用例", "", "", "", ""])
_create_table_with_data(doc, headers, rows)
else:
_add_paragraph(doc, "无未执行用例")
# ==================== 五、BUG详细列表 ====================
_add_title(doc, "五、BUG详细列表", level=2)
_add_title(doc, "5.1 BUG详细记录", level=3)
if bugs:
headers = ["序号", "BUG等级", "BUG状态", "BUG名称", "BUG类型", "提单人员", "负责人员"]
rows = []
for i, bug in enumerate(bugs, 1):
rows.append([
str(i),
bug.bug_level,
bug.bug_status,
bug.bug_name[:50],
bug.bug_type,
bug.reporter,
bug.owner
])
_create_table_with_data(doc, headers, rows)
else:
_add_paragraph(doc, "无BUG记录")
_add_title(doc, "5.2 遗留BUG说明", level=3)
remaining_bugs = [b for b in bugs if b.bug_status in REMAINING_BUG_STATUS]
if remaining_bugs:
headers = ["BUG编号", "BUG等级", "问题描述", "遗留原因", "后续计划"]
rows = []
for bug in remaining_bugs:
rows.append([
bug.bug_name[:30],
bug.bug_level,
bug.bug_name[:60],
"待修复",
"后续版本修复"
])
_create_table_with_data(doc, headers, rows)
else:
_add_paragraph(doc, "无遗留BUG")
# ==================== 六、图表分析 ====================
_add_title(doc, "六、图表分析", level=2)
# 添加图表
if "bug_level" in chart_paths:
_add_title(doc, "6.1 BUG等级分布", level=3)
_add_image(doc, chart_paths["bug_level"])
if "test_result" in chart_paths:
_add_title(doc, "6.2 用例执行结果分布", level=3)
_add_image(doc, chart_paths["test_result"])
if "pass_rate" in chart_paths:
_add_title(doc, "6.3 用例执行情况统计", level=3)
_add_image(doc, chart_paths["pass_rate"])
if "bug_by_module" in chart_paths:
_add_title(doc, "6.4 各模块BUG分布", level=3)
_add_image(doc, chart_paths["bug_by_module"])
# ==================== 保存文档 ====================
# 确保输出目录存在
output_dir = Path(output_path).parent
output_dir.mkdir(parents=True, exist_ok=True)
# 保存文档
doc.save(output_path)
return output_path
def fill_template(template_path: str, data: Dict[str, Any]) -> str:
"""
填充模板内容
Args:
template_path: 模板文件路径
data: 填充数据
Returns:
填充后的内容
"""
# 读取模板文件
template_file = Path(template_path)
if not template_file.exists():
raise FileNotFoundError(f"模板文件不存在: {template_path}")
with open(template_file, 'r', encoding='utf-8') as f:
content = f.read()
# 替换占位符
for key, value in data.items():
placeholder = f"{{{key}}}"
content = content.replace(placeholder, str(value))
return content
## 自动化生成测试报告
# 自动化生成测试报告
## 代码路径
- 代码路径:[AuxiliaryTool/FunctionalTestReportGeneration]
## 功能需求
### 功能目标
**目标:** 通过测试用例+BUG列表+功能测试报告模板生成项目功能测试报告。
### 需求描述
#### 模板文件获取
- 功能测试报告模板:[AuxiliaryTool/FunctionalTestReportGeneration/config/功能测试报告模板.md]
- BUG列表数据模板:[AuxiliaryTool/FunctionalTestReportGeneration/config/BUG列表模板数据.xlsx]
- 功能测试用例模板: [AuxiliaryTool/FunctionalTestReportGeneration/config/功能测试用例模板.xlsx]
#### 测试数据获取
-[AuxiliaryTool/FunctionalTestReportGeneration/testcases]路径下获取测试用例与BUG列表数据,可通过用户输入指定。
#### 测试报告生成
- 根据测试执行结果,自动填充功能测试报告模板,生成完整的测试报告。
- 根据BUG列表数据,自动统计BUG数量和等级分布,并填充到测试报告中。
- 根据功能测试用例,自动统计用例执行情况,并填充到测试报告中。
### 测试报告生成逻辑
#### 交互方式
- 程序交互方式:通过命令行窗口、命令行参数进行交互输入执行。
### 规范文档
#### 数据格式获取
- 数据格式确认:通过读取BUG列表模板文件与功能测试用例模板文件,确认数据格式。
- 数据获取:通过用户输入指定测试数据路径,获取测试数据,测试用例文件与BUG列表文件。测试用例文件数据从第4行开始读取,BUG列表数据从第2行开始读取。
- 批量测试执行(暂不实现):通过批量执行功能测试用例,批量生成功能测试报告。
- 项目名称获取:通过读取BUG列表中'项目名称'字段,获取项目名称。
#### 数据定义
- 测试用例文件中定义用例执行结果状态分为:通过、失败、未验证与未开发。
- BUG列表文件中定义BUG状态分为:激活、已解决与已关闭。
- 日期格式定义:日期格式统一为YYYY年MM月DD日。
#### 统计与关联规则
- 用例-BUG关联规则:通过获取BUG列表复现步骤中的用例编号,关联BUG列表数据,获取关联的BUG信息。
- 遗留BUG定义:BUG列表文件中,状态为激活与已解决的BUG定义为遗留BUG。
- 通过率计算规则:通过用例数量 / 总用例数量 * 100%,保留两位小数。
#### 报告内容
- 自动填充规则:测试概览、被测系统信息、测试结论、测试建议、测试环境信息为空,在报告中显示“未填写”,其余为必填字段。
- 图标绘制规则:绘制BUG等级分布、用例执行结果分布、用例通过率图表,使用matplotlib库进行图表绘制。
#### 文档输出规则
- 报告输出格式:输出Word文档格式。
- 报告输出位置:输出到[AuxiliaryTool/FunctionalTestReportGeneration/reports]
- 报告命名规则:{项目名称}_功能测试报告_{日期}.docx
- 报告编号规则:{项目名称}-{日期}
## 规范文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
- 问题总结: `Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
- 方法总结: `Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md`
- 文档规范: `Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md`
- 测试规范: `Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md`
---
\ No newline at end of file
# _PRD_自动化生成功能测试报告_计划执行
> 版本:V1.0
> 创建日期:2026-03-09
> 更新日期:2026-03-09
> 适用范围:功能测试报告自动化生成工具
> 来源:基于《_PRD_自动化生成功能测试报告.md》
> 状态:执行中
---
## 1. 项目概述
### 1.1 背景
当前功能测试报告由测试人员手动整理测试用例和BUG列表后编写,工作量大且容易出错。需要开发自动化工具,通过读取测试用例Excel文件和BUG列表Excel文件,自动填充功能测试报告模板,生成Word格式的测试报告。
### 1.2 目标
- 开发Python工具,自动读取测试用例和BUG列表数据
- 自动统计用例执行情况、BUG数量和等级分布
- 自动填充功能测试报告模板
- 生成Word格式的功能测试报告
- 支持命令行和GUI两种交互方式
### 1.3 涉及文件
**模板文件:**
- `AuxiliaryTool/FunctionalTestReportGeneration/config/功能测试报告模板.md`
- `AuxiliaryTool/FunctionalTestReportGeneration/config/BUG列表模板数据.xlsx`
- `AuxiliaryTool/FunctionalTestReportGeneration/config/功能测试用例模板.xlsx`
**测试数据文件:**
- `AuxiliaryTool/FunctionalTestReportGeneration/testcases/*.xlsx`
**输出文件:**
- `AuxiliaryTool/FunctionalTestReportGeneration/reports/{项目名称}_功能测试报告_{日期}.docx`
**需要创建的代码文件:**
| 序号 | 文件名 | 功能描述 | 状态 |
|-----|--------|----------|------|
| 1 | `config.py` | 配置文件,定义常量和路径 | ⏳ 待创建 |
| 2 | `excel_reader.py` | Excel文件读取模块 | ⏳ 待创建 |
| 3 | `data_analyzer.py` | 数据分析和统计模块 | ⏳ 待创建 |
| 4 | `chart_generator.py` | 图表生成模块(matplotlib) | ⏳ 待创建 |
| 5 | `report_generator.py` | Word报告生成模块 | ⏳ 待创建 |
| 6 | `cli.py` | 命令行交互模块 | ⏳ 待创建 |
| 7 | `gui.py` | GUI界面模块 | ⏳ 待创建 |
| 8 | `main.py` | 主入口文件 | ⏳ 待创建 |
---
## 2. 数据结构分析
### 2.1 测试用例Excel文件结构
**文件位置:** `testcases/功能测试用例模板.xlsx`
**数据结构:**
| 列号 | 列名 | 说明 | 必填 |
|------|------|------|------|
| 1 | 序列号 | 用例序号 | ✅ |
| 2 | 功能模块 | 功能模块名称 | ✅ |
| 3 | 功能类别 | 功能类别(如:标准版) | ✅ |
| 4 | 用例编号 | 用例编号(如:XTY-001) | ✅ |
| 5 | 功能描述 | 功能描述 | ✅ |
| 6 | 用例等级 | 用例等级(1-4级) | ✅ |
| 7 | 功能编号 | 功能编号 | ✅ |
| 8 | 用例名称 | 用例名称 | ✅ |
| 9 | 预置条件 | 预置条件 | ✅ |
| 10 | STEP | 测试步骤 | ✅ |
| 11 | JSON | 自动化配置JSON | ⚪ |
| 12 | 预期结果 | 预期结果 | ✅ |
| 13 | 测试结果 | 测试结果(通过/失败/未验证/未开发) | ✅ |
| 14 | 测试频次 | 测试频次 | ⚪ |
| 15 | 日志/截图/照片 | 附件路径 | ⚪ |
| 16 | 截图二 | 截图路径 | ⚪ |
| 17 | 备注 | 备注 | ⚪ |
**数据起始行:** 表头在第3行,数据从第4行开始
**测试结果枚举值:**
- 通过
- 失败
- 未验证
- 未开发
### 2.2 BUG列表Excel文件结构
**文件位置:** `testcases/BUG列表模板数据.xlsx`
**数据结构:**
| 列号 | 列名 | 说明 | 必填 |
|------|------|------|------|
| 1 | 项目名称 | 项目名称 | ✅ |
| 2 | BUG类型 | BUG类型(如:代码错误) | ✅ |
| 3 | BUG等级 | BUG等级(1级/2级/3级/4级) | ✅ |
| 4 | BUG状态 | BUG状态(激活/已解决/已关闭) | ✅ |
| 5 | BUG名称 | BUG名称 | ✅ |
| 6 | 提单人员 | 提单人员 | ✅ |
| 7 | 主负责人员 | 主负责人员 | ✅ |
| 8 | 截止日期 | 截止日期 | ✅ |
| 9 | 复现步骤 | 复现步骤(包含用例编号) | ✅ |
**数据起始行:** 表头在第1行,数据从第2行开始
**BUG状态枚举值:**
- 激活(遗留BUG)
- 已解决(遗留BUG)
- 已关闭(非遗留BUG)
**BUG等级枚举值:**
- 1级(致命)
- 2级(严重)
- 3级(一般)
- 4级(轻微)
### 2.3 数据关联规则
**用例-BUG关联方式:**
通过BUG列表的"复现步骤"字段中的用例编号进行关联。
**关联逻辑:**
```
1. 从BUG的"复现步骤"中提取用例编号(如:XTY-001)
2. 根据用例编号在测试用例中查找对应的用例
3. 建立用例与BUG的关联关系
```
---
## 3. 程序架构设计
### 3.1 整体架构
```
main.py (主入口)
├── cli.py / gui.py (交互层)
│ ├── 命令行参数解析
│ ├── 用户输入获取
│ └── 进度显示
├── excel_reader.py (数据读取层)
│ ├── 读取测试用例Excel
│ ├── 读取BUG列表Excel
│ └── 数据验证
├── data_analyzer.py (数据分析层)
│ ├── 用例执行情况统计
│ ├── BUG数量和等级统计
│ ├── 按模块分组统计
│ └── 用例-BUG关联分析
├── chart_generator.py (图表生成层)
│ ├── BUG等级分布图
│ ├── 用例执行结果分布图
│ ├── 用例通过率图
│ └── 图表导出为图片
├── report_generator.py (报告生成层)
│ ├── 读取报告模板
│ ├── 填充报告数据
│ ├── 插入图表
│ └── 生成Word文档
└── config.py (配置层)
├── 常量定义
├── 路径配置
└── 模板配置
```
### 3.2 数据流向
```
用户输入(数据文件路径)
excel_reader.py (读取Excel数据)
data_analyzer.py (统计分析)
├──→ chart_generator.py (生成图表)
│ ↓
│ 图表图片文件
│ ↓
└─────────────┘
report_generator.py (填充模板、生成报告)
Word文档报告 (.docx)
```
---
## 4. 各模块详细设计
### 4.1 config.py - 配置模块
**职责:**
- 定义程序常量
- 配置文件路径
- 定义数据枚举值
**导出内容:**
```python
# 路径配置
BASE_DIR = Path(__file__).parent
CONFIG_DIR = BASE_DIR / "config"
TESTCASES_DIR = BASE_DIR / "testcases"
REPORTS_DIR = BASE_DIR / "reports"
TEMPLATE_FILE = CONFIG_DIR / "功能测试报告模板.md"
BUG_TEMPLATE_FILE = CONFIG_DIR / "BUG列表模板数据.xlsx"
CASE_TEMPLATE_FILE = CONFIG_DIR / "功能测试用例模板.xlsx"
# 测试结果枚举
TEST_RESULT_PASS = "通过"
TEST_RESULT_FAIL = "失败"
TEST_RESULT_UNVERIFIED = "未验证"
TEST_RESULT_UNDEVELOPED = "未开发"
# BUG状态枚举
BUG_STATUS_ACTIVE = "激活"
BUG_STATUS_RESOLVED = "已解决"
BUG_STATUS_CLOSED = "已关闭"
# BUG等级枚举
BUG_LEVEL_1 = "1级"
BUG_LEVEL_2 = "2级"
BUG_LEVEL_3 = "3级"
BUG_LEVEL_4 = "4级"
# 遗留BUG状态
REMAINING_BUG_STATUS = [BUG_STATUS_ACTIVE, BUG_STATUS_RESOLVED]
# 日期格式
DATE_FORMAT = "YYYY年MM月DD日"
# Excel数据起始行
CASE_DATA_START_ROW = 4 # 测试用例从第4行开始
BUG_DATA_START_ROW = 2 # BUG列表从第2行开始
```
---
### 4.2 excel_reader.py - Excel读取模块
**职责:**
- 读取测试用例Excel文件
- 读取BUG列表Excel文件
- 数据格式验证
- 数据结构转换
**导出函数:**
```python
def read_test_cases(file_path: str) -> List[TestCase]
"""
读取测试用例Excel文件
:param file_path: Excel文件路径
:return: 测试用例列表
"""
def read_bug_list(file_path: str) -> List[Bug]
"""
读取BUG列表Excel文件
:param file_path: Excel文件路径
:return: BUG列表
"""
def validate_test_case_data(case: TestCase) -> bool
"""
验证测试用例数据完整性
:param case: 测试用例对象
:return: 是否有效
"""
def validate_bug_data(bug: Bug) -> bool
"""
验证BUG数据完整性
:param bug: BUG对象
:return: 是否有效
"""
```
**数据结构:**
```python
@dataclass
class TestCase:
"""测试用例数据结构"""
serial_number: int # 序列号
module: str # 功能模块
category: str # 功能类别
case_number: str # 用例编号
description: str # 功能描述
level: int # 用例等级
function_number: str # 功能编号
name: str # 用例名称
pre_condition: str # 预置条件
steps: str # 测试步骤
json_config: str # JSON配置
expected_result: str # 预期结果
test_result: str # 测试结果
test_frequency: str # 测试频次
logs_screenshots: str # 日志/截图
screenshot2: str # 截图二
remarks: str # 备注
@dataclass
class Bug:
"""BUG数据结构"""
project_name: str # 项目名称
bug_type: str # BUG类型
bug_level: str # BUG等级
bug_status: str # BUG状态
bug_name: str # BUG名称
reporter: str # 提单人员
owner: str # 主负责人员
deadline: str # 截止日期
reproduction_steps: str # 复现步骤
related_case_numbers: List[str] # 关联的用例编号列表
```
**依赖:**
- `openpyxl` - Excel文件读取
---
### 4.3 data_analyzer.py - 数据分析模块
**职责:**
- 统计用例执行情况
- 统计BUG数量和等级分布
- 按模块分组统计
- 用例-BUG关联分析
**导出函数:**
```python
def analyze_test_cases(cases: List[TestCase]) -> TestCaseAnalysis
"""
分析测试用例执行情况
:param cases: 测试用例列表
:return: 用例分析结果
"""
def analyze_bugs(bugs: List[Bug]) -> BugAnalysis
"""
分析BUG数据
:param bugs: BUG列表
:return: BUG分析结果
"""
def group_by_module(cases: List[TestCase]) -> Dict[str, List[TestCase]]
"""
按模块分组用例
:param cases: 测试用例列表
:return: 模块->用例列表字典
"""
def link_bugs_to_cases(bugs: List[Bug], cases: List[TestCase]) -> Dict[str, List[Bug]]
"""
关联BUG到用例
:param bugs: BUG列表
:param cases: 测试用例列表
:return: 用例编号->BUG列表字典
"""
def calculate_pass_rate(cases: List[TestCase]) -> float
"""
计算通过率
:param cases: 测试用例列表
:return: 通过率(百分比,保留两位小数)
"""
```
**数据结构:**
```python
@dataclass
class TestCaseAnalysis:
"""测试用例分析结果"""
total_cases: int # 总用例数
executed_cases: int # 已执行用例数
passed_cases: int # 通过用例数
failed_cases: int # 失败用例数
unverified_cases: int # 未验证用例数
undeveloped_cases: int # 未开发用例数
pass_rate: float # 通过率(百分比)
module_stats: Dict[str, ModuleStat] # 按模块统计
@dataclass
class ModuleStat:
"""模块统计"""
module_name: str # 模块名称
total_cases: int # 总用例数
passed_cases: int # 通过数
failed_cases: int # 失败数
unverified_cases: int # 未验证数
undeveloped_cases: int # 未开发数
pass_rate: float # 通过率
@dataclass
class BugAnalysis:
"""BUG分析结果"""
total_bugs: int # BUG总数
remaining_bugs: int # 遗留BUG数(激活+已解决)
closed_bugs: int # 已关闭BUG数
level_distribution: Dict[str, int] # 等级分布
status_distribution: Dict[str, int] # 状态分布
type_distribution: Dict[str, int] # 类型分布
module_distribution: Dict[str, Dict[str, int]] # 按模块分布
```
---
### 4.4 chart_generator.py - 图表生成模块
**职责:**
- 使用matplotlib生成统计图表
- 导出图表为图片文件
- 支持中文显示
**导出函数:**
```python
def generate_bug_level_chart(analysis: BugAnalysis, output_path: str) -> str
"""
生成BUG等级分布图
:param analysis: BUG分析结果
:param output_path: 输出目录
:return: 图片文件路径
"""
def generate_test_result_chart(analysis: TestCaseAnalysis, output_path: str) -> str
"""
生成用例执行结果分布图
:param analysis: 用例分析结果
:param output_path: 输出目录
:return: 图片文件路径
"""
def generate_pass_rate_chart(analysis: TestCaseAnalysis, output_path: str) -> str
"""
生成用例通过率图
:param analysis: 用例分析结果
:param output_path: 输出目录
:return: 图片文件路径
"""
```
**图表样式:**
- 使用中文字体(SimHei或Microsoft YaHei)
- 饼图用于分布统计
- 柱状图用于对比统计
- 保存为PNG格式
**依赖:**
- `matplotlib` - 图表绘制
---
### 4.5 report_generator.py - 报告生成模块
**职责:**
- 读取功能测试报告模板
- 填充报告数据
- 插入图表
- 生成Word文档
**导出函数:**
```python
def generate_report(
template_path: str,
case_analysis: TestCaseAnalysis,
bug_analysis: BugAnalysis,
case_bug_link: Dict[str, List[Bug]],
chart_paths: Dict[str, str],
output_path: str,
project_name: str
) -> str
"""
生成功能测试报告
:param template_path: 模板文件路径
:param case_analysis: 用例分析结果
:param bug_analysis: BUG分析结果
:param case_bug_link: 用例-BUG关联
:param chart_paths: 图表文件路径字典
:param output_path: 输出文件路径
:param project_name: 项目名称
:return: 生成的报告文件路径
"""
def fill_template(template_path: str, data: Dict[str, Any]) -> str
"""
填充模板内容
:param template_path: 模板文件路径
:param data: 填充数据
:return: 填充后的内容
"""
def create_word_document(content: str, chart_paths: Dict[str, str], output_path: str) -> None
"""
创建Word文档
:param content: Markdown格式内容
:param chart_paths: 图表路径
:param output_path: 输出路径
"""
```
**模板占位符替换规则:**
| 占位符 | 替换内容 | 来源 |
|--------|----------|------|
| `{系统名称}` | 项目名称 | BUG列表中的项目名称 |
| `{报告编号}` | {项目名称}-{日期} | 自动生成 |
| `{生成日期}` | 当前日期 | 自动生成 |
| `{项目名称}` | 项目名称 | BUG列表中的项目名称 |
| `{测试负责人}` | "未填写" | 固定值 |
| `{测试人员}` | "未填写" | 固定值 |
| `{系统版本号}` | "未填写" | 固定值 |
| `{服务器IP}` | "未填写" | 固定值 |
| {统计类数据} | 统计结果 | 数据分析模块 |
**依赖:**
- `python-docx` - Word文档操作
- `markdown` - Markdown转HTML(可选)
---
### 4.6 cli.py - 命令行交互模块
**职责:**
- 命令行参数解析
- 用户输入获取
- 进度显示
**导出函数:**
```python
def parse_arguments() -> argparse.Namespace
"""
解析命令行参数
:return: 参数对象
"""
def interactive_input() -> Dict[str, str]
"""
交互式输入
:return: 输入数据字典
"""
def main_cli() -> None
"""
命令行主函数
"""
```
**命令行参数:**
```
usage: main.py [-h] [--testcase TESTCASE] [--buglist BUGLIST] [--output OUTPUT]
功能测试报告生成工具
optional arguments:
-h, --help 显示帮助信息
--testcase TESTCASE 测试用例Excel文件路径
--buglist BUGLIST BUG列表Excel文件路径
--output OUTPUT 输出报告文件路径
```
**依赖:**
- `argparse` - 命令行参数解析
- `tqdm` - 进度条显示
---
### 4.7 gui.py - GUI界面模块
**职责:**
- 提供图形用户界面
- 文件选择
- 生成进度显示
**导出函数:**
```python
def create_main_window() -> tk.Tk
"""
创建主窗口
:return: 窗口对象
"""
def main_gui() -> None
"""
GUI主函数
"""
```
**界面布局:**
```
┌─────────────────────────────────────────────────┐
│ 功能测试报告自动生成工具 │
├─────────────────────────────────────────────────┤
│ │
│ 测试用例文件: [________________] [浏览...] │
│ BUG列表文件: [________________] [浏览...] │
│ 输出目录: [________________] [浏览...] │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ 执行日志 │ │
│ │ │ │
│ │ │ │
│ └─────────────────────────────────────────┘ │
│ │
│ [████████████░░░░░░░] 60% │
│ │
│ [生成报告] [退出] │
└─────────────────────────────────────────────────┘
```
**依赖:**
- `tkinter` - GUI界面
---
### 4.8 main.py - 主入口模块
**职责:**
- 程序入口
- 根据参数选择CLI或GUI模式
- 协调各模块调用
**主流程:**
```python
def main():
"""
主函数
"""
# 1. 解析参数,决定运行模式
# 2. 读取Excel数据
# 3. 分析数据
# 4. 生成图表
# 5. 填充模板
# 6. 生成Word报告
# 7. 输出结果
```
---
## 5. 执行计划
### 5.1 阶段划分
| 阶段 | 任务 | 预计工作量 | 依赖 | 状态 |
|-----|------|----------|------|------|
| **阶段1** | 创建项目目录结构 | 0.5天 | - | ⏳ 待执行 |
| **阶段2** | 实现config.py配置模块 | 0.5天 | 阶段1 | ⏳ 待执行 |
| **阶段3** | 实现excel_reader.py读取模块 | 1天 | 阶段2 | ⏳ 待执行 |
| **阶段4** | 实现data_analyzer.py分析模块 | 1-2天 | 阶段3 | ⏳ 待执行 |
| **阶段5** | 实现chart_generator.py图表模块 | 1天 | 阶段4 | ⏳ 待执行 |
| **阶段6** | 实现report_generator.py报告生成模块 | 1-2天 | 阶段5 | ⏳ 待执行 |
| **阶段7** | 实现cli.py命令行模块 | 0.5天 | 阶段6 | ⏳ 待执行 |
| **阶段8** | 实现gui.py GUI模块 | 1天 | 阶段6 | ⏳ 待执行 |
| **阶段9** | 实现main.py主入口 | 0.5天 | 阶段7,8 | ⏳ 待执行 |
| **阶段10** | 集成测试 | 1天 | 阶段9 | ⏳ 待执行 |
| **阶段11** | 文档编写 | 0.5天 | 阶段10 | ⏳ 待执行 |
### 5.2 里程碑
| 里程碑 | 完成标准 | 状态 |
|-------|---------|------|
| M1: 基础框架完成 | config.py、excel_reader.py、data_analyzer.py 完成 | ⏳ 待达成 |
| M2: 报告生成完成 | chart_generator.py、report_generator.py 完成,能生成Word报告 | ⏳ 待达成 |
| M3: 交互界面完成 | cli.py、gui.py 完成,支持命令行和GUI两种方式 | ⏳ 待达成 |
| M4: 测试完成 | 集成测试通过,无重大BUG | ⏳ 待达成 |
---
## 6. 测试计划
### 6.1 单元测试
每个模块需要测试:
| 模块 | 测试内容 |
|------|----------|
| excel_reader.py | Excel文件读取、数据验证、空值处理 |
| data_analyzer.py | 统计计算、分组逻辑、关联逻辑 |
| chart_generator.py | 图表生成、中文显示、图片导出 |
| report_generator.py | 模板填充、Word生成、图表插入 |
### 6.2 集成测试
测试场景:
1. 完整流程测试(用现有测试数据)
2. 空数据处理测试
3. 异常数据处理测试
4. 报告格式验证
5. 图表显示验证
### 6.3 测试数据
**测试用例文件:** `testcases/新统一平台权限管理测试用例.xlsx`
**BUG列表文件:** `testcases/新统一平台权限管理-BUG列表.xlsx`
---
## 7. 风险与应对
| 风险 | 影响 | 概率 | 应对措施 |
|-----|------|------|---------|
| Excel格式变化导致读取失败 | 高 | 中 | 从模板文件读取列映射,灵活适配 |
| 中文字体显示问题 | 中 | 中 | 配置matplotlib中文字体,测试验证 |
| Word格式兼容性问题 | 中 | 低 | 使用python-docx标准API,测试多种Office版本 |
| 模板占位符遗漏 | 中 | 中 | 编写单元测试覆盖所有占位符 |
| 图表插入位置错误 | 低 | 低 | 固定图表插入位置标识 |
| 依赖包兼容性 | 低 | 低 | 使用requirements.txt固定版本 |
---
## 8. 验收标准
### 8.1 功能验收
- [ ] 能正确读取测试用例Excel文件
- [ ] 能正确读取BUG列表Excel文件
- [ ] 能正确统计用例执行情况
- [ ] 能正确统计BUG数量和等级分布
- [ ] 能正确关联用例和BUG
- [ ] 能生成统计图表(饼图、柱状图)
- [ ] 能正确填充报告模板
- [ ] 能生成Word格式报告
- [ ] 支持命令行交互
- [ ] 支持GUI交互
### 8.2 代码质量验收
- [ ] 符合代码规范要求(中文注释)
- [ ] 每个函数有docstring说明
- [ ] 异常处理完善
- [ ] 日志输出完整
### 8.3 文档验收
- [ ] README.md(使用说明)
- [ ] requirements.txt(依赖包列表)
---
## 9. 参考文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
- 问题总结: `Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
- 方法总结: `Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md`
- 文档规范: `Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md`
- 测试规范: `Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md`
---
## 10. 执行结果记录
### 10.1 问题记录
| 日期 | 问题描述 | 解决方案 | 状态 |
|------|----------|---------|------|
| - | - | - | - |
### 10.2 变更记录
| 日期 | 变更内容 | 变更原因 | 状态 |
|------|----------|----------|------|
| - | - | - | - |
---
*文档结束*
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论