fix: 修复多个关键问题
- 修复前端路由守卫:未登录时不显示提示,直接跳转登录页 - 修复API拦截器:401错误不显示提示,直接跳转 - 增强验证码显示:图片尺寸从120x40增加到200x80 - 增大验证码字体:从28号增加到48号 - 优化验证码字符:排除易混淆的0和1 - 减少干扰线:从5条减少到3条,添加背景色优化 - 增强登录API日志:添加详细的调试日志 - 增强验证码生成和验证日志 - 优化异常处理和错误追踪 影响文件: - src/router/index.ts - src/api/request.ts - app/services/auth_service.py - app/api/v1/auth.py - app/schemas/user.py 测试状态: - 前端构建通过 - 后端语法检查通过 - 验证码显示效果优化完成 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
500
tests/scripts/generate_test_report.py
Normal file
500
tests/scripts/generate_test_report.py
Normal file
@@ -0,0 +1,500 @@
|
||||
"""
|
||||
测试报告生成脚本
|
||||
|
||||
生成完整的测试报告,包括:
|
||||
- 测试执行摘要
|
||||
- 覆盖率报告
|
||||
- 性能测试结果
|
||||
- 安全测试结果
|
||||
- Bug清单
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class TestReportGenerator:
|
||||
"""测试报告生成器"""
|
||||
|
||||
def __init__(self, project_root: str):
|
||||
self.project_root = Path(project_root)
|
||||
self.report_dir = self.project_root / "test_reports"
|
||||
self.report_dir.mkdir(exist_ok=True)
|
||||
|
||||
self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
self.report_data = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"project": "资产管理系统",
|
||||
"version": "1.0.0",
|
||||
"summary": {},
|
||||
"unit_tests": {},
|
||||
"integration_tests": {},
|
||||
"e2e_tests": {},
|
||||
"coverage": {},
|
||||
"performance": {},
|
||||
"security": {},
|
||||
"bugs": []
|
||||
}
|
||||
|
||||
def run_unit_tests(self):
|
||||
"""运行单元测试"""
|
||||
print("=" * 60)
|
||||
print("运行单元测试...")
|
||||
print("=" * 60)
|
||||
|
||||
cmd = [
|
||||
"pytest",
|
||||
"-v",
|
||||
"-m", "unit",
|
||||
"--html=test_reports/unit_test_report.html",
|
||||
"--self-contained-html",
|
||||
"--json-report",
|
||||
"--json-report-file=test_reports/unit_test_results.json"
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
# 解析结果
|
||||
if os.path.exists("test_reports/unit_test_results.json"):
|
||||
with open("test_reports/unit_test_results.json", "r") as f:
|
||||
data = json.load(f)
|
||||
self.report_data["unit_tests"] = {
|
||||
"total": data.get("summary", {}).get("total", 0),
|
||||
"passed": data.get("summary", {}).get("passed", 0),
|
||||
"failed": data.get("summary", {}).get("failed", 0),
|
||||
"skipped": data.get("summary", {}).get("skipped", 0),
|
||||
"duration": data.get("summary", {}).get("duration", 0)
|
||||
}
|
||||
|
||||
return result.returncode == 0
|
||||
|
||||
def run_integration_tests(self):
|
||||
"""运行集成测试"""
|
||||
print("\n" + "=" * 60)
|
||||
print("运行集成测试...")
|
||||
print("=" * 60)
|
||||
|
||||
cmd = [
|
||||
"pytest",
|
||||
"-v",
|
||||
"-m", "integration",
|
||||
"--html=test_reports/integration_test_report.html",
|
||||
"--self-contained-html"
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
|
||||
def run_coverage_tests(self):
|
||||
"""运行覆盖率测试"""
|
||||
print("\n" + "=" * 60)
|
||||
print("生成覆盖率报告...")
|
||||
print("=" * 60)
|
||||
|
||||
cmd = [
|
||||
"pytest",
|
||||
"--cov=app",
|
||||
"--cov-report=html:test_reports/htmlcov",
|
||||
"--cov-report=term-missing",
|
||||
"--cov-report=json:test_reports/coverage.json",
|
||||
"--cov-fail-under=70"
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
# 解析覆盖率数据
|
||||
if os.path.exists("test_reports/coverage.json"):
|
||||
with open("test_reports/coverage.json", "r") as f:
|
||||
data = json.load(f)
|
||||
totals = data.get("totals", {})
|
||||
self.report_data["coverage"] = {
|
||||
"line_coverage": totals.get("percent_covered", 0),
|
||||
"lines_covered": totals.get("covered_lines", 0),
|
||||
"lines_missing": totals.get("missing_lines", 0),
|
||||
"num_statements": totals.get("num_statements", 0)
|
||||
}
|
||||
|
||||
return result.returncode == 0
|
||||
|
||||
def run_security_tests(self):
|
||||
"""运行安全测试"""
|
||||
print("\n" + "=" * 60)
|
||||
print("运行安全测试...")
|
||||
print("=" * 60)
|
||||
|
||||
cmd = [
|
||||
"pytest",
|
||||
"-v",
|
||||
"tests/security/",
|
||||
"-m", "security",
|
||||
"--html=test_reports/security_test_report.html"
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
|
||||
def collect_bugs(self):
|
||||
"""收集测试中发现的Bug"""
|
||||
print("\n" + "=" * 60)
|
||||
print("分析测试结果,收集Bug...")
|
||||
print("=" * 60)
|
||||
|
||||
bugs = []
|
||||
|
||||
# 从失败的测试中提取Bug
|
||||
test_results = [
|
||||
"test_reports/unit_test_results.json",
|
||||
"test_reports/integration_test_results.json"
|
||||
]
|
||||
|
||||
for result_file in test_results:
|
||||
if os.path.exists(result_file):
|
||||
with open(result_file, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
for test in data.get("tests", []):
|
||||
if test.get("outcome") == "failed":
|
||||
bugs.append({
|
||||
"test_name": test.get("name"),
|
||||
"error": test.get("call", {}).get("crash", {}).get("message", ""),
|
||||
"severity": "high" if "critical" in test.get("name", "").lower() else "medium",
|
||||
"status": "open"
|
||||
})
|
||||
|
||||
self.report_data["bugs"] = bugs
|
||||
return bugs
|
||||
|
||||
def generate_html_report(self):
|
||||
"""生成HTML测试报告"""
|
||||
print("\n" + "=" * 60)
|
||||
print("生成HTML测试报告...")
|
||||
print("=" * 60)
|
||||
|
||||
html_template = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>资产管理系统 - 测试报告</title>
|
||||
<style>
|
||||
* {{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}}
|
||||
|
||||
body {{
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
}}
|
||||
|
||||
.container {{
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}}
|
||||
|
||||
h1 {{
|
||||
color: #333;
|
||||
border-bottom: 3px solid #FF6B35;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 30px;
|
||||
}}
|
||||
|
||||
h2 {{
|
||||
color: #FF6B35;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
}}
|
||||
|
||||
.summary {{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}}
|
||||
|
||||
.metric {{
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}}
|
||||
|
||||
.metric.success {{
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}}
|
||||
|
||||
.metric.warning {{
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}}
|
||||
|
||||
.metric.danger {{
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}}
|
||||
|
||||
.metric-value {{
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}}
|
||||
|
||||
.metric-label {{
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
}}
|
||||
|
||||
.bug-list {{
|
||||
list-style: none;
|
||||
}}
|
||||
|
||||
.bug-item {{
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
border-left: 4px solid #dc3545;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
|
||||
.bug-item.high {{
|
||||
border-left-color: #dc3545;
|
||||
}}
|
||||
|
||||
.bug-item.medium {{
|
||||
border-left-color: #ffc107;
|
||||
}}
|
||||
|
||||
.bug-item.low {{
|
||||
border-left-color: #28a745;
|
||||
}}
|
||||
|
||||
table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}}
|
||||
|
||||
th, td {{
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}}
|
||||
|
||||
th {{
|
||||
background: #f8f9fa;
|
||||
font-weight: bold;
|
||||
}}
|
||||
|
||||
.status-pass {{
|
||||
color: #28a745;
|
||||
font-weight: bold;
|
||||
}}
|
||||
|
||||
.status-fail {{
|
||||
color: #dc3545;
|
||||
font-weight: bold;
|
||||
}}
|
||||
|
||||
footer {{
|
||||
margin-top: 50px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ddd;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📊 资产管理系统 - 测试报告</h1>
|
||||
|
||||
<div class="summary">
|
||||
<div class="metric success">
|
||||
<div class="metric-value">{total_tests}</div>
|
||||
<div class="metric-label">总测试数</div>
|
||||
</div>
|
||||
<div class="metric success">
|
||||
<div class="metric-value">{passed_tests}</div>
|
||||
<div class="metric-label">通过</div>
|
||||
</div>
|
||||
<div class="metric {failed_class}">
|
||||
<div class="metric-value">{failed_tests}</div>
|
||||
<div class="metric-label">失败</div>
|
||||
</div>
|
||||
<div class="metric {coverage_class}">
|
||||
<div class="metric-value">{coverage}%</div>
|
||||
<div class="metric-label">代码覆盖率</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>📋 测试摘要</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>测试类型</th>
|
||||
<th>总数</th>
|
||||
<th>通过</th>
|
||||
<th>失败</th>
|
||||
<th>通过率</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>单元测试</td>
|
||||
<td>{unit_total}</td>
|
||||
<td>{unit_passed}</td>
|
||||
<td>{unit_failed}</td>
|
||||
<td>{unit_pass_rate}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>集成测试</td>
|
||||
<td>{integration_total}</td>
|
||||
<td>{integration_passed}</td>
|
||||
<td>{integration_failed}</td>
|
||||
<td>{integration_pass_rate}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>E2E测试</td>
|
||||
<td>{e2e_total}</td>
|
||||
<td>{e2e_passed}</td>
|
||||
<td>{e2e_failed}</td>
|
||||
<td>{e2e_pass_rate}%</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>🐛 Bug清单 ({bug_count})</h2>
|
||||
<ul class="bug-list">
|
||||
{bug_items}
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
<p>生成时间: {timestamp}</p>
|
||||
<p>资产管理系统 v{version} | 测试框架: Pytest + Vitest + Playwright</p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# 计算统计数据
|
||||
total_tests = (
|
||||
self.report_data["unit_tests"].get("total", 0) +
|
||||
self.report_data["integration_tests"].get("total", 0) +
|
||||
self.report_data["e2e_tests"].get("total", 0)
|
||||
)
|
||||
|
||||
passed_tests = (
|
||||
self.report_data["unit_tests"].get("passed", 0) +
|
||||
self.report_data["integration_tests"].get("passed", 0) +
|
||||
self.report_data["e2e_tests"].get("passed", 0)
|
||||
)
|
||||
|
||||
failed_tests = (
|
||||
self.report_data["unit_tests"].get("failed", 0) +
|
||||
self.report_data["integration_tests"].get("failed", 0) +
|
||||
self.report_data["e2e_tests"].get("failed", 0)
|
||||
)
|
||||
|
||||
coverage = self.report_data["coverage"].get("line_coverage", 0)
|
||||
|
||||
# 生成Bug列表HTML
|
||||
bug_items = ""
|
||||
for bug in self.report_data.get("bugs", []):
|
||||
bug_items += f"""
|
||||
<li class="bug-item {bug.get('severity', 'medium')}">
|
||||
<strong>{bug.get('test_name', '')}</strong><br>
|
||||
<small>{bug.get('error', '')}</small>
|
||||
</li>
|
||||
"""
|
||||
|
||||
html = html_template.format(
|
||||
total_tests=total_tests,
|
||||
passed_tests=passed_tests,
|
||||
failed_tests=failed_tests,
|
||||
coverage=int(coverage),
|
||||
failed_class="success" if failed_tests == 0 else "danger",
|
||||
coverage_class="success" if coverage >= 70 else "warning" if coverage >= 50 else "danger",
|
||||
unit_total=self.report_data["unit_tests"].get("total", 0),
|
||||
unit_passed=self.report_data["unit_tests"].get("passed", 0),
|
||||
unit_failed=self.report_data["unit_tests"].get("failed", 0),
|
||||
unit_pass_rate=0,
|
||||
integration_total=self.report_data["integration_tests"].get("total", 0),
|
||||
integration_passed=self.report_data["integration_tests"].get("passed", 0),
|
||||
integration_failed=self.report_data["integration_tests"].get("failed", 0),
|
||||
integration_pass_rate=0,
|
||||
e2e_total=self.report_data["e2e_tests"].get("total", 0),
|
||||
e2e_passed=self.report_data["e2e_tests"].get("passed", 0),
|
||||
e2e_failed=self.report_data["e2e_tests"].get("failed", 0),
|
||||
e2e_pass_rate=0,
|
||||
bug_count=len(self.report_data.get("bugs", [])),
|
||||
bug_items=bug_items if bug_items else "<li>暂无Bug</li>",
|
||||
timestamp=self.report_data["timestamp"],
|
||||
version=self.report_data["version"]
|
||||
)
|
||||
|
||||
report_path = self.report_dir / f"test_report_{self.timestamp}.html"
|
||||
with open(report_path, "w", encoding="utf-8") as f:
|
||||
f.write(html)
|
||||
|
||||
print(f"✓ HTML报告已生成: {report_path}")
|
||||
return report_path
|
||||
|
||||
def generate_json_report(self):
|
||||
"""生成JSON测试报告"""
|
||||
json_path = self.report_dir / f"test_report_{self.timestamp}.json"
|
||||
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(self.report_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"✓ JSON报告已生成: {json_path}")
|
||||
return json_path
|
||||
|
||||
def generate_all_reports(self):
|
||||
"""生成所有报告"""
|
||||
print("\n" + "=" * 60)
|
||||
print("🚀 开始生成测试报告...")
|
||||
print("=" * 60)
|
||||
|
||||
# 运行各类测试
|
||||
self.run_unit_tests()
|
||||
self.run_integration_tests()
|
||||
self.run_coverage_tests()
|
||||
self.run_security_tests()
|
||||
|
||||
# 收集Bug
|
||||
self.collect_bugs()
|
||||
|
||||
# 生成报告
|
||||
html_report = self.generate_html_report()
|
||||
json_report = self.generate_json_report()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ 测试报告生成完成!")
|
||||
print("=" * 60)
|
||||
print(f"\n📄 HTML报告: {html_report}")
|
||||
print(f"📄 JSON报告: {json_report}")
|
||||
print(f"📄 覆盖率报告: {self.report_dir}/htmlcov/index.html")
|
||||
print(f"📄 单元测试报告: {self.report_dir}/unit_test_report.html")
|
||||
print(f"📄 集成测试报告: {self.report_dir}/integration_test_report.html")
|
||||
print(f"📄 安全测试报告: {self.report_dir}/security_test_report.html")
|
||||
print("\n" + "=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
# 项目根目录
|
||||
project_root = sys.argv[1] if len(sys.argv) > 1 else "."
|
||||
|
||||
# 生成测试报告
|
||||
generator = TestReportGenerator(project_root)
|
||||
generator.generate_all_reports()
|
||||
Reference in New Issue
Block a user