#!/usr/bin/env python3 """ 测试报告生成脚本 生成详细的HTML测试报告,包含测试结果、截图、缺陷统计等 """ import json import os from pathlib import Path from datetime import datetime from typing import List, Dict, Optional from dataclasses import dataclass, asdict import base64 @dataclass class TestCase: """测试用例""" id: str name: str description: str status: str # passed, failed, skipped duration: float error_message: Optional[str] = None screenshot_path: Optional[str] = None steps: List[str] = None timestamp: str = None def __post_init__(self): if self.steps is None: self.steps = [] self.timestamp = datetime.now().isoformat() @dataclass class TestSuite: """测试套件""" name: str test_cases: List[TestCase] start_time: str end_time: str total_duration: float @property def total_tests(self) -> int: return len(self.test_cases) @property def passed_tests(self) -> int: return len([tc for tc in self.test_cases if tc.status == "passed"]) @property def failed_tests(self) -> int: return len([tc for tc in self.test_cases if tc.status == "failed"]) @property def skipped_tests(self) -> int: return len([tc for tc in self.test_cases if tc.status == "skipped"]) @property def pass_rate(self) -> float: if self.total_tests == 0: return 0.0 return (self.passed_tests / self.total_tests) * 100 class TestReportGenerator: """测试报告生成器""" def __init__(self, output_dir: str = "test_reports"): self.output_dir = Path(output_dir) self.output_dir.mkdir(exist_ok=True) self.test_suites: List[TestSuite] = [] def add_test_suite(self, test_suite: TestSuite): """添加测试套件""" self.test_suites.append(test_suite) def generate_html_report(self) -> str: """生成HTML报告""" html_content = self._generate_html() timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") report_file = self.output_dir / f"test_report_{timestamp}.html" with open(report_file, 'w', encoding='utf-8') as f: f.write(html_content) return str(report_file) def generate_json_report(self) -> str: """生成JSON报告""" report_data = { "generated_at": datetime.now().isoformat(), "test_suites": [asdict(suite) for suite in self.test_suites], "summary": self._generate_summary() } timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") report_file = self.output_dir / f"test_report_{timestamp}.json" with open(report_file, 'w', encoding='utf-8') as f: json.dump(report_data, f, indent=2, ensure_ascii=False) return str(report_file) def _generate_summary(self) -> Dict: """生成测试摘要""" total_tests = sum(suite.total_tests for suite in self.test_suites) total_passed = sum(suite.passed_tests for suite in self.test_suites) total_failed = sum(suite.failed_tests for suite in self.test_suites) total_skipped = sum(suite.skipped_tests for suite in self.test_suites) total_duration = sum(suite.total_duration for suite in self.test_suites) overall_pass_rate = (total_passed / total_tests * 100) if total_tests > 0 else 0 # 统计缺陷 defects = [] for suite in self.test_suites: for test_case in suite.test_cases: if test_case.status == "failed" and test_case.error_message: defects.append({ "test_case": test_case.name, "test_suite": suite.name, "error": test_case.error_message, "timestamp": test_case.timestamp }) return { "total_tests": total_tests, "total_passed": total_passed, "total_failed": total_failed, "total_skipped": total_skipped, "overall_pass_rate": overall_pass_rate, "total_duration": total_duration, "defects": defects, "test_suites_count": len(self.test_suites) } def _generate_html(self) -> str: """生成HTML内容""" summary = self._generate_summary() html = f""" E2E和UAT测试报告

🧪 E2E和UAT测试报告

生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}

📊 测试摘要

{summary['total_tests']}

总测试数

{summary['total_passed']}

通过测试

{summary['total_failed']}

失败测试

{summary['overall_pass_rate']:.1f}%

通过率

📋 测试套件

""" # 生成测试套件内容 for suite in self.test_suites: html += f"""

{suite.name}

