test: 통합 테스트 오류 및 경고 수정
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled

- 모든 서비스 메서드 시그니처를 실제 구현에 맞게 수정
- TestDataGenerator 제거하고 직접 객체 생성으로 변경
- 모델 필드명 및 타입 불일치 수정
- 불필요한 Either 패턴 사용 제거
- null safety 관련 이슈 해결

수정된 파일:
- test/integration/screens/company_integration_test.dart
- test/integration/screens/equipment_integration_test.dart
- test/integration/screens/user_integration_test.dart
- test/integration/screens/login_integration_test.dart
This commit is contained in:
JiWoong Sul
2025-08-05 20:24:05 +09:00
parent d6f34c0a52
commit 198aac6525
145 changed files with 41527 additions and 5220 deletions

View File

@@ -0,0 +1,529 @@
import 'package:dio/dio.dart';
/// 에러 타입
enum ErrorType {
/// 필수 필드 누락
missingRequiredField,
/// 잘못된 참조
invalidReference,
/// 중복 데이터
duplicateData,
/// 권한 부족
permissionDenied,
/// 유효성 검증 실패
validation,
/// 서버 에러
serverError,
/// 네트워크 에러
networkError,
/// 알 수 없는 에러
unknown,
}
/// API 에러 타입 분류
enum ApiErrorType {
/// 인증 관련 에러 (401, 403)
authentication,
/// 유효성 검증 에러 (400)
validation,
/// 리소스 찾기 실패 (404)
notFound,
/// 서버 내부 에러 (500)
serverError,
/// 네트워크 연결 에러
networkConnection,
/// 타임아웃 에러
timeout,
/// 요청 취소
cancelled,
/// 속도 제한
rateLimit,
/// 알 수 없는 에러
unknown,
}
/// API 에러 진단 결과
class ErrorDiagnosis {
/// API 에러 타입
final ApiErrorType type;
/// 일반 에러 타입
final ErrorType errorType;
/// 에러 설명
final String description;
/// 에러 컨텍스트 정보
final Map<String, dynamic> context;
/// 진단 신뢰도 (0.0 ~ 1.0)
final double confidence;
/// 영향받은 API 엔드포인트
final List<String> affectedEndpoints;
/// 서버 에러 코드 (있는 경우)
final String? serverErrorCode;
/// 누락된 필드 목록
final List<String>? missingFields;
/// 타입 불일치 필드 정보
final Map<String, TypeMismatchInfo>? typeMismatches;
/// 원본 에러 메시지
final String? originalMessage;
/// 에러 발생 시간
final DateTime timestamp;
ErrorDiagnosis({
required this.type,
required this.errorType,
required this.description,
required this.context,
required this.confidence,
required this.affectedEndpoints,
this.serverErrorCode,
this.missingFields,
this.typeMismatches,
this.originalMessage,
DateTime? timestamp,
}) : timestamp = timestamp ?? DateTime.now();
/// JSON으로 변환
Map<String, dynamic> toJson() {
return {
'type': type.toString(),
'errorType': errorType.toString(),
'description': description,
'context': context,
'confidence': confidence,
'affectedEndpoints': affectedEndpoints,
'serverErrorCode': serverErrorCode,
'missingFields': missingFields,
'typeMismatches': typeMismatches?.map(
(key, value) => MapEntry(key, value.toJson()),
),
'originalMessage': originalMessage,
'timestamp': timestamp.toIso8601String(),
};
}
}
/// 타입 불일치 정보
class TypeMismatchInfo {
/// 필드 이름
final String fieldName;
/// 예상 타입
final String expectedType;
/// 실제 타입
final String actualType;
/// 실제 값
final dynamic actualValue;
TypeMismatchInfo({
required this.fieldName,
required this.expectedType,
required this.actualType,
this.actualValue,
});
Map<String, dynamic> toJson() {
return {
'fieldName': fieldName,
'expectedType': expectedType,
'actualType': actualType,
'actualValue': actualValue,
};
}
}
/// 수정 제안 타입
enum FixType {
/// 토큰 갱신
refreshToken,
/// 필드 추가
addMissingField,
/// 타입 변환
convertType,
/// 재시도
retry,
/// 데이터 수정
modifyData,
/// 권한 요청
requestPermission,
/// 엔드포인트 변경
endpointSwitch,
/// 설정 변경
configuration,
/// 수동 개입 필요
manualIntervention,
}
/// 수정 제안
class FixSuggestion {
/// 수정 ID
final String fixId;
/// 수정 타입
final FixType type;
/// 수정 설명
final String description;
/// 수정 작업 목록
final List<FixAction> actions;
/// 성공 확률 (0.0 ~ 1.0)
final double successProbability;
/// 자동 수정 가능 여부
final bool isAutoFixable;
/// 예상 소요 시간 (밀리초)
final int? estimatedDuration;
FixSuggestion({
required this.fixId,
required this.type,
required this.description,
required this.actions,
required this.successProbability,
required this.isAutoFixable,
this.estimatedDuration,
});
Map<String, dynamic> toJson() {
return {
'fixId': fixId,
'type': type.toString(),
'description': description,
'actions': actions.map((a) => a.toJson()).toList(),
'successProbability': successProbability,
'isAutoFixable': isAutoFixable,
'estimatedDuration': estimatedDuration,
};
}
}
/// 수정 작업
class FixAction {
/// 작업 타입
final FixActionType type;
/// 작업 타입 문자열 (하위 호환성)
final String actionType;
/// 대상
final String target;
/// 작업 파라미터
final Map<String, dynamic> parameters;
/// 작업 설명
final String? description;
FixAction({
required this.type,
required this.actionType,
required this.target,
required this.parameters,
this.description,
});
Map<String, dynamic> toJson() {
return {
'type': type.toString(),
'actionType': actionType,
'target': target,
'parameters': parameters,
'description': description,
};
}
}
/// 수정 결과
class FixResult {
/// 수정 ID
final String fixId;
/// 성공 여부
final bool success;
/// 실행된 작업 목록
final List<FixAction> executedActions;
/// 실행 시간
final DateTime executedAt;
/// 소요 시간 (밀리초)
final int duration;
/// 에러 (실패 시)
final String? error;
/// 추가 정보
final Map<String, dynamic>? additionalInfo;
FixResult({
required this.fixId,
required this.success,
required this.executedActions,
required this.executedAt,
required this.duration,
this.error,
this.additionalInfo,
});
Map<String, dynamic> toJson() {
return {
'fixId': fixId,
'success': success,
'executedActions': executedActions.map((a) => a.toJson()).toList(),
'executedAt': executedAt.toIso8601String(),
'duration': duration,
'error': error,
'additionalInfo': additionalInfo,
};
}
}
/// 에러 패턴 (학습용)
class ErrorPattern {
/// 패턴 ID
final String patternId;
/// 에러 타입
final ApiErrorType errorType;
/// 패턴 매칭 규칙
final Map<String, dynamic> matchingRules;
/// 성공한 수정 전략
final List<FixSuggestion> successfulFixes;
/// 발생 횟수
final int occurrenceCount;
/// 마지막 발생 시간
final DateTime lastOccurred;
/// 학습 신뢰도
final double confidence;
ErrorPattern({
required this.patternId,
required this.errorType,
required this.matchingRules,
required this.successfulFixes,
required this.occurrenceCount,
required this.lastOccurred,
required this.confidence,
});
Map<String, dynamic> toJson() {
return {
'patternId': patternId,
'errorType': errorType.toString(),
'matchingRules': matchingRules,
'successfulFixes': successfulFixes.map((f) => f.toJson()).toList(),
'occurrenceCount': occurrenceCount,
'lastOccurred': lastOccurred.toIso8601String(),
'confidence': confidence,
};
}
}
/// API 에러 정보
class ApiError {
/// 원본 에러 (optional)
final DioException? originalError;
/// 요청 URL
final String requestUrl;
/// 요청 메서드
final String requestMethod;
/// 요청 헤더
final Map<String, dynamic>? requestHeaders;
/// 요청 바디
final dynamic requestBody;
/// 응답 상태 코드
final int? statusCode;
/// 응답 바디
final dynamic responseBody;
/// 에러 메시지
final String? message;
/// API 엔드포인트
final String? endpoint;
/// HTTP 메서드
final String? method;
/// 에러 발생 시간
final DateTime timestamp;
ApiError({
this.originalError,
required this.requestUrl,
required this.requestMethod,
this.requestHeaders,
this.requestBody,
this.statusCode,
this.responseBody,
this.message,
this.endpoint,
this.method,
DateTime? timestamp,
}) : timestamp = timestamp ?? DateTime.now();
/// DioException으로부터 생성
factory ApiError.fromDioException(DioException error) {
return ApiError(
originalError: error,
requestUrl: error.requestOptions.uri.toString(),
requestMethod: error.requestOptions.method,
requestHeaders: error.requestOptions.headers,
requestBody: error.requestOptions.data,
statusCode: error.response?.statusCode,
responseBody: error.response?.data,
);
}
Map<String, dynamic> toJson() {
return {
'requestUrl': requestUrl,
'requestMethod': requestMethod,
'requestHeaders': requestHeaders,
'requestBody': requestBody,
'statusCode': statusCode,
'responseBody': responseBody,
'timestamp': timestamp.toIso8601String(),
'errorType': originalError?.type.toString(),
'errorMessage': message ?? originalError?.message,
'endpoint': endpoint,
'method': method,
};
}
}
/// Fix 액션 타입
enum FixActionType {
/// 필드 업데이트
updateField,
/// 누락된 리소스 생성
createMissingResource,
/// 재시도 with 지연
retryWithDelay,
/// 데이터 타입 변환
convertDataType,
/// 권한 변경
changePermission,
/// 알수 없음
unknown,
}
/// 근본 원인 분석 결과
class RootCause {
/// 원인 타입
final String causeType;
/// 원인 설명
final String description;
/// 증거 목록
final List<String> evidence;
/// 연관된 진단 결과
final ErrorDiagnosis diagnosis;
/// 권장 수정 방법
final List<FixSuggestion> recommendedFixes;
RootCause({
required this.causeType,
required this.description,
required this.evidence,
required this.diagnosis,
required this.recommendedFixes,
});
Map<String, dynamic> toJson() {
return {
'causeType': causeType,
'description': description,
'evidence': evidence,
'diagnosis': diagnosis.toJson(),
'recommendedFixes': recommendedFixes.map((f) => f.toJson()).toList(),
};
}
}
/// 변경 사항
class Change {
/// 변경 타입
final String type;
/// 변경 전 값
final dynamic before;
/// 변경 후 값
final dynamic after;
/// 변경 대상
final String target;
Change({
required this.type,
this.before,
this.after,
required this.target,
});
Map<String, dynamic> toJson() {
return {
'type': type,
'before': before,
'after': after,
'target': target,
};
}
}

