08ea5fbe98
添加用户管理视图、API和状态管理文件
291 lines
8.0 KiB
TypeScript
291 lines
8.0 KiB
TypeScript
import { TestResult, TestModule, TestCase, TDDIteration, TDDFix, TestStatus } from '../models/test-result';
|
|
import { ErrorAnalyzer, ErrorAnalysis, ErrorCategory, ErrorSeverity } from './error-analyzer';
|
|
import { TestExecutor } from './test-executor';
|
|
import { logger } from '../utils/logger';
|
|
|
|
export interface TDDConfig {
|
|
maxIterations: number;
|
|
autoFixEnabled: boolean;
|
|
stopOnCriticalFailure: boolean;
|
|
stopThreshold: number;
|
|
}
|
|
|
|
export class TDDIterator {
|
|
private config: TDDConfig;
|
|
private errorAnalyzer: ErrorAnalyzer;
|
|
private testExecutor: TestExecutor;
|
|
private iterations: TDDIteration[] = [];
|
|
private appliedFixes: Map<string, TDDFix> = new Map();
|
|
|
|
constructor(config?: Partial<TDDConfig>) {
|
|
this.config = {
|
|
maxIterations: config?.maxIterations ?? 3,
|
|
autoFixEnabled: config?.autoFixEnabled ?? true,
|
|
stopOnCriticalFailure: config?.stopOnCriticalFailure ?? true,
|
|
stopThreshold: config?.stopThreshold ?? 0.3
|
|
};
|
|
this.errorAnalyzer = new ErrorAnalyzer();
|
|
this.testExecutor = new TestExecutor();
|
|
}
|
|
|
|
async iterate(initialResult: TestResult): Promise<TestResult> {
|
|
logger.section('TDD 迭代优化');
|
|
|
|
let currentResult = initialResult;
|
|
let iteration = 0;
|
|
|
|
while (iteration < this.config.maxIterations) {
|
|
iteration++;
|
|
logger.info(`开始第 ${iteration} 次迭代`);
|
|
|
|
if (currentResult.failedTests === 0) {
|
|
logger.info('所有测试通过,无需迭代');
|
|
break;
|
|
}
|
|
|
|
const failedTests = this.extractFailedTests(currentResult);
|
|
logger.info(`发现 ${failedTests.length} 个失败测试`);
|
|
|
|
const analyses = this.errorAnalyzer.analyzeBatch(failedTests);
|
|
const stats = this.errorAnalyzer.getErrorStatistics(analyses);
|
|
|
|
logger.info('错误统计', {
|
|
byType: stats.byType,
|
|
byCategory: stats.byCategory,
|
|
autoFixable: stats.autoFixableCount
|
|
});
|
|
|
|
if (this.shouldStop(stats, failedTests.length)) {
|
|
logger.warn('达到停止条件,终止迭代');
|
|
break;
|
|
}
|
|
|
|
if (!this.config.autoFixEnabled) {
|
|
logger.info('自动修复已禁用,终止迭代');
|
|
break;
|
|
}
|
|
|
|
const fixes = await this.generateFixes(analyses, failedTests);
|
|
|
|
if (fixes.length === 0) {
|
|
logger.info('没有可自动修复的问题,终止迭代');
|
|
break;
|
|
}
|
|
|
|
const appliedFixes = await this.applyFixes(fixes);
|
|
|
|
if (appliedFixes.length === 0) {
|
|
logger.info('没有成功应用的修复,终止迭代');
|
|
break;
|
|
}
|
|
|
|
logger.info(`应用了 ${appliedFixes.length} 个修复,重新运行测试...`);
|
|
|
|
const newResult = await this.testExecutor.executeAll();
|
|
|
|
const tddIteration: TDDIteration = {
|
|
iteration,
|
|
previousResult: currentResult,
|
|
currentResult: newResult,
|
|
fixes: appliedFixes,
|
|
timestamp: new Date()
|
|
};
|
|
|
|
this.iterations.push(tddIteration);
|
|
|
|
if (newResult.passRate > currentResult.passRate) {
|
|
logger.info(`通过率提升: ${currentResult.passRate.toFixed(2)}% -> ${newResult.passRate.toFixed(2)}%`);
|
|
} else {
|
|
logger.warn(`通过率未提升: ${currentResult.passRate.toFixed(2)}% -> ${newResult.passRate.toFixed(2)}%`);
|
|
}
|
|
|
|
currentResult = newResult;
|
|
}
|
|
|
|
logger.info(`TDD 迭代完成,共 ${iteration} 次迭代`);
|
|
return currentResult;
|
|
}
|
|
|
|
private extractFailedTests(result: TestResult): TestCase[] {
|
|
const failedTests: TestCase[] = [];
|
|
|
|
for (const module of result.modules) {
|
|
for (const suite of module.suites) {
|
|
for (const test of suite.tests) {
|
|
if (test.status === TestStatus.FAILED) {
|
|
failedTests.push(test);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return failedTests;
|
|
}
|
|
|
|
private shouldStop(stats: ReturnType<ErrorAnalyzer['getErrorStatistics']>, failedCount: number): boolean {
|
|
if (this.config.stopOnCriticalFailure) {
|
|
if (stats.bySeverity[ErrorSeverity.CRITICAL] > 0) {
|
|
logger.warn('发现严重错误,停止迭代');
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const criticalAndHighCount =
|
|
(stats.bySeverity[ErrorSeverity.CRITICAL] || 0) +
|
|
(stats.bySeverity[ErrorSeverity.HIGH] || 0);
|
|
|
|
if (failedCount > 0 && criticalAndHighCount / failedCount > this.config.stopThreshold) {
|
|
logger.warn('高严重性错误比例过高,停止迭代');
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private async generateFixes(
|
|
analyses: Map<string, ErrorAnalysis>,
|
|
failedTests: TestCase[]
|
|
): Promise<TDDFix[]> {
|
|
const fixes: TDDFix[] = [];
|
|
|
|
const analysisEntries = Array.from(analyses.entries());
|
|
for (const [testId, analysis] of analysisEntries) {
|
|
if (!analysis.autoFixable || !analysis.fixStrategy) {
|
|
continue;
|
|
}
|
|
|
|
const testCase = failedTests.find(t => t.id === testId);
|
|
if (!testCase) continue;
|
|
|
|
const fix: TDDFix = {
|
|
id: `fix-${testId}-${Date.now()}`,
|
|
type: this.mapFixType(analysis.fixStrategy.type),
|
|
description: analysis.fixStrategy.description,
|
|
affectedTests: [testId],
|
|
status: 'pending'
|
|
};
|
|
|
|
fixes.push(fix);
|
|
}
|
|
|
|
return fixes;
|
|
}
|
|
|
|
private mapFixType(type: string): 'code' | 'test' | 'config' | 'data' {
|
|
switch (type) {
|
|
case 'code':
|
|
return 'code';
|
|
case 'config':
|
|
return 'config';
|
|
case 'data':
|
|
return 'data';
|
|
case 'wait':
|
|
case 'retry':
|
|
default:
|
|
return 'test';
|
|
}
|
|
}
|
|
|
|
private async applyFixes(fixes: TDDFix[]): Promise<TDDFix[]> {
|
|
const appliedFixes: TDDFix[] = [];
|
|
|
|
for (const fix of fixes) {
|
|
try {
|
|
const success = await this.applyFix(fix);
|
|
|
|
if (success) {
|
|
fix.status = 'applied';
|
|
this.appliedFixes.set(fix.id, fix);
|
|
appliedFixes.push(fix);
|
|
logger.info(`修复已应用: ${fix.description}`);
|
|
} else {
|
|
fix.status = 'failed';
|
|
logger.warn(`修复应用失败: ${fix.description}`);
|
|
}
|
|
} catch (error) {
|
|
fix.status = 'failed';
|
|
logger.error(`修复应用出错: ${fix.description}`, error);
|
|
}
|
|
}
|
|
|
|
return appliedFixes;
|
|
}
|
|
|
|
private async applyFix(fix: TDDFix): Promise<boolean> {
|
|
switch (fix.type) {
|
|
case 'config':
|
|
return await this.applyConfigFix(fix);
|
|
|
|
case 'test':
|
|
return await this.applyTestFix(fix);
|
|
|
|
case 'code':
|
|
return await this.applyCodeFix(fix);
|
|
|
|
case 'data':
|
|
return await this.applyDataFix(fix);
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private async applyConfigFix(fix: TDDFix): Promise<boolean> {
|
|
logger.debug(`应用配置修复: ${fix.description}`);
|
|
|
|
return true;
|
|
}
|
|
|
|
private async applyTestFix(fix: TDDFix): Promise<boolean> {
|
|
logger.debug(`应用测试修复: ${fix.description}`);
|
|
|
|
return true;
|
|
}
|
|
|
|
private async applyCodeFix(fix: TDDFix): Promise<boolean> {
|
|
logger.debug(`应用代码修复: ${fix.description}`);
|
|
|
|
return false;
|
|
}
|
|
|
|
private async applyDataFix(fix: TDDFix): Promise<boolean> {
|
|
logger.debug(`应用数据修复: ${fix.description}`);
|
|
|
|
return true;
|
|
}
|
|
|
|
getIterations(): TDDIteration[] {
|
|
return [...this.iterations];
|
|
}
|
|
|
|
getAppliedFixes(): TDDFix[] {
|
|
return Array.from(this.appliedFixes.values());
|
|
}
|
|
|
|
getIterationReport(): {
|
|
totalIterations: number;
|
|
totalFixes: number;
|
|
passRateImprovement: number;
|
|
finalPassRate: number;
|
|
} {
|
|
if (this.iterations.length === 0) {
|
|
return {
|
|
totalIterations: 0,
|
|
totalFixes: 0,
|
|
passRateImprovement: 0,
|
|
finalPassRate: 0
|
|
};
|
|
}
|
|
|
|
const firstIteration = this.iterations[0];
|
|
const lastIteration = this.iterations[this.iterations.length - 1];
|
|
|
|
return {
|
|
totalIterations: this.iterations.length,
|
|
totalFixes: this.appliedFixes.size,
|
|
passRateImprovement: lastIteration.currentResult.passRate - firstIteration.previousResult.passRate,
|
|
finalPassRate: lastIteration.currentResult.passRate
|
|
};
|
|
}
|
|
}
|