e2ad1331cc
feat(测试): 新增Playwright和Vitest测试配置 feat(测试): 添加测试覆盖率报告生成功能 feat(测试): 实现前后端测试脚本集成 fix(测试): 修复测试密码不匹配问题 fix(测试): 修正URL等待策略 fix(测试): 调整错误消息选择器 refactor(测试): 重构测试目录结构 refactor(测试): 优化测试用例组织方式 docs: 更新测试报告文档 docs: 添加测试覆盖率报告模板 ci: 添加Docker测试环境配置 ci: 实现测试自动化脚本 chore: 更新依赖版本 chore: 添加测试相关配置文件
347 lines
10 KiB
JavaScript
347 lines
10 KiB
JavaScript
#!/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 <standard> <value>');
|
|
process.exit(1);
|
|
}
|
|
break;
|
|
|
|
case 'standards':
|
|
qualityGate.getStandards();
|
|
break;
|
|
|
|
default:
|
|
console.log('质量门禁检查工具');
|
|
console.log('');
|
|
console.log('用法:');
|
|
console.log(' node qualityGate.js check <results.json> - 执行质量门禁检查');
|
|
console.log(' node qualityGate.js set <standard> <value> - 设置质量标准');
|
|
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;
|