#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ HealthLink-HIS 三甲医院全流程业务逻辑测试 v2 使用实际Controller中的正确API路径 """ import requests, json, sys, os, time from datetime import datetime from typing import Dict, Any, List BASE_URL = "http://localhost:18082/healthlink-his" TOKEN = "" class TestStats: def __init__(self): self.total = self.passed = self.failed = 0 self.results = [] def record(self, module, case_id, name, passed, detail=""): self.total += 1 if passed: self.passed += 1 else: self.failed += 1 status = "✅ PASS" if passed else "❌ FAIL" self.results.append({"module":module,"case_id":case_id,"name":name,"status":status,"detail":detail}) print(f" {status} [{module}] {case_id}: {name}") if detail and not passed: print(f" → {detail}") def summary(self): print(f"\n{'='*70}") print(f"测试汇总: 总数={self.total}, 通过={self.passed}, 失败={self.failed}") if self.total > 0: print(f"通过率: {self.passed*100/self.total:.1f}%") print(f"{'='*70}") return self.failed == 0 stats = TestStats() def login(): r = requests.post(f"{BASE_URL}/login", json={"username":"admin","password":"admin123","tenantId":"1"}) return r.json().get("token","") def H(): return {"Authorization":f"Bearer {TOKEN}","Content-Type":"application/json"} def GET(p, params=None): return requests.get(f"{BASE_URL}{p}", headers=H(), params=params).json() def POST(p, d=None): return requests.post(f"{BASE_URL}{p}", headers=H(), json=d).json() def PUT(p, d=None): return requests.put(f"{BASE_URL}{p}", headers=H(), json=d).json() def chk_resp(resp, mod, cid, name, code=200, fields=None, not_empty=None): ok = True; detail = "" if resp.get("code") != code: ok = False; detail = f"code={resp.get('code')}, msg={resp.get('msg','')[:120]}" if ok and fields: for f in fields: if f not in resp: ok = False; detail=f"缺少字段: {f}"; break if ok and not_empty: for f in not_empty: v = resp.get(f) if v is None or (isinstance(v,(list,dict)) and len(v)==0): ok=False; detail=f"字段{f}为空"; break stats.record(mod, cid, name, ok, detail) return resp def chk_page(resp, mod, cid, name, min_rows=0): ok = True; detail = "" if resp.get("code") != 200: ok = False; detail = f"code={resp.get('code')}, msg={resp.get('msg','')[:120]}" else: rows = resp.get("rows", resp.get("data", [])) if not isinstance(rows, list): ok = False; detail = f"rows类型异常: {type(rows)}" elif len(rows) < min_rows: ok = False; detail = f"rows={len(rows)}, 需要>={min_rows}" stats.record(mod, cid, name, ok, detail) return resp # ============================ # 模块1: 系统登录认证 # ============================ def test_auth(): print(f"\n{'='*50}\n模块1: 系统登录认证\n{'='*50}") r = POST("/login", {"username":"admin","password":"admin123","tenantId":"1"}) chk_resp(r, "认证","1.1","登录成功-返回token", 200, ["token"], ["token"]) r = POST("/login", {"username":"admin","password":"wrong","tenantId":"1"}) chk_resp(r, "认证","1.2","错误密码-应失败", 500) r = GET("/getInfo") chk_resp(r, "认证","1.3","获取用户信息", 200, ["user","roles"], ["user"]) r = GET("/getRouters") chk_resp(r, "认证","1.4","获取路由菜单", 200, ["data"], ["data"]) # ============================ # 模块2: 门诊挂号 (实际路径: /charge-manage/register) # ============================ def test_registration(): print(f"\n{'='*50}\n模块2: 门诊挂号流程\n{'='*50}") # 2.1 挂号初始化 - 应返回选项数据 r = GET("/charge-manage/register/init") chk_resp(r, "挂号","2.1","挂号初始化", 200) # 2.2 当日挂号列表 (current-day-encounter) r = GET("/charge-manage/register/current-day-encounter") chk_resp(r, "挂号","2.2","当日挂号列表", 200) # 2.3 患者元数据查询 r = GET("/charge-manage/register/patient-metadata", {"searchKey":"测试"}) chk_resp(r, "挂号","2.3","患者元数据查询", 200) # 2.4 医生列表 r = GET("/charge-manage/register/all-doctors") chk_resp(r, "挂号","2.4","全部医生列表", 200) # ============================ # 模块3: 门诊医生站 (实际路径) # ============================ def test_doctor_station(): print(f"\n{'='*50}\n模块3: 门诊医生站\n{'='*50}") # 3.1 医生站初始化 r = GET("/doctor-station/main/init") chk_resp(r, "医生站","3.1","医生站初始化", 200) # 3.2 患者信息查询 r = GET("/doctor-station/main/patient-info") chk_resp(r, "医生站","3.2","患者信息查询", 200) # 3.3 接诊统计 r = GET("/doctor-station/main/reception-statistics") chk_resp(r, "医生站","3.3","接诊统计", 200) # 3.4 医嘱基础信息 r = GET("/doctor-station/advice/advice-base-info") chk_resp(r, "医生站","3.4","医嘱基础信息", 200) # 3.5 诊断初始化 r = GET("/doctor-station/diagnosis/init") chk_resp(r, "医生站","3.5","诊断初始化", 200) # 3.6 诊断定义分类 r = GET("/doctor-station/diagnosis/get-condition-definition-class") chk_resp(r, "医生站","3.6","诊断定义分类", 200) # 3.7 检查申请 r = GET("/doctor-station/inspection/init") chk_resp(r, "医生站","3.7","检查申请初始化", 200) # ============================ # 模块4: 收费管理 (实际路径) # ============================ def test_charge(): print(f"\n{'='*50}\n模块4: 收费管理\n{'='*50}") # 4.1 门诊收费初始化 r = GET("/charge-manage/charge/init") chk_resp(r, "收费","4.1","门诊收费初始化", 200) # 4.2 门诊收费-患者列表 r = GET("/charge-manage/charge/encounter-patient-page") chk_resp(r, "收费","4.2","收费患者列表", 200) # 4.3 退费初始化 r = GET("/charge-manage/refund/init") chk_resp(r, "收费","4.3","退费初始化", 200) # 4.4 退费患者列表 r = GET("/charge-manage/refund/encounter-patient-page") chk_resp(r, "收费","4.4","退费患者列表", 200) # 4.5 住院收费初始化 r = GET("/charge-manage/inpatient-charge/init") chk_resp(r, "收费","4.5","住院收费初始化", 200) # 4.6 住院收费-患者列表 r = GET("/charge-manage/inpatient-charge/encounter-patient-page") chk_resp(r, "收费","4.6","住院收费患者列表", 200) # 4.7 定价-患者信息 r = GET("/charge-manage/pricing/patient-info") chk_resp(r, "收费","4.7","定价患者信息", 200) # ============================ # 模块5: 住院管理 (实际路径) # ============================ def test_inpatient(): print(f"\n{'='*50}\n模块5: 住院管理\n{'='*50}") # 5.1 患者主页初始化 r = GET("/patient-home-manage/init") chk_resp(r, "住院","5.1","患者主页初始化", 200) # 5.2 空床查询 r = GET("/patient-home-manage/empty-bed") chk_resp(r, "住院","5.2","空床查询", 200) # 5.3 科室统计 r = GET("/patient-home-manage/caty") chk_resp(r, "住院","5.3","科室统计", 200) # 5.4 押金初始化 r = GET("/deposit-manage/init") chk_resp(r, "住院","5.4","押金初始化", 200) # 5.5 入院登记-按医生 r = GET("/inhospitalmanage/register/ward-list") chk_resp(r, "住院","5.5","入院登记-病区列表", 200) # 5.6 入院登记-床位数 r = GET("/inhospitalmanage/register/beds-num") chk_resp(r, "住院","5.6","入院登记-床位数", 200) # 5.7 入院登记-患者信息 r = GET("/inhospitalmanage/register/patient-info") chk_resp(r, "住院","5.7","入院登记-患者信息", 200) # ============================ # 模块6: 护理管理 # ============================ def test_nursing(): print(f"\n{'='*50}\n模块6: 护理管理\n{'='*50}") # 6.1 护理评估列表 r = GET("/nursing-assessment-enhanced/page", {"pageNum":1,"pageSize":10}) chk_page(r, "护理","6.1","护理评估列表") # 6.2 护理评估统计 r = GET("/nursing-assessment-enhanced/stats") chk_resp(r, "护理","6.2","护理评估统计", 200) # 6.3 Braden评估 - 验证分数计算 braden = {"patientName":"测试患者甲","encounterId":"6006", "itemScores":json.dumps({"sensation":2,"moisture":2,"activity":1,"mobility":2,"nutrition":3,"friction":2}), "detail":"压疮高危"} r = POST("/nursing-assessment-enhanced/braden/assess", braden) chk_resp(r, "护理","6.3","Braden评估-成功", 200) # 6.4 Morse跌倒评估 morse = {"patientName":"测试患者乙","encounterId":"6007", "itemScores":json.dumps({"history":15,"diagnosis":0,"ambulation":15,"iv":20,"gait":0,"mental":15}), "detail":"跌倒高危"} r = POST("/nursing-assessment-enhanced/morse/assess", morse) chk_resp(r, "护理","6.4","Morse评估-成功", 200) # 6.5 体征记录查询 r = GET("/vital-signs/record-search") chk_resp(r, "护理","6.5","体征记录查询", 200) # 6.6 体征图表 r = GET("/vital-signs-chart/page", {"pageNum":1,"pageSize":10}) chk_page(r, "护理","6.6","体征图表") # 6.7 护理执行列表 r = GET("/nurse-station/advice-process/page", {"pageNum":1,"pageSize":10}) chk_page(r, "护理","6.7","护理执行列表") # 6.8 护理质量 r = GET("/nursing-quality/page", {"pageNum":1,"pageSize":10}) chk_page(r, "护理","6.8","护理质量指标") # ============================ # 模块7: 检验检查 # ============================ def test_inspection(): print(f"\n{'='*50}\n模块7: 检验检查\n{'='*50}") endpoints = [ ("7.1","标本采集","/inspection/collection/page"), ("7.2","检验观察","/inspection/observation/page"), ("7.3","标本定义","/inspection/specimen/page"), ("7.4","LIS配置","/inspection/lisConfig/page"), ("7.5","仪器管理","/inspection/instrument/page"), ("7.6","检验结果","/inspection/laboratory/page"), ("7.7","参考范围","/lab-ref-range/page"), ("7.8","检查申请","/exam/apply/page"), ] for cid,name,path in endpoints: r = GET(path, {"pageNum":1,"pageSize":10}) chk_page(r, "检验", cid, name) # ============================ # 模块8: 影像检查 # ============================ def test_radiology(): print(f"\n{'='*50}\n模块8: 影像检查\n{'='*50}") r = GET("/radiology-image/page", {"pageNum":1,"pageSize":10}) chk_page(r, "影像","8.1","影像列表") r = GET("/radiology-enhanced/page", {"pageNum":1,"pageSize":10}) chk_page(r, "影像","8.2","影像增强") r = GET("/radiology-comparison/page", {"pageNum":1,"pageSize":10}) chk_page(r, "影像","8.3","影像对比") r = GET("/reconstruction/page", {"pageNum":1,"pageSize":10}) chk_page(r, "影像","8.4","3D重建") # ============================ # 模块9: 手术麻醉 # ============================ def test_surgery(): print(f"\n{'='*50}\n模块9: 手术麻醉\n{'='*50}") r = GET("/clinical-manage/surgery/page", {"pageNum":1,"pageSize":10}) chk_page(r, "手术","9.1","手术列表") r = GET("/clinical-manage/surgery-schedule/page", {"pageNum":1,"pageSize":10}) chk_page(r, "手术","9.2","手术排程") r = GET("/preop-discussion/page", {"pageNum":1,"pageSize":10}) chk_page(r, "手术","9.3","术前讨论") r = GET("/surgery-safety-check/page", {"pageNum":1,"pageSize":10}) chk_page(r, "手术","9.4","安全核查") r = GET("/api/v1/anesthesia/page", {"pageNum":1,"pageSize":10}) chk_page(r, "麻醉","9.5","麻醉记录") r = GET("/anesthesia-enhanced/page", {"pageNum":1,"pageSize":10}) chk_page(r, "麻醉","9.6","麻醉增强") # ============================ # 模块10: 院感管理 # ============================ def test_infection(): print(f"\n{'='*50}\n模块10: 院感管理\n{'='*50}") r = GET("/infection-enhanced/surveillance/page", {"pageNum":1,"pageSize":10}) chk_page(r, "院感","10.1","院感监测") r = GET("/infection-enhanced/warning/page", {"pageNum":1,"pageSize":10}) chk_page(r, "院感","10.2","院感预警") r = GET("/infection-enhanced/resistance/page", {"pageNum":1,"pageSize":10}) chk_page(r, "院感","10.3","耐药监测") r = GET("/infection-enhanced/exposure/page", {"pageNum":1,"pageSize":10}) chk_page(r, "院感","10.4","职业暴露") r = GET("/infection-enhanced/hand-hygiene/page", {"pageNum":1,"pageSize":10}) chk_page(r, "院感","10.5","手卫生") r = GET("/infection-enhanced/environment/page", {"pageNum":1,"pageSize":10}) chk_page(r, "院感","10.6","环境监测") # ============================ # 模块11: 质量管理 # ============================ def test_quality(): print(f"\n{'='*50}\n模块11: 质量管理\n{'='*50}") r = GET("/quality-enhanced/runtime/page", {"pageNum":1,"pageSize":10}) chk_page(r, "质控","11.1","运行质控") r = GET("/api/v1/emr-quality/page", {"pageNum":1,"pageSize":10}) chk_page(r, "质控","11.2","终末质控") r = GET("/quality-enhanced/statistics/page", {"pageNum":1,"pageSize":10}) chk_page(r, "质控","11.3","质量统计") # ============================ # 模块12: 中医管理 # ============================ def test_tcm(): print(f"\n{'='*50}\n模块12: 中医管理\n{'='*50}") r = GET("/api/v1/tcm/constitution/page", {"pageNum":1,"pageSize":10}) chk_page(r, "中医","12.1","中医体质", min_rows=1) r = GET("/api/v1/tcm/prescriptions", {"pageNum":1,"pageSize":10}) rows = r.get("rows", r.get("data", [])) ok = isinstance(rows, list) and len(rows) >= 3 stats.record("中医","12.2","方剂列表>=3个", ok, "" if ok else f"实际={len(rows)}") r = GET("/api/v1/tcm/statistics") chk_resp(r, "中医","12.3","中医统计", 200) # ============================ # 模块13: 会诊管理 # ============================ def test_consultation(): print(f"\n{'='*50}\n模块13: 会诊管理\n{'='*50}") r = GET("/consultation/page", {"pageNum":1,"pageSize":10}) chk_page(r, "会诊","13.1","会诊记录") r = GET("/cross-module/consult-feedback/page", {"pageNum":1,"pageSize":10}) chk_page(r, "会诊","13.2","会诊反馈") r = GET("/cross-module/consulttimeout/page", {"pageNum":1,"pageSize":10}) chk_page(r, "会诊","13.3","会诊超时") # ============================ # 模块14: 临床路径 # ============================ def test_pathway(): print(f"\n{'='*50}\n模块14: 临床路径\n{'='*50}") r = GET("/clinical-pathway/page", {"pageNum":1,"pageSize":10}) rows = r.get("rows", r.get("data", [])) ok = isinstance(rows, list) and len(rows) >= 2 stats.record("路径","14.1","临床路径>=2条", ok, "" if ok else f"实际={len(rows)}") # ============================ # 模块15: 危急值 # ============================ def test_critical(): print(f"\n{'='*50}\n模块15: 危急值管理\n{'='*50}") r = GET("/api/v1/critical-value/page", {"pageNum":1,"pageSize":10}) chk_page(r, "危急值","15.1","危急值列表") # ============================ # 模块16: 处方点评 # ============================ def test_review(): print(f"\n{'='*50}\n模块16: 处方点评\n{'='*50}") r = GET("/api/v1/review/plans", {"pageNum":1,"pageSize":10}) chk_page(r, "点评","16.1","点评计划") r = GET("/api/v1/review/records", {"pageNum":1,"pageSize":10}) chk_page(r, "点评","16.2","点评记录") r = GET("/api/v1/review/statistics") chk_resp(r, "点评","16.3","点评统计", 200) # ============================ # 模块17: 合理用药 # ============================ def test_rational_drug(): print(f"\n{'='*50}\n模块17: 合理用药\n{'='*50}") r = GET("/api/v1/rational-drug/page", {"pageNum":1,"pageSize":10}) chk_page(r, "用药","17.1","合理用药") r = GET("/api/v1/rational-drug/interaction/page", {"pageNum":1,"pageSize":10}) chk_page(r, "用药","17.2","相互作用") r = GET("/api/v1/rational-drug/statistics") chk_resp(r, "用药","17.3","用药统计", 200) r = GET("/api/v1/rational-drug/audit-log", {"pageNum":1,"pageSize":10}) chk_page(r, "用药","17.4","审计日志") # ============================ # 模块18: 药品追溯 # ============================ def test_drug_trace(): print(f"\n{'='*50}\n模块18: 药品追溯\n{'='*50}") r = GET("/drugtrace/page", {"pageNum":1,"pageSize":10}) chk_page(r, "追溯","18.1","药品追溯") # ============================ # 模块19: EMPI # ============================ def test_empi(): print(f"\n{'='*50}\n模块19: EMPI主索引\n{'='*50}") r = GET("/api/v1/empi/page", {"pageNum":1,"pageSize":10}) chk_page(r, "EMPI","19.1","EMPI索引") # ============================ # 模块20: ESB # ============================ def test_esb(): print(f"\n{'='*50}\n模块20: ESB数据集成\n{'='*50}") r = GET("/esb/message/page", {"pageNum":1,"pageSize":10}) chk_page(r, "ESB","20.1","ESB消息") r = GET("/esb/registry/page", {"pageNum":1,"pageSize":10}) chk_page(r, "ESB","20.2","ESB服务注册") # ============================ # 模块21: CA签名 # ============================ def test_ca(): print(f"\n{'='*50}\n模块21: 电子签名\n{'='*50}") r = GET("/api/v1/ca-signature/page", {"pageNum":1,"pageSize":10}) chk_page(r, "CA","21.1","CA签名") r = GET("/api/v1/ca-signature/statistics") chk_resp(r, "CA","21.2","CA签名统计", 200) # ============================ # 模块22: 病案管理 # ============================ def test_mr(): print(f"\n{'='*50}\n模块22: 病案管理\n{'='*50}") r = GET("/api/v1/mr-homepage/page", {"pageNum":1,"pageSize":10}) chk_page(r, "病案","22.1","病案首页") # ============================ # 模块23: 随访 # ============================ def test_followup(): print(f"\n{'='*50}\n模块23: 随访管理\n{'='*50}") r = GET("/followup/page", {"pageNum":1,"pageSize":10}) chk_page(r, "随访","23.1","随访计划") # ============================ # 模块24: 知情同意 # ============================ def test_consent(): print(f"\n{'='*50}\n模块24: 知情同意\n{'='*50}") r = GET("/informed-consent/page", {"pageNum":1,"pageSize":10}) chk_page(r, "知情","24.1","知情同意") # ============================ # 模块25: 消毒供应 # ============================ def test_cssd(): print(f"\n{'='*50}\n模块25: 消毒供应\n{'='*50}") r = GET("/cssd/page", {"pageNum":1,"pageSize":10}) chk_page(r, "CSSD","25.1","消毒追溯") # ============================ # 模块26: 急诊 # ============================ def test_emergency(): print(f"\n{'='*50}\n模块26: 急诊管理\n{'='*50}") r = GET("/emergency/page", {"pageNum":1,"pageSize":10}) chk_page(r, "急诊","26.1","急诊记录") r = GET("/triage/queue/page", {"pageNum":1,"pageSize":10}) chk_page(r, "急诊","26.2","分诊排队") # ============================ # 模块27: 医保 # ============================ def test_insurance(): print(f"\n{'='*50}\n模块27: 医保管理\n{'='*50}") r = GET("/yb-request/page", {"pageNum":1,"pageSize":10}) chk_page(r, "医保","27.1","医保请求") # ============================ # 模块28: 抗菌药物 # ============================ def test_antibiotic(): print(f"\n{'='*50}\n模块28: 抗菌药物\n{'='*50}") r = GET("/api/v1/antibiotic/page", {"pageNum":1,"pageSize":10}) chk_page(r, "抗菌","28.1","抗菌药物") # ============================ # 模块29: DRG分析 # ============================ def test_drg(): print(f"\n{'='*50}\n模块29: DRG分析\n{'='*50}") r = GET("/drg-analysis/page", {"pageNum":1,"pageSize":10}) chk_page(r, "DRG","29.1","DRG分析") r = GET("/mr-drg/page", {"pageNum":1,"pageSize":10}) chk_page(r, "DRG","29.2","DRG分组") # ============================ # 模块30: 经营分析 # ============================ def test_analytics(): print(f"\n{'='*50}\n模块30: 经营分析\n{'='*50}") r = GET("/business-analytics/page", {"pageNum":1,"pageSize":10}) chk_page(r, "经营","30.1","经营分析") # ============================ # 模块31: 系统管理 # ============================ def test_system(): print(f"\n{'='*50}\n模块31: 系统管理\n{'='*50}") r = GET("/dict-dictionary/definition/page", {"pageNum":1,"pageSize":10}) chk_page(r, "系统","31.1","字典定义") r = GET("/system/user/list") data = r.get("data",[]) ok = isinstance(data, list) and len(data) > 0 stats.record("系统","31.2","用户列表非空", ok, "" if ok else "用户列表为空") r = GET("/system/role/list") data = r.get("data",[]) ok = isinstance(data, list) and len(data) > 0 stats.record("系统","31.3","角色列表非空", ok, "" if ok else "角色列表为空") r = GET("/system/menu/list") data = r.get("data",[]) ok = isinstance(data, list) and len(data) > 50 stats.record("系统","31.4","菜单>50条", ok, "" if ok else f"实际={len(data) if isinstance(data,list) else 'N/A'}") r = GET("/system/dept/list") data = r.get("data",[]) ok = isinstance(data, list) and len(data) > 5 stats.record("系统","31.5","部门>5个", ok, "" if ok else f"实际={len(data) if isinstance(data,list) else 'N/A'}") # ============================ # 模块32: 药房管理 # ============================ def test_pharmacy(): print(f"\n{'='*50}\n模块32: 药房管理\n{'='*50}") r = GET("/pharmacy-stock-alert/page", {"pageNum":1,"pageSize":10}) chk_page(r, "药房","32.1","库存预警") r = GET("/pharmacy-manage/western-medicine-dispense/page", {"pageNum":1,"pageSize":10}) chk_page(r, "药房","32.2","西药发药") r = GET("/pharmacy-manage/return-medicine/page", {"pageNum":1,"pageSize":10}) chk_page(r, "药房","32.3","退药管理") # ============================ # 模块33: 报表管理 # ============================ def test_reports(): print(f"\n{'='*50}\n模块33: 报表管理\n{'='*50}") r = GET("/report-manage/register/page", {"pageNum":1,"pageSize":10}) chk_page(r, "报表","33.1","挂号报表") r = GET("/report-manage/charge/page", {"pageNum":1,"pageSize":10}) chk_page(r, "报表","33.2","收费报表") r = GET("/report-manage/report-statistics/page", {"pageNum":1,"pageSize":10}) chk_page(r, "报表","33.3","经营统计") # ============================ # 主入口 # ============================ if __name__ == "__main__": print(f"{'='*70}") print("HealthLink-HIS 三甲医院全流程业务逻辑测试 v2") print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"测试环境: {BASE_URL}") print(f"{'='*70}") print("\n>>> 登录系统...") TOKEN = login() if not TOKEN: print("❌ 登录失败!"); sys.exit(1) print("✅ 登录成功") for fn in [test_auth, test_registration, test_doctor_station, test_charge, test_inpatient, test_nursing, test_inspection, test_radiology, test_surgery, test_infection, test_quality, test_tcm, test_consultation, test_pathway, test_critical, test_review, test_rational_drug, test_drug_trace, test_empi, test_esb, test_ca, test_mr, test_followup, test_consent, test_cssd, test_emergency, test_insurance, test_antibiotic, test_drg, test_analytics, test_system, test_pharmacy, test_reports]: try: fn() except Exception as e: print(f" ❌ 异常: {fn.__name__}: {e}") stats.record("异常", fn.__name__, "执行异常", False, str(e)) ok = stats.summary() os.makedirs("MD/test/reports", exist_ok=True) rp = f"MD/test/reports/biz_test_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md" with open(rp, "w") as f: f.write(f"# 业务逻辑测试报告\n\n") f.write(f"**时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n") f.write(f"**环境**: {BASE_URL}\n\n") f.write(f"## 汇总\n\n- 总数: {stats.total}\n- 通过: {stats.passed}\n- 失败: {stats.failed}\n- 通过率: {stats.passed*100/stats.total:.1f}%\n\n" if stats.total else "") f.write("## 详细\n\n| 模块 | 编号 | 测试项 | 状态 | 说明 |\n|------|------|--------|------|------|\n") for r in stats.results: f.write(f"| {r['module']} | {r['case_id']} | {r['name']} | {r['status']} | {r['detail']} |\n") print(f"\n📄 报告: {rp}") sys.exit(0 if ok else 1)