#!/usr/bin/env node /** * 质量门禁检查工具 * 定义和执行自动化质量标准,阻止低质量代码合并 */ const fs = require('fs'); const path = require('path'); class QualityGate { constructor() { this.qualityStandards = { passRate: 95, // 通过率必须 >= 95% flakyRate: 5, // 不稳定测试比例必须 <= 5% maxDuration: 600000, // 总测试时间必须 <= 10分钟 maxFailedTests: 5, // 失败测试数量必须 <= 5 maxSlowTests: 10, // 慢速测试数量必须 <= 10 }; this.checks = []; this.passed = true; this.warnings = []; this.errors = []; } checkPassRate(results) { const passRate = results.summary?.passRate || 0; const threshold = this.qualityStandards.passRate; if (passRate < threshold) { this.errors.push({ check: '通过率检查', message: `测试通过率 ${passRate.toFixed(2)}% 低于标准 ${threshold}%`, actual: passRate, threshold: threshold, status: 'failed', }); this.passed = false; } else { this.checks.push({ check: '通过率检查', message: `测试通过率 ${passRate.toFixed(2)}% 符合标准`, actual: passRate, threshold: threshold, status: 'passed', }); } } checkFlakyRate(results) { const flakyRate = results.summary?.flakyRate || 0; const threshold = this.qualityStandards.flakyRate; if (flakyRate > threshold) { this.warnings.push({ check: '不稳定测试检查', message: `不稳定测试比例 ${flakyRate.toFixed(2)}% 超过标准 ${threshold}%`, actual: flakyRate, threshold: threshold, status: 'warning', }); } else { this.checks.push({ check: '不稳定测试检查', message: `不稳定测试比例 ${flakyRate.toFixed(2)}% 符合标准`, actual: flakyRate, threshold: threshold, status: 'passed', }); } } checkDuration(results) { const duration = results.summary?.totalDuration || 0; const threshold = this.qualityStandards.maxDuration; if (duration > threshold) { this.warnings.push({ check: '测试耗时检查', message: `测试总耗时 ${this.formatDuration(duration)} 超过标准 ${this.formatDuration(threshold)}`, actual: duration, threshold: threshold, status: 'warning', }); } else { this.checks.push({ check: '测试耗时检查', message: `测试总耗时 ${this.formatDuration(duration)} 符合标准`, actual: duration, threshold: threshold, status: 'passed', }); } } checkFailedTests(results) { const failedCount = results.failedTests?.length || 0; const threshold = this.qualityStandards.maxFailedTests; if (failedCount > threshold) { this.errors.push({ check: '失败测试数量检查', message: `失败测试数量 ${failedCount} 超过标准 ${threshold}`, actual: failedCount, threshold: threshold, status: 'failed', }); this.passed = false; } else { this.checks.push({ check: '失败测试数量检查', message: `失败测试数量 ${failedCount} 符合标准`, actual: failedCount, threshold: threshold, status: 'passed', }); } } checkSlowTests(results) { const slowCount = results.slowestTests?.length || 0; const threshold = this.qualityStandards.maxSlowTests; if (slowCount > threshold) { this.warnings.push({ check: '慢速测试数量检查', message: `慢速测试数量 ${slowCount} 超过标准 ${threshold}`, actual: slowCount, threshold: threshold, status: 'warning', }); } else { this.checks.push({ check: '慢速测试数量检查', message: `慢速测试数量 ${slowCount} 符合标准`, actual: slowCount, threshold: threshold, status: 'passed', }); } } checkCriticalTests(results) { const criticalTests = results.failedTests?.filter(test => { const title = test.title.toLowerCase(); return title.includes('登录') || title.includes('认证') || title.includes('安全'); }) || []; if (criticalTests.length > 0) { this.errors.push({ check: '关键功能测试检查', message: `关键功能测试失败: ${criticalTests.map(t => t.title).join(', ')}`, actual: criticalTests.length, threshold: 0, status: 'failed', }); this.passed = false; } else { this.checks.push({ check: '关键功能测试检查', message: '所有关键功能测试通过', actual: 0, threshold: 0, status: 'passed', }); } } execute(results) { this.checkPassRate(results); this.checkFlakyRate(results); this.checkDuration(results); this.checkFailedTests(results); this.checkSlowTests(results); this.checkCriticalTests(results); return this.generateReport(); } generateReport() { const report = { timestamp: new Date().toISOString(), passed: this.passed, summary: { total: this.checks.length, passed: this.checks.filter(c => c.status === 'passed').length, warnings: this.warnings.length, errors: this.errors.length, }, checks: this.checks, warnings: this.warnings, errors: this.errors, }; this.printReport(report); this.saveReport(report); return report; } printReport(report) { console.log(''); console.log('═══════════════════════════════════════════'); console.log('🚪 质量门禁检查报告'); console.log('═══════════════════════════════════════════'); console.log(''); console.log(`📊 检查时间: ${new Date(report.timestamp).toLocaleString('zh-CN')}`); console.log(`📈 检查结果: ${report.passed ? '✅ 通过' : '❌ 失败'}`); console.log(''); console.log(`📋 检查统计:`); console.log(` - 总检查项: ${report.summary.total}`); console.log(` - 通过: ${report.summary.passed}`); console.log(` - 警告: ${report.summary.warnings}`); console.log(` - 错误: ${report.summary.errors}`); console.log(''); if (report.checks.length > 0) { console.log('✅ 通过的检查:'); report.checks.forEach(check => { console.log(` ✓ ${check.check}: ${check.message}`); }); console.log(''); } if (report.warnings.length > 0) { console.log('⚠️ 警告:'); report.warnings.forEach(warning => { console.log(` ⚠️ ${warning.check}: ${warning.message}`); }); console.log(''); } if (report.errors.length > 0) { console.log('❌ 错误:'); report.errors.forEach(error => { console.log(` ❌ ${error.check}: ${error.message}`); }); console.log(''); } console.log('═══════════════════════════════════════════'); console.log(''); if (!report.passed) { console.error('❌ 质量门禁检查失败!请修复错误后重试。'); process.exit(1); } } saveReport(report) { const dir = path.join(process.cwd(), 'test-results'); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } const reportPath = path.join(dir, 'quality-gate-report.json'); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8'); console.log(`📄 质量门禁报告已保存: ${reportPath}`); } formatDuration(ms) { if (ms < 1000) { return `${ms}ms`; } else if (ms < 60000) { return `${(ms / 1000).toFixed(1)}s`; } else { return `${(ms / 60000).toFixed(1)}m`; } } setStandard(standard, value) { if (this.qualityStandards.hasOwnProperty(standard)) { this.qualityStandards[standard] = value; console.log(`✅ 质量标准已更新: ${standard} = ${value}`); } else { console.error(`❌ 错误: 未知的质量标准 ${standard}`); process.exit(1); } } getStandards() { console.log('当前质量标准:'); console.log(''); console.log(` 通过率: >= ${this.qualityStandards.passRate}%`); console.log(` 不稳定测试比例: <= ${this.qualityStandards.flakyRate}%`); console.log(` 最大测试时间: <= ${this.formatDuration(this.qualityStandards.maxDuration)}`); console.log(` 最大失败测试数: <= ${this.qualityStandards.maxFailedTests}`); console.log(` 最大慢速测试数: <= ${this.qualityStandards.maxSlowTests}`); console.log(''); } } // 命令行接口 if (require.main === module) { const qualityGate = new QualityGate(); const command = process.argv[2]; switch (command) { case 'check': const resultsFile = process.argv[3]; if (resultsFile && fs.existsSync(resultsFile)) { const results = JSON.parse(fs.readFileSync(resultsFile, 'utf-8')); qualityGate.execute(results); } else { console.error('❌ 错误: 请提供有效的测试结果文件'); process.exit(1); } break; case 'set': const standard = process.argv[3]; const value = parseFloat(process.argv[4]); if (standard && !isNaN(value)) { qualityGate.setStandard(standard, value); } else { console.error('❌ 错误: 请提供有效的标准和数值'); console.error('用法: node qualityGate.js set '); process.exit(1); } break; case 'standards': qualityGate.getStandards(); break; default: console.log('质量门禁检查工具'); console.log(''); console.log('用法:'); console.log(' node qualityGate.js check - 执行质量门禁检查'); console.log(' node qualityGate.js set - 设置质量标准'); console.log(' node qualityGate.js standards - 显示当前质量标准'); console.log(''); console.log('质量标准:'); console.log(' - passRate: 通过率 (%)'); console.log(' - flakyRate: 不稳定测试比例 (%)'); console.log(' - maxDuration: 最大测试时间 (ms)'); console.log(' - maxFailedTests: 最大失败测试数'); console.log(' - maxSlowTests: 最大慢速测试数'); console.log(''); break; } } module.exports = QualityGate;