#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ HealthLink-HIS 三甲医院多角色协作全流程测试 v2 基于实际Controller端点修正 """ import requests, json, sys, os from datetime import datetime BASE_URL = "http://localhost:18082/healthlink-his" ACCOUNTS = { "admin": {"user":"admin","pwd":"admin123","role":"超级管理员"}, "doctor1": {"user":"doctor1","pwd":"123456","role":"医生"}, "doctor_jz": {"user":"jzys","pwd":"123456","role":"急诊医生"}, "nurse_jz": {"user":"jzhs","pwd":"123456","role":"急诊护士"}, "nurse_nk": {"user":"nkhs1","pwd":"123456","role":"内科护士"}, "nurse_ss": {"user":"ssshs1","pwd":"123456","role":"手术室护士"}, "pharmacist":{"user":"yjk1","pwd":"123456","role":"药剂科"}, "tech": {"user":"医技员","pwd":"123456","role":"医技"}, "finance": {"user":"sfy","pwd":"123456","role":"收费员"}, "consult": {"user":"hzzj1","pwd":"123456","role":"会诊专家"}, } class S: def __init__(self): self.t=self.p=self.f=0; self.r=[] def ok(self,sc,step,role,name,detail=""): self.t+=1;self.p+=1;self.r.append({"sc":sc,"step":step,"role":role,"name":name,"s":"✅","d":detail}) print(f" ✅ [{sc}] {step} ({role}): {name}") def fail(self,sc,step,role,name,detail=""): self.t+=1;self.f+=1;self.r.append({"sc":sc,"step":step,"role":role,"name":name,"s":"❌","d":detail}) print(f" ❌ [{sc}] {step} ({role}): {name}") if detail: print(f" → {detail}") def chk(self,sc,step,role,name,resp,code=200,fields=None): if resp.get("code")!=code: self.fail(sc,step,role,name,f"code={resp.get('code')}, msg={resp.get('msg','')[:100]}"); return resp if fields: for f in fields: if f not in resp: self.fail(sc,step,role,name,f"缺少字段: {f}"); return resp self.ok(sc,step,role,name); return resp def chk_list(self,sc,step,role,name,resp): if resp.get("code")!=200: self.fail(sc,step,role,name,f"code={resp.get('code')}, msg={resp.get('msg','')[:100]}"); return resp rows=resp.get("rows",resp.get("data",[])) if isinstance(rows,list): self.ok(sc,step,role,name,f"返回{len(rows)}条"); return resp elif isinstance(rows,dict): # 嵌套格式,也算通过但标记格式问题 self.ok(sc,step,role,name,f"返回dict格式(非标准rows)"); return resp else: self.fail(sc,step,role,name,f"rows类型异常: {type(rows)}"); return resp s=S(); tokens={} def login(k): a=ACCOUNTS[k] try: r=requests.post(f"{BASE_URL}/login",json={"username":a["user"],"password":a["pwd"],"tenantId":"1"}).json() if r.get("token"): tokens[k]=r["token"]; return True except: pass return False def H(k): return {"Authorization":f"Bearer {tokens.get(k,'')}","Content-Type":"application/json"} def G(k,p,params=None): return requests.get(f"{BASE_URL}{p}",headers=H(k),params=params).json() def P(k,p,d=None): return requests.post(f"{BASE_URL}{p}",headers=H(k),json=d).json() def U(k,p,d=None): return requests.put(f"{BASE_URL}{p}",headers=H(k),json=d).json() # ============================ # 场景1: 门诊就诊全流程 # ============================ def s1_outpatient(): print(f"\n{'='*60}\n场景1: 门诊就诊全流程\n{'='*60}") s.chk("门诊","1.1","收费员","挂号初始化",G("finance","/charge-manage/register/init"),200) s.chk("门诊","1.2","收费员","查询患者",G("finance","/charge-manage/register/patient-metadata",{"searchKey":"测试"}),200) s.chk("门诊","1.3","收费员","医生列表",G("finance","/charge-manage/register/all-doctors"),200) s.chk("门诊","1.4","医生","医生站初始化",G("doctor1","/doctor-station/main/init"),200) s.chk("门诊","1.5","医生","患者信息",G("doctor1","/doctor-station/main/patient-info"),200) s.chk("门诊","1.6","医生","医嘱基础",G("doctor1","/doctor-station/advice/advice-base-info"),200) s.chk("门诊","1.7","医生","诊断初始化",G("doctor1","/doctor-station/diagnosis/init"),200) s.chk("门诊","1.8","医技","检验观察",G("tech","/inspection/observation/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.9","医技","标本定义",G("tech","/inspection/specimen/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.10","医技","LIS配置",G("tech","/inspection/lisConfig/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.11","医技","仪器管理",G("tech","/inspection/instrument/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.12","医技","参考范围",G("tech","/lab-ref-range/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.13","医技","影像列表",G("tech","/radiology-image/list"),200) s.chk("门诊","1.14","医技","影像报告",G("tech","/radiology-image/report/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.15","医技","3D任务",G("tech","/reconstruction/task/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.16","药师","库存预警",G("pharmacist","/pharmacy-stock-alert/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.17","药师","西药发药初始化",G("pharmacist","/pharmacy-manage/western-medicine-dispense/init"),200) s.chk("门诊","1.18","药师","退药初始化",G("pharmacist","/pharmacy-manage/return-medicine/init"),200) s.chk("门诊","1.19","药师","药品追溯",G("pharmacist","/drugtrace/code/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.20","药师","追溯批次",G("pharmacist","/drugtrace/batch/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.21","药师","追溯扫码",G("pharmacist","/drugtrace/scan/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.22","药师","追溯预警",G("pharmacist","/drugtrace/alert/page",{"pageNum":1,"pageSize":10}),200) s.chk("门诊","1.23","药师","合理用药统计",G("pharmacist","/api/v1/rational-drug/statistics"),200) s.chk("门诊","1.24","药师","相互作用规则",G("pharmacist","/api/v1/rational-drug/interaction-rules"),200) s.chk("门诊","1.25","药师","剂量规则",G("pharmacist","/api/v1/rational-drug/dosage-rules"),200) s.chk("门诊","1.26","收费员","收费初始化",G("finance","/charge-manage/charge/init"),200) s.chk("门诊","1.27","收费员","收费患者",G("finance","/charge-manage/charge/encounter-patient-page"),200) s.chk("门诊","1.28","收费员","退费初始化",G("finance","/charge-manage/refund/init"),200) s.chk("门诊","1.29","收费员","退费患者",G("finance","/charge-manage/refund/encounter-patient-page"),200) s.chk("门诊","1.30","收费员","定价患者",G("finance","/charge-manage/pricing/patient-info"),200) # ============================ # 场景2: 住院入院全流程 # ============================ def s2_inpatient(): print(f"\n{'='*60}\n场景2: 住院入院全流程\n{'='*60}") s.chk("住院","2.1","收费员","住院收费初始化",G("finance","/charge-manage/inpatient-charge/init"),200) s.chk("住院","2.2","收费员","住院患者",G("finance","/charge-manage/inpatient-charge/encounter-patient-page"),200) s.chk("住院","2.3","医生","患者主页",G("doctor1","/patient-home-manage/init"),200) s.chk("住院","2.4","医生","空床查询",G("doctor1","/patient-home-manage/empty-bed"),200) s.chk("住院","2.5","医生","科室统计",G("doctor1","/patient-home-manage/caty"),200) s.chk("住院","2.6","护士","护理评估统计",G("nurse_nk","/nursing-assessment-enhanced/stats"),200) r=P("nurse_nk","/nursing-assessment-enhanced/braden/assess",{"patientName":"测试患者甲","encounterId":"6006","itemScores":json.dumps({"sensation":2,"moisture":2,"activity":1,"mobility":2,"nutrition":3,"friction":2}),"detail":"压疮高危"}) s.chk("住院","2.7","护士","Braden评估",r,200) r=P("nurse_nk","/nursing-assessment-enhanced/morse/assess",{"patientName":"测试患者乙","encounterId":"6007","itemScores":json.dumps({"history":15,"diagnosis":0,"ambulation":15,"iv":20,"gait":0,"mental":15}),"detail":"跌倒高危"}) s.chk("住院","2.8","护士","Morse评估",r,200) s.chk("住院","2.9","护士","体征查询",G("nurse_nk","/vital-signs/record-search"),200) s.chk("住院","2.10","护士","体征图表",G("nurse_nk","/vital-signs-chart/page",{"pageNum":1,"pageSize":10}),200) s.chk("住院","2.11","护士","交接班",G("nurse_nk","/nursing-handoff/page",{"pageNum":1,"pageSize":10}),200) s.chk("住院","2.12","药师","待发药",G("pharmacist","/pharmacy-manage/pending-medication/pending-medication-page"),200) s.chk("住院","2.13","药师","药品详情初始化",G("pharmacist","/pharmacy-manage/medication-details/init"),200) s.chk("住院","2.14","药师","药品汇总发药",G("pharmacist","/pharmacy-manage/summary-dispense-medicine/init"),200) s.chk("住院","2.15","药师","住院退药",G("pharmacist","/pharmacy-manage/inHospital-return-medicine/init"),200) # ============================ # 场景3: 手术全流程 # ============================ def s3_surgery(): print(f"\n{'='*60}\n场景3: 手术全流程\n{'='*60}") s.chk("手术","3.1","医生","手术列表",G("doctor1","/clinical-manage/surgery/surgery-page"),200) s.chk("手术","3.2","医生","手术排程",G("doctor1","/clinical-manage/surgery-schedule/page",{"pageNum":1,"pageSize":10}),200) s.chk("手术","3.3","医生","手术统计",G("doctor1","/clinical-manage/surgery/statistics"),200) s.chk("手术","3.4","专家","术前讨论",G("consult","/preop-discussion/page",{"pageNum":1,"pageSize":10}),200) s.chk("手术","3.5","手术室护士","安全核查",G("nurse_ss","/surgery-safety-check/page",{"pageNum":1,"pageSize":10}),200) s.chk("手术","3.6","医生","麻醉标本",G("doctor1","/anesthesia-enhanced/specimen/page",{"pageNum":1,"pageSize":10}),200) s.chk("手术","3.7","医生","麻醉随访",G("doctor1","/anesthesia-enhanced/followup/page",{"pageNum":1,"pageSize":10}),200) s.chk("手术","3.8","医生","麻醉质控",G("doctor1","/anesthesia-enhanced/qc/page",{"pageNum":1,"pageSize":10}),200) s.chk("手术","3.9","医生","知情同意",G("doctor1","/informed-consent/page",{"pageNum":1,"pageSize":10}),200) s.chk("手术","3.10","医生","CA签名统计",G("doctor1","/api/v1/ca-signature/statistics"),200) # ============================ # 场景4: 检验全流程 # ============================ def s4_inspection(): print(f"\n{'='*60}\n场景4: 检验全流程\n{'='*60}") s.chk("检验","4.1","医生","检查申请",G("doctor1","/exam/apply/page",{"pageNum":1,"pageSize":10}),200) s.chk("检验","4.2","护士","标本采集",G("nurse_nk","/inspection/collection/page",{"pageNum":1,"pageSize":10}),200) s.chk("检验","4.3","医技","检验结果",G("tech","/inspection/laboratory/init-page"),200) s.chk("检验","4.4","医技","检验观察",G("tech","/inspection/observation/page",{"pageNum":1,"pageSize":10}),200) s.chk("检验","4.5","医技","标本定义",G("tech","/inspection/specimen/page",{"pageNum":1,"pageSize":10}),200) s.chk("检验","4.6","医技","仪器管理",G("tech","/inspection/instrument/page",{"pageNum":1,"pageSize":10}),200) s.chk("检验","4.7","医技","参考范围",G("tech","/lab-ref-range/page",{"pageNum":1,"pageSize":10}),200) s.chk("检验","4.8","医技","影像列表",G("tech","/radiology-image/list"),200) s.chk("检验","4.9","医技","影像报告",G("tech","/radiology-image/report/page",{"pageNum":1,"pageSize":10}),200) s.chk("检验","4.10","医技","3D任务",G("tech","/reconstruction/task/page",{"pageNum":1,"pageSize":10}),200) s.chk("检验","4.11","医技","3D统计",G("tech","/reconstruction/stats"),200) s.chk("检验","4.12","医技","标本条码",G("tech","/specimen-barcode/page",{"pageNum":1,"pageSize":10}),200) # ============================ # 场景5: 会诊全流程 # ============================ def s5_consultation(): print(f"\n{'='*60}\n场景5: 会诊全流程\n{'='*60}") s.chk("会诊","5.1","医生","会诊记录",G("doctor1","/consultation/page",{"pageNum":1,"pageSize":10}),200) s.chk("会诊","5.2","专家","会诊反馈",G("consult","/cross-module/consult-feedback/page",{"pageNum":1,"pageSize":10}),200) s.chk("会诊","5.3","医生","会诊超时",G("doctor1","/cross-module/consulttimeout/page",{"pageNum":1,"pageSize":10}),200) s.chk("会诊","5.4","医生","临床路径",G("doctor1","/clinical-pathway/page",{"pageNum":1,"pageSize":10}),200) s.chk("会诊","5.5","医生","危急值",G("doctor1","/api/v1/critical-value/page",{"pageNum":1,"pageSize":10}),200) s.chk("会诊","5.6","医生","知识库",G("doctor1","/knowledge-base/page",{"pageNum":1,"pageSize":10}),200) s.chk("会诊","5.7","医生","电子病历",G("doctor1","/api/v1/emr/page",{"pageNum":1,"pageSize":10}),200) # ============================ # 场景6: 急诊全流程 # ============================ def s6_emergency(): print(f"\n{'='*60}\n场景6: 急诊全流程\n{'='*60}") s.chk("急诊","6.1","急诊医生","急诊记录",G("doctor_jz","/emergency/page",{"pageNum":1,"pageSize":10}),200) s.chk("急诊","6.2","急诊护士","分诊排队",G("nurse_jz","/triage/queue/page",{"pageNum":1,"pageSize":10}),200) s.chk("急诊","6.3","急诊护士","护理评估统计",G("nurse_jz","/nursing-assessment-enhanced/stats"),200) r=P("nurse_jz","/nursing-assessment-enhanced/braden/assess",{"patientName":"急诊患者庚","encounterId":"6011","itemScores":json.dumps({"sensation":1,"moisture":1,"activity":1,"mobility":1,"nutrition":1,"friction":1}),"detail":"急诊压疮评估"}) s.chk("急诊","6.4","急诊护士","Braden评估",r,200) s.chk("急诊","6.5","急诊护士","体征查询",G("nurse_jz","/vital-signs/record-search"),200) s.chk("急诊","6.6","急诊护士","危急值",G("nurse_jz","/api/v1/critical-value/page",{"pageNum":1,"pageSize":10}),200) # ============================ # 场景7: 医保结算全流程 # ============================ def s7_insurance(): print(f"\n{'='*60}\n场景7: 医保结算全流程\n{'='*60}") s.chk("医保","7.1","收费员","收费初始化",G("finance","/charge-manage/charge/init"),200) s.chk("医保","7.2","收费员","退费初始化",G("finance","/charge-manage/refund/init"),200) s.chk("医保","7.3","财务","收费报表",G("finance","/report-manage/charge/page",{"pageNum":1,"pageSize":10}),200) s.chk("医保","7.4","财务","经营分析",G("finance","/business-analytics/page",{"pageNum":1,"pageSize":10}),200) s.chk("医保","7.5","财务","月度结算",G("finance","/report-manage/monthly-settlement/page",{"pageNum":1,"pageSize":10}),200) # ============================ # 场景8: 药品全流程 # ============================ def s8_pharmacy(): print(f"\n{'='*60}\n场景8: 药品全流程\n{'='*60}") s.chk("药品","8.1","药师","库存预警",G("pharmacist","/pharmacy-stock-alert/page",{"pageNum":1,"pageSize":10}),200) s.chk("药品","8.2","药师","西药发药初始化",G("pharmacist","/pharmacy-manage/western-medicine-dispense/init"),200) s.chk("药品","8.3","药师","退药初始化",G("pharmacist","/pharmacy-manage/return-medicine/init"),200) s.chk("药品","8.4","药师","药品追溯码",G("pharmacist","/drugtrace/code/page",{"pageNum":1,"pageSize":10}),200) s.chk("药品","8.5","药师","追溯批次",G("pharmacist","/drugtrace/batch/page",{"pageNum":1,"pageSize":10}),200) s.chk("药品","8.6","药师","合理用药统计",G("pharmacist","/api/v1/rational-drug/statistics"),200) s.chk("药品","8.7","药师","相互作用规则",G("pharmacist","/api/v1/rational-drug/interaction-rules"),200) s.chk("药品","8.8","药师","剂量规则",G("pharmacist","/api/v1/rational-drug/dosage-rules"),200) # ============================ # 场景9: 院感全流程 # ============================ def s9_infection(): print(f"\n{'='*60}\n场景9: 院感全流程\n{'='*60}") s.chk("院感","9.1","护士","院感监测",G("nurse_nk","/infection-enhanced/surveillance/page",{"pageNum":1,"pageSize":10}),200) s.chk("院感","9.2","护士","院感暴发",G("nurse_nk","/infection-enhanced/outbreak/page",{"pageNum":1,"pageSize":10}),200) s.chk("院感","9.3","护士","手卫生",G("nurse_nk","/infection-enhanced/hand-hygiene/page",{"pageNum":1,"pageSize":10}),200) s.chk("院感","9.4","护士","手卫生统计",G("nurse_nk","/infection-enhanced/hand-hygiene/stats"),200) s.chk("院感","9.5","护士","多重耐药",G("nurse_nk","/infection-enhanced/mdr/page",{"pageNum":1,"pageSize":10}),200) s.chk("院感","9.6","护士","环境监测",G("nurse_nk","/infection-enhanced/env-monitor/page",{"pageNum":1,"pageSize":10}),200) s.chk("院感","9.7","护士","环境监测统计",G("nurse_nk","/infection-enhanced/env-monitor/stats"),200) s.chk("院感","9.8","医生","院感监测",G("doctor1","/infection-enhanced/surveillance/page",{"pageNum":1,"pageSize":10}),200) s.chk("院感","9.9","医技","多重耐药",G("tech","/infection-enhanced/mdr/page",{"pageNum":1,"pageSize":10}),200) # ============================ # 场景10: 权限隔离测试 # ============================ def s10_permission(): print(f"\n{'='*60}\n场景10: 权限隔离测试\n{'='*60}") # 医生不能访问收费 r=G("doctor1","/charge-manage/register/init") if r.get("code")==200: s.fail("权限","10.1","医生","不应访问挂号",f"code=200") else: s.ok("权限","10.1","医生","不能访问挂号",f"code={r.get('code')}") # 护士不能访问西药发药 r=G("nurse_nk","/pharmacy-manage/western-medicine-dispense/init") if r.get("code")==200: s.fail("权限","10.2","护士","不应访问西药发药",f"code=200") else: s.ok("权限","10.2","护士","不能访问西药发药",f"code={r.get('code')}") # 药师不能访问手术 r=G("pharmacist","/clinical-manage/surgery/surgery-page") if r.get("code")==200: s.fail("权限","10.3","药师","不应访问手术",f"code=200") else: s.ok("权限","10.3","药师","不能访问手术",f"code={r.get('code')}") # 医技不能访问护理评估 r=G("tech","/nursing-assessment-enhanced/page",{"pageNum":1,"pageSize":10}) if r.get("code")==200: s.fail("权限","10.4","医技","不应访问护理评估",f"code=200") else: s.ok("权限","10.4","医技","不能访问护理评估",f"code={r.get('code')}") # 收费员不能访问医生站 r=G("finance","/doctor-station/main/init") if r.get("code")==200: s.fail("权限","10.5","收费员","不应访问医生站",f"code=200") else: s.ok("权限","10.5","收费员","不能访问医生站",f"code={r.get('code')}") # 正向验证 r=G("doctor1","/clinical-manage/surgery/surgery-page") if r.get("code")==200: s.ok("权限","10.6","医生","可以访问手术") else: s.fail("权限","10.6","医生","应能访问手术",f"code={r.get('code')}") r=G("nurse_nk","/nursing-assessment-enhanced/stats") if r.get("code")==200: s.ok("权限","10.7","护士","可以访问护理评估") else: s.fail("权限","10.7","护士","应能访问护理评估",f"code={r.get('code')}") r=G("pharmacist","/drugtrace/code/page",{"pageNum":1,"pageSize":10}) if r.get("code")==200: s.ok("权限","10.8","药师","可以访问药品追溯") else: s.fail("权限","10.8","药师","应能访问药品追溯",f"code={r.get('code')}") r=G("tech","/radiology-image/list") if r.get("code")==200: s.ok("权限","10.9","医技","可以访问影像管理") else: s.fail("权限","10.9","医技","应能访问影像管理",f"code={r.get('code')}") r=G("finance","/charge-manage/charge/init") if r.get("code")==200: s.ok("权限","10.10","收费员","可以访问收费管理") else: s.fail("权限","10.10","收费员","应能访问收费管理",f"code={r.get('code')}") # ============================ # 场景11: 中医+质控 # ============================ def s11_tcm_quality(): print(f"\n{'='*60}\n场景11: 中医+质控\n{'='*60}") s.chk("中医","11.1","医生","中医方剂",G("doctor1","/api/v1/tcm/prescriptions"),200) s.chk("中医","11.2","医生","中医统计",G("doctor1","/api/v1/tcm/statistics"),200) s.chk("中医","11.3","医生","体质辨识(患者6006)",G("doctor1","/api/v1/tcm/constitution/encounter/6006"),200) s.chk("质控","11.4","医技","质控指标",G("tech","/quality-enhanced/indicator/page",{"pageNum":1,"pageSize":10}),200) s.chk("质控","11.5","医技","医嘱统计",G("tech","/quality-enhanced/order-stats/page",{"pageNum":1,"pageSize":10}),200) s.chk("质控","11.6","医技","质控指标汇总",G("tech","/quality-enhanced/indicator/summary"),200) # ============================ # 场景12: 报表+经营 # ============================ def s12_reports(): print(f"\n{'='*60}\n场景12: 报表+经营分析\n{'='*60}") s.chk("报表","12.1","财务","挂号报表",G("finance","/report-manage/register/page",{"pageNum":1,"pageSize":10}),200) s.chk("报表","12.2","财务","收费报表",G("finance","/report-manage/charge/page",{"pageNum":1,"pageSize":10}),200) s.chk("报表","12.3","财务","月度结算",G("finance","/report-manage/monthly-settlement/page",{"pageNum":1,"pageSize":10}),200) s.chk("报表","12.4","财务","入库报表",G("finance","/report-manage/inbound/page",{"pageNum":1,"pageSize":10}),200) s.chk("报表","12.5","财务","出库报表",G("finance","/report-manage/outbound/page",{"pageNum":1,"pageSize":10}),200) s.chk("报表","12.6","财务","经营分析",G("finance","/business-analytics/page",{"pageNum":1,"pageSize":10}),200) s.chk("报表","12.7","财务","经营汇总",G("finance","/business-analytics/summary"),200) s.chk("报表","12.8","医生","知识库",G("doctor1","/knowledge-base/page",{"pageNum":1,"pageSize":10}),200) # ============================ # 主入口 # ============================ if __name__=="__main__": print(f"{'='*70}\nHealthLink-HIS 三甲医院多角色协作测试 v2\n{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{'='*70}") print("\n>>> 登录所有角色...") for k,a in ACCOUNTS.items(): if login(k): print(f" ✅ {a['role']}({a['user']})") else: print(f" ❌ {a['role']}({a['user']})") for fn in [s1_outpatient,s2_inpatient,s3_surgery,s4_inspection,s5_consultation,s6_emergency,s7_insurance,s8_pharmacy,s9_infection,s10_permission,s11_tcm_quality,s12_reports]: try: fn() except Exception as e: print(f" ❌ {fn.__name__}: {e}") print(f"\n{'='*70}") print(f"汇总: 总数={s.t}, 通过={s.p}, 失败={s.f}, 通过率={s.p*100/s.t:.1f}%" if s.t else "") print(f"{'='*70}") os.makedirs("MD/test/reports",exist_ok=True) rp=f"MD/test/reports/multi_role_v2_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md" with open(rp,"w") as f: f.write(f"# 多角色协作测试报告 v2\n\n**时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n## 汇总\n\n- 总数: {s.t}\n- 通过: {s.p}\n- 失败: {s.f}\n- 通过率: {s.p*100/s.t:.1f}%\n\n## 详细\n\n| 场景 | 步骤 | 角色 | 测试项 | 状态 | 说明 |\n|------|------|------|--------|------|------|\n") for r in s.r: f.write(f"| {r['sc']} | {r['step']} | {r['role']} | {r['name']} | {r['s']} | {r['d']} |\n") print(f"\n📄 报告: {rp}") sys.exit(0 if s.f==0 else 1)