#!/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()