""" 增强版报告生成器模块 支持趋势分析、历史报告对比、性能基准对比等功能 """ import json from pathlib import Path from datetime import datetime from typing import Dict, Any, List, Optional from dataclasses import dataclass, asdict import statistics from .reporter import TestResult, TestSummary, ReportGenerator @dataclass class TrendData: """趋势数据""" date: str total: int passed: int failed: int pass_rate: float avg_response_time: float total_time: float @dataclass class PerformanceBenchmark: """性能基准数据""" endpoint: str method: str avg_response_time: float min_response_time: float max_response_time: float p95_response_time: float p99_response_time: float class EnhancedReportGenerator(ReportGenerator): """增强版报告生成器""" def __init__(self, output_dir: str = "reports", history_dir: str = "reports/history"): """ 初始化增强版报告生成器 Args: output_dir: 报告输出目录 history_dir: 历史报告目录 """ super().__init__(output_dir) self.history_dir = Path(history_dir) self.history_dir.mkdir(parents=True, exist_ok=True) def generate_trend_report(self, days: int = 7) -> str: """ 生成趋势分析报告 Args: days: 分析最近几天的数据 Returns: 报告文件路径 """ trend_data = self._load_trend_data(days) if not trend_data: return "" report_path = self.output_dir / f"trend_report_{datetime.now().strftime('%Y%m%d')}.html" html_content = self._generate_trend_html(trend_data) with open(report_path, 'w', encoding='utf-8') as f: f.write(html_content) return str(report_path) def generate_comparison_report(self, current_results: List[TestResult], previous_results: List[TestResult] = None) -> str: """ 生成对比报告 Args: current_results: 当前测试结果 previous_results: 之前的测试结果 Returns: 报告文件路径 """ if previous_results is None: previous_results = self._load_latest_results() if not previous_results: return "" comparison_data = self._compare_results(current_results, previous_results) report_path = self.output_dir / f"comparison_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" html_content = self._generate_comparison_html(comparison_data) with open(report_path, 'w', encoding='utf-8') as f: f.write(html_content) return str(report_path) def generate_performance_report(self, results: List[TestResult], benchmarks: Dict[str, PerformanceBenchmark] = None) -> str: """ 生成性能报告 Args: results: 测试结果列表 benchmarks: 性能基准数据 Returns: 报告文件路径 """ performance_data = self._analyze_performance(results) if benchmarks: performance_data = self._compare_with_benchmarks(performance_data, benchmarks) report_path = self.output_dir / f"performance_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" html_content = self._generate_performance_html(performance_data) with open(report_path, 'w', encoding='utf-8') as f: f.write(html_content) return str(report_path) def _load_trend_data(self, days: int) -> List[TrendData]: """ 加载趋势数据 Args: days: 加载最近几天的数据 Returns: 趋势数据列表 """ trend_data = [] for i in range(days): date = datetime.now() - timedelta(days=i) date_str = date.strftime('%Y%m%d') report_file = self.history_dir / f"test_report_{date_str}.json" if report_file.exists(): with open(report_file, 'r', encoding='utf-8') as f: report_data = json.load(f) summary = report_data.get('summary', {}) results = report_data.get('results', []) avg_response_time = statistics.mean([r['response_time'] for r in results]) if results else 0 trend_data.append(TrendData( date=date.strftime('%Y-%m-%d'), total=summary.get('total', 0), passed=summary.get('passed', 0), failed=summary.get('failed', 0), pass_rate=summary.get('pass_rate', 0), avg_response_time=avg_response_time, total_time=summary.get('total_time', 0) )) return sorted(trend_data, key=lambda x: x.date) def _load_latest_results(self) -> List[TestResult]: """ 加载最新的测试结果 Returns: 测试结果列表 """ report_files = list(self.history_dir.glob("test_report_*.json")) if not report_files: return [] latest_file = max(report_files, key=lambda f: f.stat().st_mtime) with open(latest_file, 'r', encoding='utf-8') as f: report_data = json.load(f) return [TestResult(**r) for r in report_data.get('results', [])] def _compare_results(self, current: List[TestResult], previous: List[TestResult]) -> Dict[str, Any]: """ 对比测试结果 Args: current: 当前测试结果 previous: 之前的测试结果 Returns: 对比数据 """ current_summary = self._calculate_summary(current) previous_summary = self._calculate_summary(previous) return { "current": current_summary, "previous": previous_summary, "changes": { "total": current_summary["total"] - previous_summary["total"], "passed": current_summary["passed"] - previous_summary["passed"], "failed": current_summary["failed"] - previous_summary["failed"], "pass_rate": current_summary["pass_rate"] - previous_summary["pass_rate"], "avg_response_time": current_summary["avg_response_time"] - previous_summary["avg_response_time"] } } def _analyze_performance(self, results: List[TestResult]) -> Dict[str, Any]: """ 分析性能数据 Args: results: 测试结果列表 Returns: 性能分析数据 """ response_times = [r.response_time for r in results if r.response_time > 0] if not response_times: return {} return { "avg_response_time": statistics.mean(response_times), "min_response_time": min(response_times), "max_response_time": max(response_times), "median_response_time": statistics.median(response_times), "std_response_time": statistics.stdev(response_times) if len(response_times) > 1 else 0, "p95_response_time": self._calculate_percentile(response_times, 95), "p99_response_time": self._calculate_percentile(response_times, 99), "total_tests": len(results), "failed_tests": sum(1 for r in results if not r.success) } def _compare_with_benchmarks(self, performance_data: Dict[str, Any], benchmarks: Dict[str, PerformanceBenchmark]) -> Dict[str, Any]: """ 与性能基准对比 Args: performance_data: 性能数据 benchmarks: 性能基准 Returns: 对比后的性能数据 """ comparisons = [] for endpoint, benchmark in benchmarks.items(): current_avg = performance_data.get("avg_response_time", 0) comparison = { "endpoint": endpoint, "method": benchmark.method, "benchmark_avg": benchmark.avg_response_time, "current_avg": current_avg, "diff": current_avg - benchmark.avg_response_time, "diff_percent": ((current_avg - benchmark.avg_response_time) / benchmark.avg_response_time * 100) if benchmark.avg_response_time > 0 else 0, "status": "good" if current_avg <= benchmark.avg_response_time * 1.2 else "warning" if current_avg <= benchmark.avg_response_time * 1.5 else "critical" } comparisons.append(comparison) performance_data["benchmarks"] = comparisons return performance_data def _calculate_percentile(self, data: List[float], percentile: int) -> float: """ 计算百分位数 Args: data: 数据列表 percentile: 百分位数 Returns: 百分位数值 """ sorted_data = sorted(data) index = (percentile / 100) * (len(sorted_data) - 1) if index.is_integer(): return sorted_data[int(index)] else: lower = sorted_data[int(index)] upper = sorted_data[int(index) + 1] return lower + (upper - lower) * (index - int(index)) def _calculate_summary(self, results: List[TestResult]) -> Dict[str, Any]: """ 计算测试摘要 Args: results: 测试结果列表 Returns: 测试摘要 """ total = len(results) passed = sum(1 for r in results if r.success) failed = total - passed response_times = [r.response_time for r in results if r.response_time > 0] return { "total": total, "passed": passed, "failed": failed, "pass_rate": (passed / total * 100) if total > 0 else 0, "avg_response_time": statistics.mean(response_times) if response_times else 0 } def _generate_trend_html(self, trend_data: List[TrendData]) -> str: """生成趋势分析HTML""" # 这里简化实现,实际应该使用图表库(如Chart.js) return f""" 趋势分析报告

