2311061111-lyt/autograde/grade_grouped.py
liyitian 3b69599ab1
All checks were successful
autograde-final-vibevault / check-trigger (push) Successful in 9s
autograde-final-vibevault / grade (push) Has been skipped
移除硬编码路径,使用命令行传入的参数
2025-12-14 19:36:46 +08:00

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()