generated from Java-2025Fall/final-vibevault-template
183 lines
6.8 KiB
Python
183 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
grade_grouped.py - 编程测试评分脚本
|
|
根据测试分组配置对JUnit测试结果进行评分
|
|
"""
|
|
|
|
import json
|
|
import argparse
|
|
import os
|
|
import xml.etree.ElementTree as ET
|
|
from typing import Dict, List, Tuple
|
|
|
|
|
|
def parse_args():
|
|
"""解析命令行参数"""
|
|
parser = argparse.ArgumentParser(description='Programming Test Grading Script')
|
|
parser.add_argument('--junit-dir', required=True, help='JUnit test results directory')
|
|
parser.add_argument('--groups', required=True, help='Test groups configuration file')
|
|
parser.add_argument('--out', required=True, help='Output grade file (JSON)')
|
|
parser.add_argument('--summary', required=True, help='Output summary file (Markdown)')
|
|
return parser.parse_args()
|
|
|
|
|
|
def load_test_groups(groups_file: str) -> Dict[str, Dict[str, any]]:
|
|
"""加载测试分组配置"""
|
|
# 使用命令行传入的文件路径
|
|
with open(groups_file, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
|
|
|
|
def parse_junit_results(junit_dir: str) -> Dict[str, Dict[str, any]]:
|
|
"""解析JUnit测试结果"""
|
|
results = {}
|
|
|
|
# 遍历JUnit目录中的所有XML文件
|
|
for filename in os.listdir(junit_dir):
|
|
if filename.endswith('.xml'):
|
|
filepath = os.path.join(junit_dir, filename)
|
|
tree = ET.parse(filepath)
|
|
root = tree.getroot()
|
|
|
|
# 命名空间处理
|
|
ns = {'junit': 'http://www.junit.org/XML/ns/junit'} if '{' in root.tag else {}
|
|
|
|
# 遍历所有测试类
|
|
for testcase in root.findall('.//testcase', ns):
|
|
class_name = testcase.get('classname')
|
|
test_name = testcase.get('name')
|
|
full_test_name = f"{class_name}.{test_name}"
|
|
|
|
# 检查是否有失败或错误
|
|
failure = testcase.find('.//failure', ns)
|
|
error = testcase.find('.//error', ns)
|
|
|
|
results[full_test_name] = {
|
|
'status': 'failed' if failure else 'error' if error else 'passed',
|
|
'duration': float(testcase.get('time', '0')),
|
|
'message': failure.get('message') if failure else error.get('message') if error else ''
|
|
}
|
|
|
|
return results
|
|
|
|
|
|
def calculate_grades(test_results: Dict[str, Dict[str, any]], test_groups: Dict[str, Dict[str, any]]) -> Tuple[float, List[Dict[str, any]]]:
|
|
"""计算最终成绩"""
|
|
total_score = 0.0
|
|
group_details = []
|
|
|
|
for group_name, group_config in test_groups.items():
|
|
group_weight = group_config.get('weight', 1.0)
|
|
group_score = 0.0
|
|
group_total = 0.0
|
|
group_passed = 0
|
|
group_total_tests = 0
|
|
group_test_details = []
|
|
|
|
for test_pattern, test_weight in group_config.get('tests', {}).items():
|
|
# 查找匹配的测试
|
|
for test_name, test_result in test_results.items():
|
|
if test_pattern in test_name:
|
|
group_total += test_weight
|
|
group_total_tests += 1
|
|
|
|
if test_result['status'] == 'passed':
|
|
group_score += test_weight
|
|
group_passed += 1
|
|
|
|
group_test_details.append({
|
|
'test_name': test_name,
|
|
'status': test_result['status'],
|
|
'weight': test_weight,
|
|
'score': test_weight if test_result['status'] == 'passed' else 0,
|
|
'duration': test_result['duration'],
|
|
'message': test_result['message']
|
|
})
|
|
|
|
# 计算组内成绩
|
|
if group_total > 0:
|
|
group_percentage = (group_score / group_total) * 100
|
|
weighted_group_score = (group_score / group_total) * group_weight
|
|
total_score += weighted_group_score
|
|
|
|
group_details.append({
|
|
'group_name': group_name,
|
|
'weight': group_weight,
|
|
'score': weighted_group_score,
|
|
'percentage': group_percentage,
|
|
'passed': group_passed,
|
|
'total': group_total_tests,
|
|
'tests': group_test_details
|
|
})
|
|
|
|
return total_score, group_details
|
|
|
|
|
|
def generate_summary(total_score: float, group_details: List[Dict[str, any]]) -> str:
|
|
"""生成评分摘要Markdown"""
|
|
summary = "# 编程测试评分报告\n\n"
|
|
summary += f"## 最终成绩: {total_score:.2f}\n\n"
|
|
|
|
for group in group_details:
|
|
summary += f"### {group['group_name']}\n"
|
|
summary += f"- 权重: {group['weight']}\n"
|
|
summary += f"- 得分: {group['score']:.2f} ({group['percentage']:.1f}%)\n"
|
|
summary += f"- 通过率: {group['passed']}/{group['total']}\n\n"
|
|
|
|
summary += "| 测试名称 | 状态 | 权重 | 得分 | 耗时 (秒) | 错误信息 |\n"
|
|
summary += "|---------|------|------|------|----------|---------|\n"
|
|
|
|
for test in group['tests']:
|
|
status = "✅ 通过" if test['status'] == 'passed' else "❌ 失败" if test['status'] == 'failed' else "⚠️ 错误"
|
|
message = test['message'][:50] + "..." if len(test['message']) > 50 else test['message']
|
|
message = message.replace('|', '\\|').replace('\n', ' ') # 转义Markdown表格字符
|
|
|
|
summary += f"| {test['test_name']} | {status} | {test['weight']} | {test['score']:.2f} | {test['duration']:.2f} | {message} |\n"
|
|
|
|
summary += "\n"
|
|
|
|
return summary
|
|
|
|
|
|
def main():
|
|
"""主函数"""
|
|
args = parse_args()
|
|
|
|
# 加载测试分组配置
|
|
print(f"📁 加载测试分组配置: {args.groups}")
|
|
test_groups = load_test_groups(args.groups)
|
|
|
|
# 解析JUnit测试结果
|
|
print(f"🔍 解析JUnit测试结果: {args.junit_dir}")
|
|
test_results = parse_junit_results(args.junit_dir)
|
|
|
|
# 计算成绩
|
|
print("📊 计算最终成绩...")
|
|
total_score, group_details = calculate_grades(test_results, test_groups)
|
|
|
|
# 生成JSON输出
|
|
print(f"💾 保存评分结果: {args.out}")
|
|
output = {
|
|
'total': total_score,
|
|
'groups': group_details,
|
|
'timestamp': '2025-12-14T18:00:00Z',
|
|
'version': '1.0'
|
|
}
|
|
|
|
with open(args.out, 'w', encoding='utf-8') as f:
|
|
json.dump(output, f, ensure_ascii=False, indent=2)
|
|
|
|
# 生成摘要
|
|
print(f"📝 生成评分摘要: {args.summary}")
|
|
summary = generate_summary(total_score, group_details)
|
|
|
|
with open(args.summary, 'w', encoding='utf-8') as f:
|
|
f.write(summary)
|
|
|
|
print(f"✅ 评分完成! 最终成绩: {total_score:.2f}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|