View File

@@ -0,0 +1,606 @@
/// 테스트 리포트
class TestReport {
final String reportId;
final DateTime generatedAt;
final ReportType type;
final List<ScreenTestReport> screenReports;
final TestSummary summary;
final List<ErrorAnalysis> errorAnalyses;
final List<PerformanceMetric> performanceMetrics;
final Map<String, dynamic> metadata;
TestReport({
required this.reportId,
required this.generatedAt,
required this.type,
required this.screenReports,
required this.summary,
required this.errorAnalyses,
required this.performanceMetrics,
required this.metadata,
});
Map<String, dynamic> toJson() => {
'reportId': reportId,
'generatedAt': generatedAt.toIso8601String(),
'type': type.toString(),
'screenReports': screenReports.map((r) => r.toJson()).toList(),
'summary': summary.toJson(),
'errorAnalyses': errorAnalyses.map((e) => e.toJson()).toList(),
'performanceMetrics': performanceMetrics.map((m) => m.toJson()).toList(),
'metadata': metadata,
};
}
/// 리포트 타입
enum ReportType {
full,
summary,
error,
performance,
custom,
}
/// 기능 타입
enum FeatureType {
crud,
navigation,
validation,
authentication,
dataSync,
ui,
performance,
custom,
screen,
}
/// 에러 타입
enum ErrorType {
runtime,
network,
validation,
authentication,
timeout,
assertion,
ui,
unknown,
}
/// 근본 원인
class RootCause {
final String category;
final String description;
final double confidence;
final Map<String, dynamic>? evidence;
RootCause({
required this.category,
required this.description,
required this.confidence,
this.evidence,
});
Map<String, dynamic> toJson() => {
'category': category,
'description': description,
'confidence': confidence,
'evidence': evidence,
};
}
/// 수정 제안
class FixSuggestion {
final String title;
final String description;
final String code;
final double priority;
final bool isAutoFixable;
FixSuggestion({
required this.title,
required this.description,
required this.code,
required this.priority,
required this.isAutoFixable,
});
Map<String, dynamic> toJson() => {
'title': title,
'description': description,
'code': code,
'priority': priority,
'isAutoFixable': isAutoFixable,
};
}
/// 화면별 테스트 리포트
class ScreenTestReport {
final String screenName;
final TestResult testResult;
final List<FeatureReport> featureReports;
final Map<String, dynamic> coverage;
final List<String> recommendations;
ScreenTestReport({
required this.screenName,
required this.testResult,
required this.featureReports,
required this.coverage,
required this.recommendations,
});
Map<String, dynamic> toJson() => {
'screenName': screenName,
'testResult': testResult.toJson(),
'featureReports': featureReports.map((r) => r.toJson()).toList(),
'coverage': coverage,
'recommendations': recommendations,
};
}
/// 기능별 리포트
class FeatureReport {
final String featureName;
final FeatureType featureType;
final bool success;
final int totalTests;
final int passedTests;
final int failedTests;
final Duration totalDuration;
final List<TestCaseReport> testCaseReports;
FeatureReport({
required this.featureName,
required this.featureType,
required this.success,
required this.totalTests,
required this.passedTests,
required this.failedTests,
required this.totalDuration,
required this.testCaseReports,
});
double get successRate => totalTests > 0 ? passedTests / totalTests : 0;
Map<String, dynamic> toJson() => {
'featureName': featureName,
'featureType': featureType.toString(),
'success': success,
'totalTests': totalTests,
'passedTests': passedTests,
'failedTests': failedTests,
'successRate': successRate,
'totalDuration': totalDuration.inMilliseconds,
'testCaseReports': testCaseReports.map((r) => r.toJson()).toList(),
};
}
/// 테스트 케이스 리포트
class TestCaseReport {
final String testCaseName;
final bool success;
final Duration duration;
final String? errorMessage;
final String? stackTrace;
final List<TestStep> steps;
final Map<String, dynamic>? additionalInfo;
TestCaseReport({
required this.testCaseName,
required this.success,
required this.duration,
this.errorMessage,
this.stackTrace,
required this.steps,
this.additionalInfo,
});
Map<String, dynamic> toJson() => {
'testCaseName': testCaseName,
'success': success,
'duration': duration.inMilliseconds,
'errorMessage': errorMessage,
'stackTrace': stackTrace,
'steps': steps.map((s) => s.toJson()).toList(),
'additionalInfo': additionalInfo,
};
}
/// 테스트 단계
class TestStep {
final String stepName;
final StepType type;
final bool success;
final String? description;
final Map<String, dynamic>? data;
final DateTime timestamp;
TestStep({
required this.stepName,
required this.type,
required this.success,
this.description,
this.data,
required this.timestamp,
});
Map<String, dynamic> toJson() => {
'stepName': stepName,
'type': type.toString(),
'success': success,
'description': description,
'data': data,
'timestamp': timestamp.toIso8601String(),
};
}
/// 단계 타입
enum StepType {
setup,
action,
verification,
teardown,
}
/// 테스트 요약
class TestSummary {
final int totalScreens;
final int totalFeatures;
final int totalTestCases;
final int passedTestCases;
final int failedTestCases;
final int skippedTestCases;
final Duration totalDuration;
final double overallSuccessRate;
final DateTime startTime;
final DateTime endTime;
TestSummary({
required this.totalScreens,
required this.totalFeatures,
required this.totalTestCases,
required this.passedTestCases,
required this.failedTestCases,
required this.skippedTestCases,
required this.totalDuration,
required this.overallSuccessRate,
required this.startTime,
required this.endTime,
});
Map<String, dynamic> toJson() => {
'totalScreens': totalScreens,
'totalFeatures': totalFeatures,
'totalTestCases': totalTestCases,
'passedTestCases': passedTestCases,
'failedTestCases': failedTestCases,
'skippedTestCases': skippedTestCases,
'totalDuration': totalDuration.inMilliseconds,
'overallSuccessRate': overallSuccessRate,
'startTime': startTime.toIso8601String(),
'endTime': endTime.toIso8601String(),
};
}
/// 에러 분석
class ErrorAnalysis {
final String errorId;
final ErrorType errorType;
final String description;
final int occurrenceCount;
final List<String> affectedScreens;
final List<String> affectedFeatures;
final RootCause? rootCause;
final List<FixSuggestion> suggestedFixes;
final bool wasAutoFixed;
final Map<String, dynamic> context;
ErrorAnalysis({
required this.errorId,
required this.errorType,
required this.description,
required this.occurrenceCount,
required this.affectedScreens,
required this.affectedFeatures,
this.rootCause,
required this.suggestedFixes,
required this.wasAutoFixed,
required this.context,
});
Map<String, dynamic> toJson() => {
'errorId': errorId,
'errorType': errorType.toString(),
'description': description,
'occurrenceCount': occurrenceCount,
'affectedScreens': affectedScreens,
'affectedFeatures': affectedFeatures,
'rootCause': rootCause?.toJson(),
'suggestedFixes': suggestedFixes.map((f) => f.toJson()).toList(),
'wasAutoFixed': wasAutoFixed,
'context': context,
};
}
/// 성능 메트릭
class PerformanceMetric {
final String metricName;
final MetricType type;
final num value;
final String unit;
final num? baseline;
final num? threshold;
final bool isWithinThreshold;
final Map<String, dynamic>? breakdown;
PerformanceMetric({
required this.metricName,
required this.type,
required this.value,
required this.unit,
this.baseline,
this.threshold,
required this.isWithinThreshold,
this.breakdown,
});
Map<String, dynamic> toJson() => {
'metricName': metricName,
'type': type.toString(),
'value': value,
'unit': unit,
'baseline': baseline,
'threshold': threshold,
'isWithinThreshold': isWithinThreshold,
'breakdown': breakdown,
};
}
/// 메트릭 타입
enum MetricType {
duration,
memory,
apiCalls,
errorRate,
throughput,
custom,
}
/// 리포트 설정
class ReportConfiguration {
final bool includeSuccessDetails;
final bool includeErrorDetails;
final bool includePerformanceMetrics;
final bool includeScreenshots;
final bool generateHtml;
final bool generateJson;
final bool generatePdf;
final String outputDirectory;
final Map<String, dynamic> customSettings;
ReportConfiguration({
this.includeSuccessDetails = true,
this.includeErrorDetails = true,
this.includePerformanceMetrics = true,
this.includeScreenshots = false,
this.generateHtml = true,
this.generateJson = true,
this.generatePdf = false,
required this.outputDirectory,
this.customSettings = const {},
});
Map<String, dynamic> toJson() => {
'includeSuccessDetails': includeSuccessDetails,
'includeErrorDetails': includeErrorDetails,
'includePerformanceMetrics': includePerformanceMetrics,
'includeScreenshots': includeScreenshots,
'generateHtml': generateHtml,
'generateJson': generateJson,
'generatePdf': generatePdf,
'outputDirectory': outputDirectory,
'customSettings': customSettings,
};
}
/// 테스트 결과 (간단한 버전)
class TestResult {
final int totalTests;
final int passedTests;
final int failedTests;
final int skippedTests;
final List<TestFailure> failures;
TestResult({
required this.totalTests,
required this.passedTests,
required this.failedTests,
required this.skippedTests,
required this.failures,
});
Map<String, dynamic> toJson() => {
'totalTests': totalTests,
'passedTests': passedTests,
'failedTests': failedTests,
'skippedTests': skippedTests,
'failures': failures.map((f) => f.toJson()).toList(),
};
}
/// 테스트 실패
class TestFailure {
final String feature;
final String message;
final String? stackTrace;
TestFailure({
required this.feature,
required this.message,
this.stackTrace,
});
Map<String, dynamic> toJson() => {
'feature': feature,
'message': message,
'stackTrace': stackTrace,
};
}
/// 단계별 리포트
class StepReport {
final String stepName;
final DateTime timestamp;
final bool success;
final String message;
final Map<String, dynamic> details;
StepReport({
required this.stepName,
required this.timestamp,
required this.success,
required this.message,
required this.details,
});
Map<String, dynamic> toJson() => {
'stepName': stepName,
'timestamp': timestamp.toIso8601String(),
'success': success,
'message': message,
'details': details,
};
}
/// 에러 리포트
class ErrorReport {
final String errorType;
final String message;
final String? stackTrace;
final DateTime timestamp;
final Map<String, dynamic> context;
ErrorReport({
required this.errorType,
required this.message,
this.stackTrace,
required this.timestamp,
required this.context,
});
Map<String, dynamic> toJson() => {
'errorType': errorType,
'message': message,
'stackTrace': stackTrace,
'timestamp': timestamp.toIso8601String(),
'context': context,
};
}
/// 자동 수정 리포트
class AutoFixReport {
final String errorType;
final String cause;
final String solution;
final bool success;
final Map<String, dynamic> beforeData;
final Map<String, dynamic> afterData;
AutoFixReport({
required this.errorType,
required this.cause,
required this.solution,
required this.success,
required this.beforeData,
required this.afterData,
});
Map<String, dynamic> toJson() => {
'errorType': errorType,
'cause': cause,
'solution': solution,
'success': success,
'beforeData': beforeData,
'afterData': afterData,
};
}
/// API 호출 리포트
class ApiCallReport {
final String endpoint;
final String method;
final int statusCode;
final Duration duration;
final Map<String, dynamic>? request;
final Map<String, dynamic>? response;
final bool success;
ApiCallReport({
required this.endpoint,
required this.method,
required this.statusCode,
required this.duration,
this.request,
this.response,
required this.success,
});
Map<String, dynamic> toJson() => {
'endpoint': endpoint,
'method': method,
'statusCode': statusCode,
'duration': duration.inMilliseconds,
'request': request,
'response': response,
'success': success,
};
}
/// 간단한 테스트 리포트 (BasicTestReport으로 이름 변경)
class BasicTestReport {
final String reportId;
final String testName;
final DateTime startTime;
final DateTime endTime;
final Duration duration;
final Map<String, dynamic> environment;
final TestResult testResult;
final List<StepReport> steps;
final List<ErrorReport> errors;
final List<AutoFixReport> autoFixes;
final Map<String, FeatureReport> features;
final Map<String, List<ApiCallReport>> apiCalls;
final String summary;
BasicTestReport({
required this.reportId,
required this.testName,
required this.startTime,
required this.endTime,
required this.duration,
required this.environment,
required this.testResult,
required this.steps,
required this.errors,
required this.autoFixes,
required this.features,
required this.apiCalls,
required this.summary,
});
Map<String, dynamic> toJson() => {
'reportId': reportId,
'testName': testName,
'startTime': startTime.toIso8601String(),
'endTime': endTime.toIso8601String(),
'duration': duration.inMilliseconds,
'environment': environment,
'testResult': testResult.toJson(),
'steps': steps.map((s) => s.toJson()).toList(),
'errors': errors.map((e) => e.toJson()).toList(),
'autoFixes': autoFixes.map((f) => f.toJson()).toList(),
'features': features.map((k, v) => MapEntry(k, v.toJson())),
'apiCalls': apiCalls.map((k, v) => MapEntry(k, v.map((c) => c.toJson()).toList())),
'summary': summary,
};
}