测试趋势分析报告

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

{"".join([f""" """ for d in trend_data])}
日期 总数 通过 失败 通过率 平均响应时间
{d.date} {d.total} {d.passed} {d.failed} {d.pass_rate:.2f}% {d.avg_response_time:.2f}ms
""" def _generate_comparison_html(self, comparison_data: Dict[str, Any]) -> str: """生成对比分析HTML""" return f""" 测试对比报告

测试对比报告

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

指标 当前 之前 变化
总数 {comparison_data['current']['total']} {comparison_data['previous']['total']} {comparison_data['changes']['total']:+d}
通过 {comparison_data['current']['passed']} {comparison_data['previous']['passed']} {comparison_data['changes']['passed']:+d}
失败 {comparison_data['current']['failed']} {comparison_data['previous']['failed']} {comparison_data['changes']['failed']:+d}
通过率 {comparison_data['current']['pass_rate']:.2f}% {comparison_data['previous']['pass_rate']:.2f}% {comparison_data['changes']['pass_rate']:+.2f}%
""" def _generate_performance_html(self, performance_data: Dict[str, Any]) -> str: """生成性能分析HTML""" return f""" 性能分析报告

性能分析报告

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

性能指标

指标
平均响应时间 {performance_data.get('avg_response_time', 0):.2f}ms
最小响应时间 {performance_data.get('min_response_time', 0):.2f}ms
最大响应时间 {performance_data.get('max_response_time', 0):.2f}ms
中位数响应时间 {performance_data.get('median_response_time', 0):.2f}ms
P95响应时间 {performance_data.get('p95_response_time', 0):.2f}ms
P99响应时间 {performance_data.get('p99_response_time', 0):.2f}ms
""" def save_to_history(self, results: List[TestResult], summary: TestSummary) -> None: """ 保存测试结果到历史记录 Args: results: 测试结果列表 summary: 测试摘要 """ date_str = datetime.now().strftime('%Y%m%d') history_file = self.history_dir / f"test_report_{date_str}.json" report_data = { "summary": summary.to_dict(), "results": [r.to_dict() for r in results], "generated_at": datetime.now().isoformat() } with open(history_file, 'w', encoding='utf-8') as f: json.dump(report_data, f, ensure_ascii=False, indent=2)