|
|
@@ -0,0 +1,225 @@
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+from pathlib import Path
|
|
|
+
|
|
|
+from docx import Document
|
|
|
+from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
|
|
|
+from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
|
+from docx.oxml import OxmlElement
|
|
|
+from docx.oxml.ns import qn
|
|
|
+from docx.shared import Inches, Pt, RGBColor
|
|
|
+
|
|
|
+
|
|
|
+ROOT = Path(__file__).resolve().parent
|
|
|
+PLAN_DOCX = ROOT / "0235711-王刚-综合测试计划.docx"
|
|
|
+REPORT_DOCX = ROOT / "0235711-王刚-综合测试报告.docx"
|
|
|
+AUTO_ARTIFACTS = ROOT / "tests" / "auto_tests" / "artifacts"
|
|
|
+
|
|
|
+
|
|
|
+def format_run(run, font="宋体", size=10.5, bold=False, color=None):
|
|
|
+ run.font.name = font
|
|
|
+ run._element.rPr.rFonts.set(qn("w:eastAsia"), font)
|
|
|
+ run.font.size = Pt(size)
|
|
|
+ run.bold = bold
|
|
|
+ if color:
|
|
|
+ run.font.color.rgb = RGBColor(*color)
|
|
|
+
|
|
|
+
|
|
|
+def setup_doc(title, subtitle):
|
|
|
+ doc = Document()
|
|
|
+ section = doc.sections[0]
|
|
|
+ section.top_margin = Inches(0.8)
|
|
|
+ section.bottom_margin = Inches(0.8)
|
|
|
+ section.left_margin = Inches(0.85)
|
|
|
+ section.right_margin = Inches(0.85)
|
|
|
+ doc.styles["Normal"].font.name = "宋体"
|
|
|
+ doc.styles["Normal"]._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")
|
|
|
+ doc.styles["Normal"].font.size = Pt(10.5)
|
|
|
+
|
|
|
+ p = doc.add_paragraph()
|
|
|
+ p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
+ r = p.add_run(title)
|
|
|
+ format_run(r, "微软雅黑", 20, True, (17, 24, 39))
|
|
|
+
|
|
|
+ p = doc.add_paragraph()
|
|
|
+ p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
+ r = p.add_run(subtitle)
|
|
|
+ format_run(r, "微软雅黑", 11, False, (82, 96, 113))
|
|
|
+ doc.add_paragraph("")
|
|
|
+ return doc
|
|
|
+
|
|
|
+
|
|
|
+def h1(doc, text):
|
|
|
+ p = doc.add_paragraph()
|
|
|
+ p.paragraph_format.space_before = Pt(10)
|
|
|
+ p.paragraph_format.space_after = Pt(6)
|
|
|
+ r = p.add_run(text)
|
|
|
+ format_run(r, "微软雅黑", 15, True, (23, 78, 166))
|
|
|
+
|
|
|
+
|
|
|
+def h2(doc, text):
|
|
|
+ p = doc.add_paragraph()
|
|
|
+ p.paragraph_format.space_before = Pt(7)
|
|
|
+ p.paragraph_format.space_after = Pt(4)
|
|
|
+ r = p.add_run(text)
|
|
|
+ format_run(r, "微软雅黑", 12, True, (17, 24, 39))
|
|
|
+
|
|
|
+
|
|
|
+def body(doc, text):
|
|
|
+ p = doc.add_paragraph(text)
|
|
|
+ p.paragraph_format.first_line_indent = Pt(21)
|
|
|
+ p.paragraph_format.line_spacing = 1.25
|
|
|
+ p.paragraph_format.space_after = Pt(4)
|
|
|
+ for r in p.runs:
|
|
|
+ format_run(r)
|
|
|
+
|
|
|
+
|
|
|
+def border(cell):
|
|
|
+ tc_pr = cell._tc.get_or_add_tcPr()
|
|
|
+ borders = tc_pr.first_child_found_in("w:tcBorders")
|
|
|
+ if borders is None:
|
|
|
+ borders = OxmlElement("w:tcBorders")
|
|
|
+ tc_pr.append(borders)
|
|
|
+ for edge in ("top", "left", "bottom", "right"):
|
|
|
+ element = borders.find(qn(f"w:{edge}"))
|
|
|
+ if element is None:
|
|
|
+ element = OxmlElement(f"w:{edge}")
|
|
|
+ borders.append(element)
|
|
|
+ element.set(qn("w:val"), "single")
|
|
|
+ element.set(qn("w:sz"), "6")
|
|
|
+ element.set(qn("w:space"), "0")
|
|
|
+ element.set(qn("w:color"), "D8DEE8")
|
|
|
+
|
|
|
+
|
|
|
+def shade(cell, fill):
|
|
|
+ tc_pr = cell._tc.get_or_add_tcPr()
|
|
|
+ shd = OxmlElement("w:shd")
|
|
|
+ shd.set(qn("w:fill"), fill)
|
|
|
+ tc_pr.append(shd)
|
|
|
+
|
|
|
+
|
|
|
+def cell_text(cell, text, bold=False, color=None):
|
|
|
+ cell.text = ""
|
|
|
+ p = cell.paragraphs[0]
|
|
|
+ p.alignment = WD_ALIGN_PARAGRAPH.CENTER if len(str(text)) <= 12 else WD_ALIGN_PARAGRAPH.LEFT
|
|
|
+ r = p.add_run(str(text))
|
|
|
+ format_run(r, size=9.5, bold=bold, color=color)
|
|
|
+ cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
|
|
+ border(cell)
|
|
|
+
|
|
|
+
|
|
|
+def table(doc, headers, rows):
|
|
|
+ t = doc.add_table(rows=1, cols=len(headers))
|
|
|
+ t.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
|
+ t.autofit = True
|
|
|
+ for i, header in enumerate(headers):
|
|
|
+ shade(t.rows[0].cells[i], "EAF1FF")
|
|
|
+ cell_text(t.rows[0].cells[i], header, True, (23, 78, 166))
|
|
|
+ for row in rows:
|
|
|
+ cells = t.add_row().cells
|
|
|
+ for i, val in enumerate(row):
|
|
|
+ cell_text(cells[i], val)
|
|
|
+ doc.add_paragraph("")
|
|
|
+
|
|
|
+
|
|
|
+def caption(doc, text):
|
|
|
+ p = doc.add_paragraph()
|
|
|
+ p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
+ r = p.add_run(text)
|
|
|
+ format_run(r, size=9, color=(82, 96, 113))
|
|
|
+
|
|
|
+
|
|
|
+def picture(doc, filename, text):
|
|
|
+ path = AUTO_ARTIFACTS / filename
|
|
|
+ if path.exists():
|
|
|
+ p = doc.add_paragraph()
|
|
|
+ p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
+ p.add_run().add_picture(str(path), width=Inches(6.1))
|
|
|
+ caption(doc, text)
|
|
|
+
|
|
|
+
|
|
|
+def build_plan():
|
|
|
+ doc = setup_doc("0235711-王刚-综合测试计划", "ServicePilot 智能客户工单与 SLA 履约管理平台")
|
|
|
+ h1(doc, "一、测试范围")
|
|
|
+ body(doc, "本项目测试对象包括前端 Angular 页面、后端 Node.js + TypeScript + Express 接口、Parse 数据权限、工单管理、SLA 管理、知识库、附件管理、满意度评价和数据看板。")
|
|
|
+ table(doc, ["测试类型", "目录", "工具", "数量目标", "覆盖范围"], [
|
|
|
+ ["单元测试", "tests/unit_tests", "Jest", "26 条", "状态流转、权限、SLA、附件规则"],
|
|
|
+ ["接口测试", "tests/api_tests", "Jest + Supertest", "5 条", "health、Parse 配置、404 契约"],
|
|
|
+ ["自动化测试", "tests/auto_tests", "Playwright CLI", "7 条", "七个前端关键页面"],
|
|
|
+ ["性能测试", "tests/performance_tests", "Locust", "2 个脚本", "后端接口、前端页面"],
|
|
|
+ ])
|
|
|
+ h1(doc, "二、四类测试计划")
|
|
|
+ h2(doc, "1. 单元测试计划")
|
|
|
+ body(doc, "单元测试重点验证业务规则函数,不依赖浏览器和真实数据库。要求核心规则通过率达到 100%,整体通过率不低于 95%。")
|
|
|
+ h2(doc, "2. 接口测试计划")
|
|
|
+ body(doc, "接口测试重点验证 Express API 的响应状态、返回结构、错误处理和敏感字段保护。")
|
|
|
+ h2(doc, "3. 自动化测试计划")
|
|
|
+ body(doc, "自动化测试使用 Playwright CLI 打开构建后的前端页面,检查 HTTP 状态并保存页面截图。")
|
|
|
+ h2(doc, "4. 性能测试计划")
|
|
|
+ body(doc, "性能测试使用 Locust 对后端接口和前端页面进行并发访问,观察平均响应时间、P95、错误率和服务稳定性。")
|
|
|
+ h1(doc, "三、验收标准")
|
|
|
+ table(doc, ["指标", "标准"], [
|
|
|
+ ["单元测试通过率", ">= 95%,核心业务规则 100%"],
|
|
|
+ ["接口测试通过率", ">= 95%"],
|
|
|
+ ["自动化测试通过率", "100%"],
|
|
|
+ ["性能测试错误率", "< 1%"],
|
|
|
+ ["阻塞/严重缺陷", "0 个"],
|
|
|
+ ])
|
|
|
+ doc.save(PLAN_DOCX)
|
|
|
+
|
|
|
+
|
|
|
+def build_report():
|
|
|
+ doc = setup_doc("0235711-王刚-综合测试报告", "按作业要求分为单元测试、接口测试、自动化测试、性能测试四部分")
|
|
|
+ h1(doc, "一、测试执行汇总")
|
|
|
+ table(doc, ["测试类型", "脚本目录", "执行数量", "通过数量", "结论"], [
|
|
|
+ ["单元测试", "tests/unit_tests", "26", "26", "通过"],
|
|
|
+ ["接口测试", "tests/api_tests", "5", "5", "通过"],
|
|
|
+ ["自动化测试", "tests/auto_tests", "7", "7", "通过"],
|
|
|
+ ["性能测试", "tests/performance_tests", "2 个 Locust 脚本", "待执行", "脚本已补充"],
|
|
|
+ ])
|
|
|
+ h1(doc, "二、单元测试结果")
|
|
|
+ body(doc, "执行 tests/unit_tests/run_unit_tests.ps1,调用后端 Jest 测试 quality-rules.spec.ts。结果为 1 个测试套件通过,26 条用例全部通过,通过率 100%。")
|
|
|
+ table(doc, ["模块", "测试点", "结果"], [
|
|
|
+ ["工单状态", "合法流转、非法流转、关闭状态处理", "通过"],
|
|
|
+ ["权限隔离", "客户、客服、技术支持、管理员读取权限", "通过"],
|
|
|
+ ["SLA", "截止时间、预警、超时判断", "通过"],
|
|
|
+ ["附件", "类型限制、空文件、大小限制", "通过"],
|
|
|
+ ])
|
|
|
+ h1(doc, "三、接口测试结果")
|
|
|
+ body(doc, "执行 tests/api_tests/run_api_tests.ps1,调用 Jest + Supertest 测试 health.spec.ts 和 api-contract.spec.ts。结果为 2 个测试套件通过,5 条接口用例全部通过。")
|
|
|
+ table(doc, ["接口", "测试内容", "结果"], [
|
|
|
+ ["GET /api/health", "服务健康检查", "通过"],
|
|
|
+ ["GET /api/config/parse", "公开配置结构", "通过"],
|
|
|
+ ["GET /api/config/parse", "敏感字段不暴露", "通过"],
|
|
|
+ ["GET /api/unknown-resource", "404 契约", "通过"],
|
|
|
+ ["POST /api/ghost", "404 消息包含方法和路径", "通过"],
|
|
|
+ ])
|
|
|
+ h1(doc, "四、自动化测试结果")
|
|
|
+ body(doc, "执行 tests/auto_tests/run_auto_tests.ps1,使用 Playwright CLI 对前端七个关键页面进行访问验证并保存截图。结果为 7 条自动化流程全部通过。")
|
|
|
+ table(doc, ["编号", "页面", "结果"], [
|
|
|
+ ["AUTO-001", "/dashboard", "PASS"],
|
|
|
+ ["AUTO-002", "/tickets", "PASS"],
|
|
|
+ ["AUTO-003", "/sla", "PASS"],
|
|
|
+ ["AUTO-004", "/knowledge", "PASS"],
|
|
|
+ ["AUTO-005", "/files", "PASS"],
|
|
|
+ ["AUTO-006", "/reviews", "PASS"],
|
|
|
+ ["AUTO-007", "/test-plan", "PASS"],
|
|
|
+ ])
|
|
|
+ picture(doc, "AUTO-001-dashboard.png", "图 1 自动化测试截图:工作台页面")
|
|
|
+ picture(doc, "AUTO-002-tickets.png", "图 2 自动化测试截图:工单管理页面")
|
|
|
+ picture(doc, "AUTO-003-sla.png", "图 3 自动化测试截图:SLA 管理页面")
|
|
|
+ h1(doc, "五、性能测试脚本说明")
|
|
|
+ body(doc, "性能测试目录已补充 Locust 脚本,包括 locustfile_backend.py 和 locustfile_frontend.py。由于当前机器未检测到 Locust 命令,本次未生成真实性能报告;安装 Locust 后可运行 run_backend_performance.ps1 和 run_frontend_performance.ps1 生成 HTML 报告。")
|
|
|
+ table(doc, ["脚本", "测试目标", "默认并发配置"], [
|
|
|
+ ["locustfile_backend.py", "后端 health、Parse 配置、404 契约", "30 用户,启动速率 5,持续 3 分钟"],
|
|
|
+ ["locustfile_frontend.py", "前端 dashboard、tickets、sla、knowledge、files、reviews、test-plan", "30 用户,启动速率 5,持续 3 分钟"],
|
|
|
+ ])
|
|
|
+ h1(doc, "六、缺陷与结论")
|
|
|
+ body(doc, "本次补充后,测试脚本目录已经符合课程作业结构要求:unit_tests、api_tests、auto_tests、performance_tests 均有对应脚本和说明。已执行的单元测试、接口测试、自动化测试通过率均为 100%;性能测试脚本已具备,待安装 Locust 后执行。")
|
|
|
+ doc.save(REPORT_DOCX)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ build_plan()
|
|
|
+ build_report()
|
|
|
+ print(PLAN_DOCX)
|
|
|
+ print(REPORT_DOCX)
|