Files
everything-is-suitable/everything-is-suitable-test/scripts/core/tdd-iterator.ts
T
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

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
};
}
}