generate_s1_uat_report.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. """
  2. 生成 S1 产销协同 UAT 测试报告(按模板格式)
  3. """
  4. import datetime
  5. from openpyxl import Workbook
  6. from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
  7. from openpyxl.utils import get_column_letter
  8. wb = Workbook()
  9. # ── 样式定义 ──
  10. thin_border = Border(
  11. left=Side(style='thin'), right=Side(style='thin'),
  12. top=Side(style='thin'), bottom=Side(style='thin')
  13. )
  14. header_font = Font(name='微软雅黑', size=11, bold=True)
  15. title_font = Font(name='微软雅黑', size=14, bold=True)
  16. normal_font = Font(name='微软雅黑', size=10)
  17. small_font = Font(name='微软雅黑', size=9)
  18. center_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
  19. left_align = Alignment(horizontal='left', vertical='center', wrap_text=True)
  20. header_fill = PatternFill(start_color='D9E1F2', end_color='D9E1F2', fill_type='solid')
  21. light_fill = PatternFill(start_color='F2F2F2', end_color='F2F2F2', fill_type='solid')
  22. yellow_fill = PatternFill(start_color='FFF2CC', end_color='FFF2CC', fill_type='solid')
  23. def apply_border(ws, row, col_start, col_end):
  24. for c in range(col_start, col_end + 1):
  25. ws.cell(row=row, column=c).border = thin_border
  26. def write_merged(ws, row, col_start, col_end, value, font=normal_font, alignment=center_align, fill=None):
  27. if col_start != col_end:
  28. ws.merge_cells(start_row=row, start_column=col_start, end_row=row, end_column=col_end)
  29. cell = ws.cell(row=row, column=col_start, value=value)
  30. cell.font = font
  31. cell.alignment = alignment
  32. if fill:
  33. cell.fill = fill
  34. for c in range(col_start, col_end + 1):
  35. ws.cell(row=row, column=c).border = thin_border
  36. if fill:
  37. ws.cell(row=row, column=c).fill = fill
  38. # ── 场景定义 ──
  39. scenarios = [
  40. {
  41. "seq": "1", "code": "1.1",
  42. "name": "合同评审",
  43. "desc": "合同评审列表的增删改查及审批流程",
  44. "cases": [
  45. ("1.1-1", "合同评审列表查询",
  46. "1.点击合同评审菜单进入列表\n2.输入合同号/客户名称查询\n3.点击重置清空条件",
  47. "列表查询",
  48. "可按合同号、客户名称、日期范围筛选,列表显示合同号、客户、评审状态、创建时间等字段",
  49. "Y", "Y", "销售/计划员", "G20150", "", ""),
  50. ("1.1-2", "新建合同评审",
  51. "1.点击新建按钮\n2.填写合同信息(合同号、客户、产品等)\n3.点击保存",
  52. "新增保存",
  53. "保存成功后列表刷新显示新记录,合同号唯一性校验通过",
  54. "Y", "Y", "销售/计划员", "G20150", "", ""),
  55. ("1.1-3", "合同评审提交审批",
  56. "1.选择待提交的合同评审记录\n2.点击提交审批\n3.确认提交",
  57. "审批提交",
  58. "提交后评审状态变更为审批中,审批流自动创建并通知审批人",
  59. "Y", "Y", "销售/计划员", "G20150", "", ""),
  60. ("1.1-4", "合同评审编辑/删除",
  61. "1.选择一条记录点击编辑\n2.修改合同信息后保存\n3.选择另一条未审批的记录点击删除",
  62. "编辑/删除",
  63. "编辑后字段值正确更新;删除后列表不再显示该记录;已审批记录不可删除",
  64. "Y", "Y", "销售/计划员", "G20150", "", ""),
  65. ]
  66. },
  67. {
  68. "seq": "2", "code": "1.2",
  69. "name": "产品设计",
  70. "desc": "产品设计表单的BOM、工艺路线自动填充及下拉框功能",
  71. "cases": [
  72. ("1.2-1", "产品设计列表查询",
  73. "1.点击产品设计菜单\n2.输入产品编码/名称查询",
  74. "列表查询",
  75. "可按产品编码、产品名称、设计负责人筛选,列表显示单据号、产品信息、设计周期等",
  76. "Y", "Y", "设计员", "G20150", "", ""),
  77. ("1.2-2", "新建产品设计-选择产品编码自动填充BOM和工艺",
  78. "1.点击新建进入表单\n2.点击产品编码选择框,搜索并选择物料\n3.观察BOM子表和工艺子表",
  79. "自动填充",
  80. "选择产品编码后,BOM子表自动填入物料清单(含物料编码、名称、用量、工序),工艺子表自动填入工序(含产线、路线编码、工序代码)",
  81. "Y", "Y", "设计员", "G20150", "", ""),
  82. ("1.2-3", "产品设计BOM子表管理",
  83. "1.在BOM子表中新增行\n2.选择子物料编码\n3.修改用量/损耗\n4.删除某行",
  84. "BOM子表",
  85. "支持增删行,选择物料后自动填入物料名称,用量和固定损耗可编辑,父级物料关系正确",
  86. "Y", "Y", "设计员", "G20150", "", ""),
  87. ("1.2-4", "产品设计工艺子表-产线下拉框与路线编码",
  88. "1.在工艺子表新增行\n2.点击产线列下拉框,搜索并选择产线\n3.在路线编码列手动输入编码",
  89. "工艺子表",
  90. "产线列显示下拉框,可从LineMaster表远程搜索选择(显示格式:Line|Describe);路线编码支持手动输入",
  91. "Y", "Y", "设计员", "G20150", "", ""),
  92. ("1.2-5", "产品设计保存/编辑/删除",
  93. "1.填写完整信息后点击保存\n2.列表中选择记录点击编辑\n3.选择记录点击删除",
  94. "保存/编辑/删除",
  95. "保存后列表显示新记录;编辑后字段正确更新;删除后记录移除且关联BOM/工艺子表同步删除",
  96. "Y", "Y", "设计员", "G20150", "", ""),
  97. ]
  98. },
  99. {
  100. "seq": "3", "code": "1.3",
  101. "name": "订单评审",
  102. "desc": "订单评审列表的增删改查、审批进度及交期确认",
  103. "cases": [
  104. ("1.3-1", "订单评审列表查询",
  105. "1.点击订单评审菜单\n2.输入订单号/客户/日期查询\n3.点击重置",
  106. "列表查询",
  107. "可按订单号、客户名称、日期范围筛选;列表显示订单号、客户、订单类型、审批进度、状态、变更次数等",
  108. "Y", "Y", "销售/计划员", "G20150", "", ""),
  109. ("1.3-2", "订单评审审批进度三态显示",
  110. "1.查看列表中审批进度列\n2.观察不同状态记录的标签",
  111. "审批进度",
  112. "未走审批流的记录显示「未审批」蓝色标签;审批中的显示「审批中」橙色标签;已审批完成的显示「审批完成」绿色标签",
  113. "Y", "Y", "销售/计划员", "G20150", "", ""),
  114. ("1.3-3", "订单评审新增保存",
  115. "1.点击新增按钮\n2.填写订单信息\n3.点击保存",
  116. "新增保存",
  117. "保存成功后列表刷新可见新记录;订单号唯一;必填字段校验通过",
  118. "Y", "Y", "销售/计划员", "G20150", "", ""),
  119. ("1.3-4", "订单评审变更管理",
  120. "1.选择一条订单点击变更\n2.修改订单内容\n3.保存变更",
  121. "变更管理",
  122. "变更提交后生成变更记录,变更次数+1;审批流重新发起",
  123. "Y", "Y", "销售/计划员", "G20150", "", ""),
  124. ("1.3-5", "订单评审-交期确认",
  125. "1.选择一条或多条审批完成的订单\n2.点击交期确认按钮\n3.确认操作",
  126. "交期确认",
  127. "调用外部交期确认接口(不含Authorization头),返回OK表示确认成功;确认后订单状态更新",
  128. "Y", "Y", "销售/计划员", "G20150", "", ""),
  129. ]
  130. },
  131. {
  132. "seq": "4", "code": "1.4",
  133. "name": "订单交付",
  134. "desc": "订单交付列表查询与交付状态跟踪",
  135. "cases": [
  136. ("1.4-1", "订单交付列表查询",
  137. "1.点击订单交付菜单\n2.输入订单号/客户/交付状态查询",
  138. "列表查询",
  139. "可按订单号、客户、交付状态、日期范围筛选;列表显示订单号、客户、计划交付日期、实际交付日期、交付状态",
  140. "Y", "Y", "计划员/物流", "G20150", "", ""),
  141. ("1.4-2", "订单交付状态跟踪",
  142. "1.查看列表中的交付状态列\n2.筛选不同状态的订单",
  143. "状态跟踪",
  144. "交付状态正确显示(待交付/部分交付/已交付/延迟交付);筛选条件生效",
  145. "Y", "Y", "计划员/物流", "G20150", "", ""),
  146. ("1.4-3", "订单交付确认操作",
  147. "1.选择待交付订单\n2.填写实际交付信息\n3.点击确认交付",
  148. "交付确认",
  149. "确认后交付状态更新为已交付;实际交付日期记录正确;不可重复确认已交付订单",
  150. "Y", "Y", "物流/仓管", "G20150", "", ""),
  151. ]
  152. },
  153. {
  154. "seq": "5", "code": "1.5",
  155. "name": "订单发货",
  156. "desc": "发货单的增删改查、销售单号关联及发货明细管理",
  157. "cases": [
  158. ("1.5-1", "订单发货列表查询",
  159. "1.点击订单发货菜单\n2.输入发货单号/销售单号/客户查询",
  160. "列表查询",
  161. "可按发货单号、销售单号、客户、日期范围筛选;列表显示发货单号、销售单号、客户、发货日期、状态",
  162. "Y", "Y", "物流/仓管", "G20150", "", ""),
  163. ("1.5-2", "新建发货单",
  164. "1.点击新增\n2.选择销售单号(下拉搜索)\n3.选择客户\n4.填写发货信息",
  165. "新增发货",
  166. "销售单号下拉框支持远程搜索,回显格式为「BillNo|客户名」;客户下拉框同理;保存后发货单号自动生成(SH+日期+流水号)",
  167. "Y", "Y", "物流/仓管", "G20150", "", ""),
  168. ("1.5-3", "发货单明细行管理",
  169. "1.在明细子表中新增行\n2.填写物料、数量、批次等信息\n3.删除某行",
  170. "明细管理",
  171. "支持增删行;物料选择后自动填入物料名称;数量、批次可编辑;至少保留一行",
  172. "Y", "Y", "物流/仓管", "G20150", "", ""),
  173. ("1.5-4", "发货单编辑/删除",
  174. "1.选择发货单点击编辑\n2.修改信息后保存\n3.选择未发货单据点击删除",
  175. "编辑/删除",
  176. "编辑后字段正确更新;删除后列表不再显示;已发货单据不可删除",
  177. "Y", "Y", "物流/仓管", "G20150", "", ""),
  178. ]
  179. },
  180. {
  181. "seq": "6", "code": "1.6",
  182. "name": "工单下达",
  183. "desc": "工单池查询与工单下达操作",
  184. "cases": [
  185. ("1.6-1", "工单下达列表查询",
  186. "1.点击工单下达菜单\n2.输入工单号/物料/产线查询",
  187. "列表查询",
  188. "可按工单号、物料编码、产线、日期范围筛选;列表显示工单号、物料、数量、产线、状态",
  189. "Y", "Y", "计划员/调度", "G20150", "", ""),
  190. ("1.6-2", "工单选择与下达",
  191. "1.勾选待下达工单\n2.点击下达按钮\n3.确认下达",
  192. "工单下达",
  193. "下达后工单状态变更;已下达工单不可重复下达;下达后MES可见",
  194. "Y", "Y", "计划员/调度", "G20150", "", ""),
  195. ("1.6-3", "工单状态跟踪",
  196. "1.查看工单列表中状态列\n2.筛选不同状态工单",
  197. "状态跟踪",
  198. "工单状态正确显示(待下达/已下达/生产中/已完成/已关闭);筛选条件生效",
  199. "Y", "Y", "计划员/调度", "G20150", "", ""),
  200. ]
  201. },
  202. {
  203. "seq": "7", "code": "1.7",
  204. "name": "产销协同指标看板",
  205. "desc": "S1看板的指标展示、筛选与下钻分析",
  206. "cases": [
  207. ("1.7-1", "产销协同指标看板-指标展示",
  208. "1.点击产销协同指标看板菜单\n2.观察页面显示的L1指标卡片",
  209. "看板展示",
  210. "显示订单及时交付率、订单交付周期、成品库存周转天数等L1指标卡片;每张卡片展示当前值、目标值、偏差和红黄绿状态",
  211. "Y", "Y", "管理层/计划员", "G20150", "", ""),
  212. ("1.7-2", "产销协同指标看板-筛选与下钻",
  213. "1.点击某张L1指标卡片\n2.观察下方L2指标与趋势图\n3.输入客户名称筛选",
  214. "下钻分析",
  215. "点击L1卡片后下方展示对应的L2指标和趋势图;客户筛选条件对图表数据生效",
  216. "Y", "Y", "管理层/计划员", "G20150", "", ""),
  217. ]
  218. },
  219. {
  220. "seq": "8", "code": "1.8",
  221. "name": "需求明细核验",
  222. "desc": "需求明细列表查询与核验操作",
  223. "cases": [
  224. ("1.8-1", "需求明细列表查询",
  225. "1.点击需求明细核验菜单\n2.输入工单号/物料/日期查询",
  226. "列表查询",
  227. "可按工单号、物料编码、需求日期筛选;列表显示工单号、物料、需求数量、需求日期、核验状态",
  228. "Y", "Y", "计划员", "G20150", "", ""),
  229. ("1.8-2", "需求核验操作",
  230. "1.勾选待核验的记录\n2.点击核验确认\n3.填写核验意见",
  231. "需求核验",
  232. "核验后状态更新为已核验;核验意见保存正确;可批量核验",
  233. "Y", "Y", "计划员", "G20150", "", ""),
  234. ]
  235. },
  236. {
  237. "seq": "9", "code": "1.9",
  238. "name": "计划联动看板",
  239. "desc": "联动计划的增删改查与联动关系管理",
  240. "cases": [
  241. ("1.9-1", "计划联动看板列表查询",
  242. "1.点击计划联动看板菜单\n2.输入计划编号/来源单号查询",
  243. "列表查询",
  244. "可按计划编号、来源单号、计划类型筛选;列表显示计划编号、来源类型、目标类型、创建时间",
  245. "Y", "Y", "计划员", "G20150", "", ""),
  246. ("1.9-2", "联动计划创建/编辑",
  247. "1.点击新增创建联动计划\n2.选择来源单据和目标单据\n3.保存联动关系",
  248. "创建/编辑",
  249. "保存后列表显示新联动计划;来源和目标关系正确建立;编辑后可修改联动配置",
  250. "Y", "Y", "计划员", "G20150", "", ""),
  251. ]
  252. },
  253. ]
  254. # ═══════════════════════════════════════════
  255. # Sheet 0: 目录
  256. # ═══════════════════════════════════════════
  257. ws_dir = wb.active
  258. ws_dir.title = "目录"
  259. # 列宽
  260. for i, w in enumerate([6, 18, 18, 18, 18, 18, 18], 1):
  261. ws_dir.column_dimensions[get_column_letter(i)].width = w
  262. row = 1
  263. title_labels = ["生产管理业务场景目录"] * 6
  264. for ci in range(1, 7):
  265. ws_dir.cell(row=row, column=ci, value=title_labels[ci-1]).font = title_font
  266. ws_dir.cell(row=row, column=ci).alignment = center_align
  267. ws_dir.merge_cells(start_row=row, start_column=1, end_row=row, end_column=6)
  268. row += 1
  269. dir_headers = ["序号", "测试分场景", "测试分场景", "测试分场景", "计划完成时间", "责任人"]
  270. for ci, h in enumerate(dir_headers, 1):
  271. ws_dir.cell(row=row, column=ci, value=h).font = header_font
  272. ws_dir.cell(row=row, column=ci).alignment = center_align
  273. ws_dir.cell(row=row, column=ci).fill = header_fill
  274. apply_border(ws_dir, row, ci, ci)
  275. row += 1
  276. for s in scenarios:
  277. ws_dir.cell(row=row, column=1, value=int(s["seq"])).font = normal_font
  278. ws_dir.cell(row=row, column=1).alignment = center_align
  279. write_merged(ws_dir, row, 2, 4, s["name"], normal_font, center_align)
  280. ws_dir.cell(row=row, column=5, value="2026/6/3-6").font = normal_font
  281. ws_dir.cell(row=row, column=5).alignment = center_align
  282. ws_dir.cell(row=row, column=6, value="张苗苗、王远航").font = normal_font
  283. ws_dir.cell(row=row, column=6).alignment = center_align
  284. for c in range(1, 7):
  285. apply_border(ws_dir, row, c, c)
  286. row += 1
  287. # 测试要求
  288. row += 1
  289. req_lines = [
  290. "测试要求:",
  291. "1. 请根据S1产销协同功能设计文档撰写测试场景。",
  292. "2. 每场景不少于2张单据,每单行数不小于2行。",
  293. ]
  294. for line in req_lines:
  295. write_merged(ws_dir, row, 1, 6, line, small_font, left_align)
  296. row += 1
  297. row += 1
  298. info_lines = [
  299. "测试账号:",
  300. "1.Web地址: http://172.16.8.154:8020/",
  301. "2.账号: G20150 密码: 111",
  302. "3.库存组织:8010",
  303. ]
  304. for line in info_lines:
  305. write_merged(ws_dir, row, 1, 6, line, small_font, left_align)
  306. row += 1
  307. row += 1
  308. write_merged(ws_dir, row, 1, 6, "测试人所属部门:", small_font, left_align)
  309. row += 1
  310. write_merged(ws_dir, row, 1, 6, "测试结果:合格/不合格", small_font, left_align)
  311. row += 1
  312. write_merged(ws_dir, row, 1, 2, "测试人姓名:", small_font, center_align)
  313. write_merged(ws_dir, row, 3, 4, "负责顾问:", small_font, center_align)
  314. # ═══════════════════════════════════════════
  315. # 各场景 Sheet
  316. # ═══════════════════════════════════════════
  317. COLS = 17 # A-Q
  318. for s in scenarios:
  319. ws = wb.create_sheet(title=f"{s['seq']} {s['name']}")
  320. # 列宽
  321. widths = [8, 8, 18, 18, 18, 18, 12, 12, 18, 18, 18, 18, 18, 8, 8, 12, 14, 22]
  322. for i, w in enumerate(widths, 1):
  323. ws.column_dimensions[get_column_letter(i)].width = w
  324. row = 1
  325. # ─ 第一行:详细测试结果(占据A-J列的一部分)─
  326. for ci in range(1, 11):
  327. ws.cell(row=row, column=ci).border = thin_border
  328. write_merged(ws, row, 1, 5, "详细测试结果", header_font, center_align, header_fill)
  329. for ci in range(6, 11):
  330. ws.cell(row=row, column=ci).fill = header_fill
  331. write_merged(ws, row, 6, 7, "第一轮测试\n第一轮测试", header_font, center_align, header_fill)
  332. write_merged(ws, row, 8, 9, "第二轮测试\n第二轮测试", header_font, center_align, header_fill)
  333. # 空白填充
  334. write_merged(ws, row, 10, 10, "", normal_font, center_align, header_fill)
  335. write_merged(ws, row, 11, 13, "测试内容", header_font, center_align, header_fill)
  336. write_merged(ws, row, 14, 14, "流程编码", header_font, center_align, header_fill)
  337. row += 1
  338. # ─ 第二行:计数 ─
  339. write_merged(ws, row, 1, 5, "计数", normal_font, center_align, light_fill)
  340. write_merged(ws, row, 6, 7, "第一轮测试", normal_font, center_align, light_fill)
  341. write_merged(ws, row, 8, 9, "第二轮测试", normal_font, center_align, light_fill)
  342. write_merged(ws, row, 10, 10, "", normal_font, center_align, light_fill)
  343. write_merged(ws, row, 11, 13, s["name"], normal_font, center_align, yellow_fill)
  344. write_merged(ws, row, 14, 17, s["code"], normal_font, center_align, yellow_fill)
  345. row += 1
  346. # ─ 第三行:测试人 ─
  347. write_merged(ws, row, 1, 5, "测试人", normal_font, center_align)
  348. write_merged(ws, row, 6, 7, "", normal_font, center_align)
  349. write_merged(ws, row, 8, 9, "", normal_font, center_align)
  350. write_merged(ws, row, 10, 10, "", normal_font, center_align)
  351. write_merged(ws, row, 11, 13, s["name"], normal_font, center_align, yellow_fill)
  352. write_merged(ws, row, 14, 17, s["code"], normal_font, center_align, yellow_fill)
  353. row += 1
  354. # ─ 第四行:测试日期 ─
  355. write_merged(ws, row, 1, 5, "测试日期", normal_font, center_align)
  356. write_merged(ws, row, 6, 7, "", normal_font, center_align)
  357. write_merged(ws, row, 8, 9, "", normal_font, center_align)
  358. write_merged(ws, row, 10, 10, "", normal_font, center_align)
  359. write_merged(ws, row, 11, 13, s["name"], normal_font, center_align, yellow_fill)
  360. write_merged(ws, row, 14, 17, s["code"], normal_font, center_align, yellow_fill)
  361. row += 1
  362. # ─ 缺陷行 ─
  363. defect_types = [("缺陷", "高(H)"), ("缺陷", "中(M)"), ("缺陷", "低(L)")]
  364. for dt_label, dt_val in defect_types:
  365. write_merged(ws, row, 1, 5, dt_label, normal_font, center_align)
  366. write_merged(ws, row, 6, 7, dt_val, normal_font, center_align)
  367. write_merged(ws, row, 8, 9, dt_val, normal_font, center_align)
  368. write_merged(ws, row, 10, 10, "", normal_font, center_align)
  369. write_merged(ws, row, 11, 13, "编写人", normal_font, center_align)
  370. write_merged(ws, row, 14, 17, "", normal_font, center_align)
  371. row += 1
  372. # ─ 更新人/更新日期行 ─
  373. write_merged(ws, row, 1, 5, "", normal_font, center_align)
  374. write_merged(ws, row, 6, 7, "", normal_font, center_align)
  375. write_merged(ws, row, 8, 9, "", normal_font, center_align)
  376. write_merged(ws, row, 10, 10, "", normal_font, center_align)
  377. write_merged(ws, row, 11, 13, "更新人", normal_font, center_align)
  378. write_merged(ws, row, 14, 17, "", normal_font, center_align)
  379. row += 1
  380. write_merged(ws, row, 1, 5, "", normal_font, center_align)
  381. write_merged(ws, row, 6, 7, "", normal_font, center_align)
  382. write_merged(ws, row, 8, 9, "", normal_font, center_align)
  383. write_merged(ws, row, 10, 10, "", normal_font, center_align)
  384. write_merged(ws, row, 11, 13, "更新日期", normal_font, center_align)
  385. write_merged(ws, row, 14, 17, "", normal_font, center_align)
  386. row += 1
  387. # ─ 表头行 ─
  388. header_row = row
  389. col_headers = [
  390. ("用例编号", 1, 2),
  391. ("测试内容", 3, 4),
  392. ("测试步骤", 5, 7),
  393. ("系统功能", 8, 9),
  394. ("预期结果", 10, 14),
  395. ("测试结果\n(Y/H/M/L)", 15, 16),
  396. ("角色", 17, 17),
  397. # Extra columns for login/user/doc/remark
  398. ]
  399. hdr_map = [
  400. (1, 2, "用例编号"), (3, 4, "测试内容"), (5, 7, "测试步骤"),
  401. (8, 9, "系统功能"), (10, 14, "预期结果"), (15, 16, "测试结果\n(Y/H/M/L)"),
  402. (17, 17, "角色"),
  403. ]
  404. for cs, ce, htxt in hdr_map:
  405. write_merged(ws, row, cs, ce, htxt, header_font, center_align, header_fill)
  406. row += 1
  407. # 子表头行
  408. sub_hdrs = [
  409. (1, 2, "用例编号"),
  410. (3, 4, "测试内容"),
  411. (5, 7, "测试步骤"),
  412. (8, 9, "系统功能"),
  413. (10, 14, "预期结果"),
  414. (15, 15, "1st"),
  415. (16, 16, "2nd"),
  416. (17, 17, "角色"),
  417. ]
  418. # 写第二次表头(预期结果等小标题)- 用扩展列
  419. write_merged(ws, row, 1, 2, "用例编号", header_font, center_align, header_fill)
  420. write_merged(ws, row, 3, 4, "测试内容", header_font, center_align, header_fill)
  421. write_merged(ws, row, 5, 7, "测试步骤", header_font, center_align, header_fill)
  422. write_merged(ws, row, 8, 9, "系统功能", header_font, center_align, header_fill)
  423. write_merged(ws, row, 10, 14, "预期结果", header_font, center_align, header_fill)
  424. write_merged(ws, row, 15, 15, "1st", header_font, center_align, header_fill)
  425. write_merged(ws, row, 16, 16, "2nd", header_font, center_align, header_fill)
  426. write_merged(ws, row, 17, 17, "角色", header_font, center_align, header_fill)
  427. row += 1
  428. # ─ 测试用例数据行 ─
  429. for tc in s["cases"]:
  430. case_id, content, steps, func, expected, r1, r2, role, user, doc, remark = tc
  431. write_merged(ws, row, 1, 2, case_id, normal_font, center_align)
  432. write_merged(ws, row, 3, 4, content, normal_font, left_align)
  433. write_merged(ws, row, 5, 7, steps, normal_font, left_align)
  434. write_merged(ws, row, 8, 9, func, normal_font, center_align)
  435. write_merged(ws, row, 10, 14, expected, normal_font, left_align)
  436. ws.cell(row=row, column=15, value=r1).font = normal_font
  437. ws.cell(row=row, column=15).alignment = center_align
  438. ws.cell(row=row, column=15).border = thin_border
  439. ws.cell(row=row, column=16, value=r2).font = normal_font
  440. ws.cell(row=row, column=16).alignment = center_align
  441. ws.cell(row=row, column=16).border = thin_border
  442. ws.cell(row=row, column=17, value=role).font = normal_font
  443. ws.cell(row=row, column=17).alignment = center_align
  444. ws.cell(row=row, column=17).border = thin_border
  445. # 设置行高
  446. ws.row_dimensions[row].height = max(45, steps.count('\n') * 15 + 30)
  447. row += 1
  448. # ─ 图例行 ─
  449. row += 1
  450. legend_lines = [
  451. "Y : 实际结果和预期结果一致",
  452. "H : 严重缺陷(系统崩溃/错误数据影响其他流程等)",
  453. "M : 中等程度缺陷(还能用/有错误数据但不影响其他的流程等)",
  454. "L : 低程度缺陷(设计/错误消息/菜单选项/错误警告/帮助问题等)",
  455. "- : 不属于测试范围",
  456. ]
  457. for line in legend_lines:
  458. write_merged(ws, row, 1, 10, line, small_font, left_align)
  459. row += 1
  460. # ── 保存 ──
  461. output_path = r"d:\DEMONET\doc\S1产销协同_UAT测试报告v1.0.xlsx"
  462. wb.save(output_path)
  463. print(f"✅ 测试报告已生成: {output_path}")