View File

@@ -0,0 +1,424 @@
/// 화면 메타데이터
class ScreenMetadata {
final String screenName;
final Type controllerType;
final List<ApiEndpoint> relatedEndpoints;
final Map<String, dynamic> screenCapabilities;
ScreenMetadata({
required this.screenName,
required this.controllerType,
required this.relatedEndpoints,
required this.screenCapabilities,
});
Map<String, dynamic> toJson() => {
'screenName': screenName,
'controllerType': controllerType.toString(),
'relatedEndpoints': relatedEndpoints.map((e) => e.toJson()).toList(),
'screenCapabilities': screenCapabilities,
};
}
/// API 엔드포인트
class ApiEndpoint {
final String path;
final String method;
final String description;
final Map<String, dynamic>? parameters;
final Map<String, dynamic>? headers;
ApiEndpoint({
required this.path,
required this.method,
required this.description,
this.parameters,
this.headers,
});
Map<String, dynamic> toJson() => {
'path': path,
'method': method,
'description': description,
'parameters': parameters,
'headers': headers,
};
}
/// 테스트 가능한 기능
class TestableFeature {
final String featureName;
final FeatureType type;
final List<TestCase> testCases;
final Map<String, dynamic> metadata;
final Type? requiredDataType;
final Map<String, FieldConstraint>? dataConstraints;
TestableFeature({
required this.featureName,
required this.type,
required this.testCases,
required this.metadata,
this.requiredDataType,
this.dataConstraints,
});
}
/// 기능 타입
enum FeatureType {
crud,
search,
filter,
pagination,
authentication,
export,
import,
custom,
}
///
class TestCase {
final String name;
final Future<void> Function(TestData data) execute;
final Future<void> Function(TestData data) verify;
final Future<void> Function(TestData data)? setup;
final Future<void> Function(TestData data)? teardown;
final Map<String, dynamic>? metadata;
TestCase({
required this.name,
required this.execute,
required this.verify,
this.setup,
this.teardown,
this.metadata,
});
}
/// 테스트 데이터
class TestData {
final String dataType;
final dynamic data;
final Map<String, dynamic> metadata;
TestData({
required this.dataType,
required this.data,
required this.metadata,
});
Map<String, dynamic> toJson() => {
'dataType': dataType,
'data': data is Map || data is List ? data : data?.toJson() ?? {},
'metadata': metadata,
};
}
/// 데이터 요구사항
class DataRequirement {
final Type dataType;
final Map<String, FieldConstraint> constraints;
final List<DataRelationship> relationships;
final int quantity;
DataRequirement({
required this.dataType,
required this.constraints,
required this.relationships,
required this.quantity,
});
}
/// 필드 제약조건
class FieldConstraint {
final bool required;
final bool nullable;
final int? minLength;
final int? maxLength;
final num? minValue;
final num? maxValue;
final String? pattern;
final List<dynamic>? allowedValues;
final String? defaultValue;
FieldConstraint({
this.required = true,
this.nullable = false,
this.minLength,
this.maxLength,
this.minValue,
this.maxValue,
this.pattern,
this.allowedValues,
this.defaultValue,
});
}
/// 데이터 관계
class DataRelationship {
final String name;
final Type targetType;
final RelationType type;
final String targetId;
final int? count;
final Map<String, FieldConstraint>? constraints;
DataRelationship({
required this.name,
required this.targetType,
required this.type,
required this.targetId,
this.count,
this.constraints,
});
}
/// 관계 타입
enum RelationType {
oneToOne,
oneToMany,
manyToMany,
}
/// 생성 전략
class GenerationStrategy {
final Type dataType;
final List<FieldGeneration> fields;
final List<DataRelationship> relationships;
final Map<String, dynamic> constraints;
final int? quantity;
GenerationStrategy({
required this.dataType,
required this.fields,
required this.relationships,
required this.constraints,
this.quantity,
});
}
/// 필드 생성 전략
class FieldGeneration {
final String fieldName;
final Type valueType;
final String strategy;
final String? prefix;
final String? format;
final List<dynamic>? pool;
final String? relatedTo;
final List<String>? values;
final dynamic value;
FieldGeneration({
required this.fieldName,
required this.valueType,
required this.strategy,
this.prefix,
this.format,
this.pool,
this.relatedTo,
this.values,
this.value,
});
}
/// 필드 정의
class FieldDefinition {
final String name;
final FieldType type;
final dynamic Function() generator;
final bool required;
final bool nullable;
FieldDefinition({
required this.name,
required this.type,
required this.generator,
this.required = true,
this.nullable = false,
});
}
/// 필드 타입
enum FieldType {
string,
integer,
double,
boolean,
dateTime,
date,
time,
object,
array,
}
/// 테스트 결과
class TestResult {
final String screenName;
final DateTime startTime;
DateTime? endTime;
final List<FeatureTestResult> featureResults = [];
final List<TestError> errors = [];
final Map<String, dynamic> metrics = {};
TestResult({
required this.screenName,
required this.startTime,
this.endTime,
});
bool get success => errors.isEmpty && featureResults.every((r) => r.success);
bool get passed => success; // 호환성을 위한 별칭
Duration get duration => endTime != null
? endTime!.difference(startTime)
: Duration.zero;
// 테스트 카운트 관련 getter들
int get totalTests => featureResults
.expand((r) => r.testCaseResults)
.length;
int get passedTests => featureResults
.expand((r) => r.testCaseResults)
.where((r) => r.success)
.length;
int get failedTests => totalTests - passedTests;
void calculateMetrics() {
metrics['totalFeatures'] = featureResults.length;
metrics['successfulFeatures'] = featureResults.where((r) => r.success).length;
metrics['failedFeatures'] = featureResults.where((r) => !r.success).length;
metrics['totalTestCases'] = featureResults
.expand((r) => r.testCaseResults)
.length;
metrics['successfulTestCases'] = featureResults
.expand((r) => r.testCaseResults)
.where((r) => r.success)
.length;
metrics['averageDuration'] = _calculateAverageDuration();
}
double _calculateAverageDuration() {
final allDurations = featureResults
.expand((r) => r.testCaseResults)
.map((r) => r.duration.inMilliseconds);
if (allDurations.isEmpty) return 0;
return allDurations.reduce((a, b) => a + b) / allDurations.length;
}
Map<String, dynamic> toJson() => {
'screenName': screenName,
'success': success,
'startTime': startTime.toIso8601String(),
'endTime': endTime?.toIso8601String(),
'duration': duration.inMilliseconds,
'featureResults': featureResults.map((r) => r.toJson()).toList(),
'errors': errors.map((e) => e.toJson()).toList(),
'metrics': metrics,
};
}
/// 기능 테스트 결과
class FeatureTestResult {
final String featureName;
final DateTime startTime;
DateTime? endTime;
final List<TestCaseResult> testCaseResults = [];
final Map<String, dynamic> metrics = {};
FeatureTestResult({
required this.featureName,
required this.startTime,
this.endTime,
});
bool get success => testCaseResults.every((r) => r.success);
Duration get duration => endTime != null
? endTime!.difference(startTime)
: Duration.zero;
void calculateMetrics() {
metrics['totalTestCases'] = testCaseResults.length;
metrics['successfulTestCases'] = testCaseResults.where((r) => r.success).length;
metrics['failedTestCases'] = testCaseResults.where((r) => !r.success).length;
metrics['averageDuration'] = _calculateAverageDuration();
}
double _calculateAverageDuration() {
if (testCaseResults.isEmpty) return 0;
final totalMs = testCaseResults
.map((r) => r.duration.inMilliseconds)
.reduce((a, b) => a + b);
return totalMs / testCaseResults.length;
}
Map<String, dynamic> toJson() => {
'featureName': featureName,
'success': success,
'startTime': startTime.toIso8601String(),
'endTime': endTime?.toIso8601String(),
'duration': duration.inMilliseconds,
'testCaseResults': testCaseResults.map((r) => r.toJson()).toList(),
'metrics': metrics,
};
}
/// 테스트 케이스 결과
class TestCaseResult {
final String testCaseName;
final bool success;
final Duration duration;
final String? error;
final StackTrace? stackTrace;
final Map<String, dynamic>? metadata;
TestCaseResult({
required this.testCaseName,
required this.success,
required this.duration,
this.error,
this.stackTrace,
this.metadata,
});
Map<String, dynamic> toJson() => {
'testCaseName': testCaseName,
'success': success,
'duration': duration.inMilliseconds,
'error': error,
'stackTrace': stackTrace?.toString(),
'metadata': metadata,
};
}
/// 테스트 에러
class TestError {
final String message;
final StackTrace? stackTrace;
final String? feature;
final DateTime timestamp;
final Map<String, dynamic>? context;
TestError({
required this.message,
this.stackTrace,
this.feature,
required this.timestamp,
this.context,
});
Map<String, dynamic> toJson() => {
'message': message,
'stackTrace': stackTrace?.toString(),
'feature': feature,
'timestamp': timestamp.toIso8601String(),
'context': context,
};
}