总计: {suite.total_tests} 通过: {suite.passed_tests} 失败: {suite.failed_tests} 耗时: {suite.total_duration:.2f}s
""" # 生成测试用例 for test_case in suite.test_cases: status_class = test_case.status status_text = "通过" if test_case.status == "passed" else "失败" if test_case.status == "failed" else "跳过" html += f"""
{test_case.id} {test_case.name}
{status_text}

{test_case.description}

测试步骤:

    """ for step in test_case.steps: html += f"
  • {step}
  • \n" html += """
""" # 错误信息 if test_case.error_message: html += f"""

❌ 错误信息:

{test_case.error_message}
""" # 截图 if test_case.screenshot_path and Path(test_case.screenshot_path).exists(): try: with open(test_case.screenshot_path, 'rb') as img_file: img_data = base64.b64encode(img_file.read()).decode() html += f"""

📸 失败截图:

测试失败截图
""" except Exception as e: html += f"""

📸 失败截图:

截图加载失败: {str(e)}

""" html += f"""
⏱️ 耗时: {test_case.duration:.2f}s 🕐 时间: {test_case.timestamp}
""" html += """
""" # 缺陷部分 if summary['defects']: html += """

🐛 缺陷统计

""" for defect in summary['defects']: html += f"""

❌ {defect['test_case']}

测试套件: {defect['test_suite']}

错误: {defect['error']}

时间: {defect['timestamp']}

""" html += """
""" html += """
""" return html def create_sample_report(): """创建示例报告""" generator = TestReportGenerator() # 创建测试用例 test_cases = [ TestCase( id="TC-001", name="完整登录流程", description="测试管理员和普通用户的完整登录流程,包括UI登录和API验证", status="passed", duration=12.5, steps=[ "打开登录页面", "输入管理员用户名和密码", "点击登录按钮", "验证跳转到Dashboard页面", "验证Dashboard数据加载", "通过API验证登录日志", "测试普通用户登录" ] ), TestCase( id="TC-002", name="角色管理完整流程", description="测试角色管理的完整流程,包括创建、编辑、删除角色", status="passed", duration=8.3, steps=[ "登录系统", "导航到角色管理页面", "验证角色列表显示", "创建新角色", "编辑角色信息", "删除角色" ] ), TestCase( id="TC-003", name="菜单管理数据验证", description="验证菜单管理页面的数据结构和字段映射", status="passed", duration=6.2, steps=[ "登录系统", "导航到菜单管理页面", "验证菜单树结构", "验证一级菜单显示", "验证二级菜单显示", "通过API验证字段映射" ] ), TestCase( id="TC-004", name="前后端字段映射一致性", description="验证前后端API的字段映射一致性", status="passed", duration=4.8, steps=[ "登录系统", "验证角色API字段映射", "验证菜单API字段映射", "验证用户API字段映射" ] ), TestCase( id="TC-005", name="RBAC权限验证", description="验证基于角色的访问控制权限", status="passed", duration=7.1, steps=[ "测试管理员权限", "验证管理员可访问所有菜单", "测试普通用户权限", "验证普通用户只能看到授权菜单", "测试未授权访问拦截" ] ) ] # 创建测试套件 test_suite = TestSuite( name="E2E和UAT测试套件", test_cases=test_cases, start_time=datetime.now().isoformat(), end_time=datetime.now().isoformat(), total_duration=sum(tc.duration for tc in test_cases) ) generator.add_test_suite(test_suite) # 生成报告 html_report = generator.generate_html_report() json_report = generator.generate_json_report() print(f"✅ HTML报告已生成: {html_report}") print(f"✅ JSON报告已生成: {json_report}") return generator if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="测试报告生成器") parser.add_argument("--output-dir", default="test_reports", help="报告输出目录") parser.add_argument("--sample", action="store_true", help="生成示例报告") args = parser.parse_args() if args.sample: generator = create_sample_report() else: print("请提供测试数据或使用 --sample 生成示例报告") print("示例: python test_report_generator.py --sample")