feat: add test performance optimization tool
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
interface TestPerformance {
|
||||
testId: string;
|
||||
file: string;
|
||||
title: string;
|
||||
duration: number;
|
||||
tier: string;
|
||||
avgDuration: number;
|
||||
percentile: number;
|
||||
isSlow: boolean;
|
||||
optimizationSuggestions: string[];
|
||||
}
|
||||
|
||||
interface OptimizationRule {
|
||||
name: string;
|
||||
condition: (perf: TestPerformance) => boolean;
|
||||
suggestions: string[];
|
||||
}
|
||||
|
||||
export class TestOptimizer {
|
||||
private rules: OptimizationRule[] = [];
|
||||
|
||||
constructor() {
|
||||
this.initializeRules();
|
||||
}
|
||||
|
||||
private initializeRules(): void {
|
||||
this.rules = [
|
||||
{
|
||||
name: 'slow-test',
|
||||
condition: (p) => p.duration > 60000,
|
||||
suggestions: [
|
||||
'考虑将测试拆分为多个小测试',
|
||||
'检查是否有不必要的等待时间',
|
||||
'优化选择器以提高定位速度',
|
||||
'考虑使用mock数据替代真实API调用',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'very-slow-test',
|
||||
condition: (p) => p.duration > 120000,
|
||||
suggestions: [
|
||||
'测试执行时间过长,强烈建议拆分',
|
||||
'检查是否有性能瓶颈(如大量DOM操作)',
|
||||
'考虑使用并行测试策略',
|
||||
'评估是否需要完整加载所有资源',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'flaky-test',
|
||||
condition: (p) => p.percentile > 90,
|
||||
suggestions: [
|
||||
'测试执行时间波动较大,可能存在稳定性问题',
|
||||
'增加重试次数或使用更稳定的等待策略',
|
||||
'检查网络请求的稳定性',
|
||||
'考虑添加更明确的断言条件',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'tier-fast-slow',
|
||||
condition: (p) => p.tier === 'fast' && p.duration > 30000,
|
||||
suggestions: [
|
||||
'快速层测试不应超过30秒',
|
||||
'重新评估测试是否属于快速层',
|
||||
'优化测试逻辑或移至标准层',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'tier-standard-slow',
|
||||
condition: (p) => p.tier === 'standard' && p.duration > 90000,
|
||||
suggestions: [
|
||||
'标准层测试不应超过90秒',
|
||||
'考虑拆分测试或优化执行流程',
|
||||
'评估是否需要移至深度层',
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
analyzePerformance(
|
||||
testId: string,
|
||||
file: string,
|
||||
title: string,
|
||||
duration: number,
|
||||
tier: string,
|
||||
history: number[]
|
||||
): TestPerformance {
|
||||
const avgDuration = history.length > 0
|
||||
? history.reduce((sum, d) => sum + d, 0) / history.length
|
||||
: duration;
|
||||
|
||||
const sortedHistory = [...history].sort((a, b) => a - b);
|
||||
const percentile = sortedHistory.length > 0
|
||||
? sortedHistory[Math.floor(sortedHistory.length * 0.9)]
|
||||
: duration;
|
||||
|
||||
const isSlow = duration > 60000;
|
||||
|
||||
const suggestions: string[] = [];
|
||||
for (const rule of this.rules) {
|
||||
if (rule.condition({
|
||||
testId,
|
||||
file,
|
||||
title,
|
||||
duration,
|
||||
tier,
|
||||
avgDuration,
|
||||
percentile,
|
||||
isSlow,
|
||||
optimizationSuggestions: [],
|
||||
})) {
|
||||
suggestions.push(...rule.suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
testId,
|
||||
file,
|
||||
title,
|
||||
duration,
|
||||
tier,
|
||||
avgDuration,
|
||||
percentile,
|
||||
isSlow,
|
||||
optimizationSuggestions: suggestions,
|
||||
};
|
||||
}
|
||||
|
||||
optimizeTestSuite(performances: TestPerformance[]): {
|
||||
totalTests: number;
|
||||
slowTests: number;
|
||||
potentialSavings: number;
|
||||
recommendations: string[];
|
||||
} {
|
||||
const slowTests = performances.filter(p => p.isSlow);
|
||||
const totalDuration = performances.reduce((sum, p) => sum + p.duration, 0);
|
||||
const slowDuration = slowTests.reduce((sum, p) => sum + p.duration, 0);
|
||||
|
||||
const potentialSavings = slowDuration * 0.3; // 假设优化可节省30%时间
|
||||
|
||||
const recommendations: string[] = [];
|
||||
|
||||
if (slowTests.length > 0) {
|
||||
recommendations.push(`发现 ${slowTests.length} 个慢速测试,建议优先优化`);
|
||||
}
|
||||
|
||||
const tierSlowTests = {
|
||||
fast: slowTests.filter(p => p.tier === 'fast').length,
|
||||
standard: slowTests.filter(p => p.tier === 'standard').length,
|
||||
deep: slowTests.filter(p => p.tier === 'deep').length,
|
||||
};
|
||||
|
||||
if (tierSlowTests.fast > 0) {
|
||||
recommendations.push(`${tierSlowTests.fast} 个快速层测试执行过慢,建议重新评估分层`);
|
||||
}
|
||||
|
||||
if (tierSlowTests.standard > 5) {
|
||||
recommendations.push(`${tierSlowTests.standard} 个标准层测试执行过慢,建议优化或拆分`);
|
||||
}
|
||||
|
||||
const avgDuration = totalDuration / performances.length;
|
||||
if (avgDuration > 60000) {
|
||||
recommendations.push('平均测试执行时间超过60秒,建议整体优化测试策略');
|
||||
}
|
||||
|
||||
return {
|
||||
totalTests: performances.length,
|
||||
slowTests: slowTests.length,
|
||||
potentialSavings,
|
||||
recommendations,
|
||||
};
|
||||
}
|
||||
|
||||
generateOptimizationReport(performances: TestPerformance[]): string {
|
||||
const analysis = this.optimizeTestSuite(performances);
|
||||
|
||||
let report = '🚀 测试性能优化报告\n';
|
||||
report += '='.repeat(50) + '\n\n';
|
||||
|
||||
report += `总测试数: ${analysis.totalTests}\n`;
|
||||
report += `慢速测试: ${analysis.slowTests}\n`;
|
||||
report += `潜在节省时间: ${(analysis.potentialSavings / 1000).toFixed(2)}s\n\n`;
|
||||
|
||||
if (analysis.recommendations.length > 0) {
|
||||
report += '📋 优化建议:\n';
|
||||
analysis.recommendations.forEach((rec, index) => {
|
||||
report += ` ${index + 1}. ${rec}\n`;
|
||||
});
|
||||
report += '\n';
|
||||
}
|
||||
|
||||
const slowTests = performances.filter(p => p.isSlow)
|
||||
.sort((a, b) => b.duration - a.duration);
|
||||
|
||||
if (slowTests.length > 0) {
|
||||
report += '🐌 慢速测试详情:\n';
|
||||
slowTests.forEach((test, index) => {
|
||||
report += `\n${index + 1}. ${test.title}\n`;
|
||||
report += ` 文件: ${test.file}\n`;
|
||||
report += ` 耗时: ${(test.duration / 1000).toFixed(2)}s\n`;
|
||||
report += ` 层级: ${test.tier}\n`;
|
||||
if (test.optimizationSuggestions.length > 0) {
|
||||
report += ` 优化建议:\n`;
|
||||
test.optimizationSuggestions.forEach(suggestion => {
|
||||
report += ` - ${suggestion}\n`;
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
report += '✅ 未发现慢速测试\n';
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
addRule(rule: OptimizationRule): void {
|
||||
this.rules.push(rule);
|
||||
}
|
||||
|
||||
removeRule(ruleName: string): void {
|
||||
this.rules = this.rules.filter(r => r.name !== ruleName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
console.log('🚀 测试性能优化工具...');
|
||||
|
||||
const mockTestResults = [
|
||||
{
|
||||
testId: 'test-1',
|
||||
file: 'smoke/navigation.smoke.spec.ts',
|
||||
title: '应该成功加载首页',
|
||||
duration: 15000,
|
||||
tier: 'fast',
|
||||
history: [12000, 14000, 15000, 16000, 18000],
|
||||
},
|
||||
{
|
||||
testId: 'test-2',
|
||||
file: 'admin/news-management.spec.ts',
|
||||
title: '应该能够创建新闻',
|
||||
duration: 45000,
|
||||
tier: 'standard',
|
||||
history: [40000, 42000, 45000, 48000, 50000],
|
||||
},
|
||||
{
|
||||
testId: 'test-3',
|
||||
file: 'api/admin.api.spec.ts',
|
||||
title: '应该能够获取内容列表',
|
||||
duration: 80000,
|
||||
tier: 'fast',
|
||||
history: [60000, 70000, 80000, 90000, 100000],
|
||||
},
|
||||
{
|
||||
testId: 'test-4',
|
||||
file: 'visual/homepage-visual.spec.ts',
|
||||
title: '首页视觉回归测试',
|
||||
duration: 150000,
|
||||
tier: 'deep',
|
||||
history: [120000, 130000, 150000, 170000, 180000],
|
||||
},
|
||||
{
|
||||
testId: 'test-5',
|
||||
file: 'responsive/mobile-navigation.spec.ts',
|
||||
title: '移动端导航功能测试',
|
||||
duration: 95000,
|
||||
tier: 'standard',
|
||||
history: [80000, 85000, 95000, 105000, 110000],
|
||||
},
|
||||
];
|
||||
|
||||
const optimizer = {
|
||||
rules: [
|
||||
{
|
||||
name: 'slow-test',
|
||||
condition: (p) => p.duration > 60000,
|
||||
suggestions: [
|
||||
'考虑将测试拆分为多个小测试',
|
||||
'检查是否有不必要的等待时间',
|
||||
'优化选择器以提高定位速度',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'very-slow-test',
|
||||
condition: (p) => p.duration > 120000,
|
||||
suggestions: [
|
||||
'测试执行时间过长,强烈建议拆分',
|
||||
'检查是否有性能瓶颈(如大量DOM操作)',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'tier-fast-slow',
|
||||
condition: (p) => p.tier === 'fast' && p.duration > 30000,
|
||||
suggestions: [
|
||||
'快速层测试不应超过30秒',
|
||||
'重新评估测试是否属于快速层',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
analyzePerformance(testId, file, title, duration, tier, history) {
|
||||
const avgDuration = history.length > 0
|
||||
? history.reduce((sum, d) => sum + d, 0) / history.length
|
||||
: duration;
|
||||
|
||||
const sortedHistory = [...history].sort((a, b) => a - b);
|
||||
const percentile = sortedHistory.length > 0
|
||||
? sortedHistory[Math.floor(sortedHistory.length * 0.9)]
|
||||
: duration;
|
||||
|
||||
const isSlow = duration > 60000;
|
||||
|
||||
const suggestions = [];
|
||||
for (const rule of this.rules) {
|
||||
if (rule.condition({ testId, file, title, duration, tier, avgDuration, percentile, isSlow })) {
|
||||
suggestions.push(...rule.suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
testId,
|
||||
file,
|
||||
title,
|
||||
duration,
|
||||
tier,
|
||||
avgDuration,
|
||||
percentile,
|
||||
isSlow,
|
||||
optimizationSuggestions: suggestions,
|
||||
};
|
||||
},
|
||||
|
||||
generateOptimizationReport(performances) {
|
||||
const slowTests = performances.filter(p => p.isSlow);
|
||||
const totalDuration = performances.reduce((sum, p) => sum + p.duration, 0);
|
||||
const slowDuration = slowTests.reduce((sum, p) => sum + p.duration, 0);
|
||||
const potentialSavings = slowDuration * 0.3;
|
||||
|
||||
let report = '🚀 测试性能优化报告\n';
|
||||
report += '='.repeat(50) + '\n\n';
|
||||
|
||||
report += `总测试数: ${performances.length}\n`;
|
||||
report += `慢速测试: ${slowTests.length}\n`;
|
||||
report += `潜在节省时间: ${(potentialSavings / 1000).toFixed(2)}s\n\n`;
|
||||
|
||||
const sortedSlowTests = slowTests.sort((a, b) => b.duration - a.duration);
|
||||
|
||||
if (sortedSlowTests.length > 0) {
|
||||
report += '🐌 慢速测试详情:\n';
|
||||
sortedSlowTests.forEach((test, index) => {
|
||||
report += `\n${index + 1}. ${test.title}\n`;
|
||||
report += ` 文件: ${test.file}\n`;
|
||||
report += ` 耗时: ${(test.duration / 1000).toFixed(2)}s\n`;
|
||||
report += ` 层级: ${test.tier}\n`;
|
||||
if (test.optimizationSuggestions.length > 0) {
|
||||
report += ` 优化建议:\n`;
|
||||
test.optimizationSuggestions.forEach(suggestion => {
|
||||
report += ` - ${suggestion}\n`;
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
report += '✅ 未发现慢速测试\n';
|
||||
}
|
||||
|
||||
return report;
|
||||
},
|
||||
};
|
||||
|
||||
console.log('📊 分析测试性能...');
|
||||
|
||||
const performances = mockTestResults.map(result =>
|
||||
optimizer.analyzePerformance(
|
||||
result.testId,
|
||||
result.file,
|
||||
result.title,
|
||||
result.duration,
|
||||
result.tier,
|
||||
result.history
|
||||
)
|
||||
);
|
||||
|
||||
const report = optimizer.generateOptimizationReport(performances);
|
||||
console.log(report);
|
||||
|
||||
const slowTestsCount = performances.filter(p => p.isSlow).length;
|
||||
if (slowTestsCount > 0) {
|
||||
console.log(`\n✅ 优化器工作正常,发现 ${slowTestsCount} 个慢速测试`);
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log('\n⚠️ 未发现慢速测试');
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user