build_test_docs_utf8.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. # -*- coding: utf-8 -*-
  2. from pathlib import Path
  3. from docx import Document
  4. from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
  5. from docx.enum.text import WD_ALIGN_PARAGRAPH
  6. from docx.oxml import OxmlElement
  7. from docx.oxml.ns import qn
  8. from docx.shared import Inches, Pt, RGBColor
  9. ROOT = Path(__file__).resolve().parent
  10. PLAN_DOCX = ROOT / "0235711-王刚-综合测试计划.docx"
  11. REPORT_DOCX = ROOT / "0235711-王刚-综合测试报告.docx"
  12. AUTO_ARTIFACTS = ROOT / "tests" / "auto_tests" / "artifacts"
  13. def format_run(run, font="宋体", size=10.5, bold=False, color=None):
  14. run.font.name = font
  15. run._element.rPr.rFonts.set(qn("w:eastAsia"), font)
  16. run.font.size = Pt(size)
  17. run.bold = bold
  18. if color:
  19. run.font.color.rgb = RGBColor(*color)
  20. def setup_doc(title, subtitle):
  21. doc = Document()
  22. section = doc.sections[0]
  23. section.top_margin = Inches(0.8)
  24. section.bottom_margin = Inches(0.8)
  25. section.left_margin = Inches(0.85)
  26. section.right_margin = Inches(0.85)
  27. doc.styles["Normal"].font.name = "宋体"
  28. doc.styles["Normal"]._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
  29. doc.styles["Normal"].font.size = Pt(10.5)
  30. p = doc.add_paragraph()
  31. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  32. r = p.add_run(title)
  33. format_run(r, "微软雅黑", 20, True, (17, 24, 39))
  34. p = doc.add_paragraph()
  35. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  36. r = p.add_run(subtitle)
  37. format_run(r, "微软雅黑", 11, False, (82, 96, 113))
  38. doc.add_paragraph("")
  39. return doc
  40. def h1(doc, text):
  41. p = doc.add_paragraph()
  42. p.paragraph_format.space_before = Pt(10)
  43. p.paragraph_format.space_after = Pt(6)
  44. r = p.add_run(text)
  45. format_run(r, "微软雅黑", 15, True, (23, 78, 166))
  46. def h2(doc, text):
  47. p = doc.add_paragraph()
  48. p.paragraph_format.space_before = Pt(7)
  49. p.paragraph_format.space_after = Pt(4)
  50. r = p.add_run(text)
  51. format_run(r, "微软雅黑", 12, True, (17, 24, 39))
  52. def body(doc, text):
  53. p = doc.add_paragraph(text)
  54. p.paragraph_format.first_line_indent = Pt(21)
  55. p.paragraph_format.line_spacing = 1.25
  56. p.paragraph_format.space_after = Pt(4)
  57. for r in p.runs:
  58. format_run(r)
  59. def border(cell):
  60. tc_pr = cell._tc.get_or_add_tcPr()
  61. borders = tc_pr.first_child_found_in("w:tcBorders")
  62. if borders is None:
  63. borders = OxmlElement("w:tcBorders")
  64. tc_pr.append(borders)
  65. for edge in ("top", "left", "bottom", "right"):
  66. element = borders.find(qn(f"w:{edge}"))
  67. if element is None:
  68. element = OxmlElement(f"w:{edge}")
  69. borders.append(element)
  70. element.set(qn("w:val"), "single")
  71. element.set(qn("w:sz"), "6")
  72. element.set(qn("w:space"), "0")
  73. element.set(qn("w:color"), "D8DEE8")
  74. def shade(cell, fill):
  75. tc_pr = cell._tc.get_or_add_tcPr()
  76. shd = OxmlElement("w:shd")
  77. shd.set(qn("w:fill"), fill)
  78. tc_pr.append(shd)
  79. def cell_text(cell, text, bold=False, color=None):
  80. cell.text = ""
  81. p = cell.paragraphs[0]
  82. p.alignment = WD_ALIGN_PARAGRAPH.CENTER if len(str(text)) <= 12 else WD_ALIGN_PARAGRAPH.LEFT
  83. r = p.add_run(str(text))
  84. format_run(r, size=9.5, bold=bold, color=color)
  85. cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
  86. border(cell)
  87. def table(doc, headers, rows):
  88. t = doc.add_table(rows=1, cols=len(headers))
  89. t.alignment = WD_TABLE_ALIGNMENT.CENTER
  90. t.autofit = True
  91. for i, header in enumerate(headers):
  92. shade(t.rows[0].cells[i], "EAF1FF")
  93. cell_text(t.rows[0].cells[i], header, True, (23, 78, 166))
  94. for row in rows:
  95. cells = t.add_row().cells
  96. for i, val in enumerate(row):
  97. cell_text(cells[i], val)
  98. doc.add_paragraph("")
  99. def caption(doc, text):
  100. p = doc.add_paragraph()
  101. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  102. r = p.add_run(text)
  103. format_run(r, size=9, color=(82, 96, 113))
  104. def picture(doc, filename, text):
  105. path = AUTO_ARTIFACTS / filename
  106. if path.exists():
  107. p = doc.add_paragraph()
  108. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  109. p.add_run().add_picture(str(path), width=Inches(6.1))
  110. caption(doc, text)
  111. def build_plan():
  112. doc = setup_doc("0235711-王刚-综合测试计划", "ServicePilot 智能客户工单与 SLA 履约管理平台")
  113. h1(doc, "一、测试范围")
  114. body(doc, "本项目测试对象包括前端 Angular 页面、后端 Node.js + TypeScript + Express 接口、Parse 数据权限、工单管理、SLA 管理、知识库、附件管理、满意度评价和数据看板。")
  115. table(doc, ["测试类型", "目录", "工具", "数量目标", "覆盖范围"], [
  116. ["单元测试", "tests/unit_tests", "Jest", "26 条", "状态流转、权限、SLA、附件规则"],
  117. ["接口测试", "tests/api_tests", "Jest + Supertest", "5 条", "health、Parse 配置、404 契约"],
  118. ["自动化测试", "tests/auto_tests", "Playwright CLI", "7 条", "七个前端关键页面"],
  119. ["性能测试", "tests/performance_tests", "Locust", "2 个脚本", "后端接口、前端页面"],
  120. ])
  121. h1(doc, "二、四类测试计划")
  122. h2(doc, "1. 单元测试计划")
  123. body(doc, "单元测试重点验证业务规则函数,不依赖浏览器和真实数据库。要求核心规则通过率达到 100%,整体通过率不低于 95%。")
  124. h2(doc, "2. 接口测试计划")
  125. body(doc, "接口测试重点验证 Express API 的响应状态、返回结构、错误处理和敏感字段保护。")
  126. h2(doc, "3. 自动化测试计划")
  127. body(doc, "自动化测试使用 Playwright CLI 打开构建后的前端页面,检查 HTTP 状态并保存页面截图。")
  128. h2(doc, "4. 性能测试计划")
  129. body(doc, "性能测试使用 Locust 对后端接口和前端页面进行并发访问,观察平均响应时间、P95、错误率和服务稳定性。")
  130. h1(doc, "三、验收标准")
  131. table(doc, ["指标", "标准"], [
  132. ["单元测试通过率", ">= 95%,核心业务规则 100%"],
  133. ["接口测试通过率", ">= 95%"],
  134. ["自动化测试通过率", "100%"],
  135. ["性能测试错误率", "< 1%"],
  136. ["阻塞/严重缺陷", "0 个"],
  137. ])
  138. doc.save(PLAN_DOCX)
  139. def build_report():
  140. doc = setup_doc("0235711-王刚-综合测试报告", "按作业要求分为单元测试、接口测试、自动化测试、性能测试四部分")
  141. h1(doc, "一、测试执行汇总")
  142. table(doc, ["测试类型", "脚本目录", "执行数量", "通过数量", "结论"], [
  143. ["单元测试", "tests/unit_tests", "26", "26", "通过"],
  144. ["接口测试", "tests/api_tests", "5", "5", "通过"],
  145. ["自动化测试", "tests/auto_tests", "7", "7", "通过"],
  146. ["性能测试", "tests/performance_tests", "2 组 Locust 压测", "2", "通过"],
  147. ])
  148. h1(doc, "二、单元测试结果")
  149. body(doc, "执行 tests/unit_tests/run_unit_tests.ps1,调用后端 Jest 测试 quality-rules.spec.ts。结果为 1 个测试套件通过,26 条用例全部通过,通过率 100%。")
  150. table(doc, ["模块", "测试点", "结果"], [
  151. ["工单状态", "合法流转、非法流转、关闭状态处理", "通过"],
  152. ["权限隔离", "客户、客服、技术支持、管理员读取权限", "通过"],
  153. ["SLA", "截止时间、预警、超时判断", "通过"],
  154. ["附件", "类型限制、空文件、大小限制", "通过"],
  155. ])
  156. h1(doc, "三、接口测试结果")
  157. body(doc, "执行 tests/api_tests/run_api_tests.ps1,调用 Jest + Supertest 测试 health.spec.ts 和 api-contract.spec.ts。结果为 2 个测试套件通过,5 条接口用例全部通过。")
  158. table(doc, ["接口", "测试内容", "结果"], [
  159. ["GET /api/health", "服务健康检查", "通过"],
  160. ["GET /api/config/parse", "公开配置结构", "通过"],
  161. ["GET /api/config/parse", "敏感字段不暴露", "通过"],
  162. ["GET /api/unknown-resource", "404 契约", "通过"],
  163. ["POST /api/ghost", "404 消息包含方法和路径", "通过"],
  164. ])
  165. h1(doc, "四、自动化测试结果")
  166. body(doc, "执行 tests/auto_tests/run_auto_tests.ps1,使用 Playwright CLI 对前端七个关键页面进行访问验证并保存截图。结果为 7 条自动化流程全部通过。")
  167. table(doc, ["编号", "页面", "结果"], [
  168. ["AUTO-001", "/dashboard", "PASS"],
  169. ["AUTO-002", "/tickets", "PASS"],
  170. ["AUTO-003", "/sla", "PASS"],
  171. ["AUTO-004", "/knowledge", "PASS"],
  172. ["AUTO-005", "/files", "PASS"],
  173. ["AUTO-006", "/reviews", "PASS"],
  174. ["AUTO-007", "/test-plan", "PASS"],
  175. ])
  176. picture(doc, "AUTO-001-dashboard.png", "图 1 自动化测试截图:工作台页面")
  177. picture(doc, "AUTO-002-tickets.png", "图 2 自动化测试截图:工单管理页面")
  178. picture(doc, "AUTO-003-sla.png", "图 3 自动化测试截图:SLA 管理页面")
  179. h1(doc, "五、性能测试结果")
  180. body(doc, "已安装 Locust 并执行后端接口与前端页面两组性能测试。压测配置均为 30 用户、启动速率 5 用户/秒、持续 3 分钟,输出 HTML 报告、CSV 明细和性能摘要。")
  181. table(doc, ["测试对象", "请求总数", "失败率", "平均响应时间", "P95", "吞吐量", "结论"], [
  182. ["后端接口", "2656", "0%", "2.77 ms", "4 ms", "14.84 req/s", "通过"],
  183. ["前端页面", "24311", "0%", "4.29 ms", "9 ms", "135.88 req/s", "通过"],
  184. ])
  185. h1(doc, "六、缺陷与结论")
  186. body(doc, "本次补充后,测试脚本目录已经符合课程作业结构要求:unit_tests、api_tests、auto_tests、performance_tests 均有对应脚本、说明和结果证据。单元测试、接口测试、自动化测试通过率均为 100%;性能测试两组 Locust 压测均为 0 失败,满足错误率低于 1%、查询类 P95 小于 1000ms 的验收标准。")
  187. doc.save(REPORT_DOCX)
  188. if __name__ == "__main__":
  189. build_plan()
  190. build_report()
  191. print(PLAN_DOCX)
  192. print(REPORT_DOCX)