test: 통합 테스트 오류 및 경고 수정
- 모든 서비스 메서드 시그니처를 실제 구현에 맞게 수정 - 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:
@@ -0,0 +1,389 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import '../models/report_models.dart';
|
||||
import '../utils/html_report_generator.dart';
|
||||
|
||||
/// 테스트 결과 리포트 수집기
|
||||
class ReportCollector {
|
||||
final List<StepReport> _steps = [];
|
||||
final List<ErrorReport> _errors = [];
|
||||
final List<AutoFixReport> _autoFixes = [];
|
||||
final Map<String, FeatureReport> _features = {};
|
||||
final Map<String, List<ApiCallReport>> _apiCalls = {};
|
||||
final DateTime _startTime = DateTime.now();
|
||||
|
||||
/// 테스트 단계 추가
|
||||
void addStep(StepReport step) {
|
||||
_steps.add(step);
|
||||
}
|
||||
|
||||
/// 에러 추가
|
||||
void addError(ErrorReport error) {
|
||||
_errors.add(error);
|
||||
}
|
||||
|
||||
/// 자동 수정 추가
|
||||
void addAutoFix(AutoFixReport fix) {
|
||||
_autoFixes.add(fix);
|
||||
}
|
||||
|
||||
/// API 호출 추가
|
||||
void addApiCall(String feature, ApiCallReport apiCall) {
|
||||
_apiCalls.putIfAbsent(feature, () => []).add(apiCall);
|
||||
}
|
||||
|
||||
/// 테스트 결과 추가 (간단한 버전)
|
||||
void addTestResult({
|
||||
required String screenName,
|
||||
required String testName,
|
||||
required bool passed,
|
||||
String? error,
|
||||
}) {
|
||||
final step = StepReport(
|
||||
stepName: testName,
|
||||
timestamp: DateTime.now(),
|
||||
message: passed ? '테스트 성공' : '테스트 실패: ${error ?? '알 수 없는 오류'}',
|
||||
success: passed,
|
||||
details: {
|
||||
'screenName': screenName,
|
||||
'testName': testName,
|
||||
'passed': passed,
|
||||
if (error != null) 'error': error,
|
||||
},
|
||||
);
|
||||
addStep(step);
|
||||
|
||||
// 기능별 리포트에도 추가
|
||||
final feature = _features[screenName] ?? FeatureReport(
|
||||
featureName: screenName,
|
||||
featureType: FeatureType.screen,
|
||||
success: true,
|
||||
totalTests: 0,
|
||||
passedTests: 0,
|
||||
failedTests: 0,
|
||||
totalDuration: Duration.zero,
|
||||
testCaseReports: [],
|
||||
);
|
||||
|
||||
_features[screenName] = FeatureReport(
|
||||
featureName: feature.featureName,
|
||||
featureType: feature.featureType,
|
||||
success: feature.passedTests + (passed ? 1 : 0) == feature.totalTests + 1,
|
||||
totalTests: feature.totalTests + 1,
|
||||
passedTests: feature.passedTests + (passed ? 1 : 0),
|
||||
failedTests: feature.failedTests + (passed ? 0 : 1),
|
||||
totalDuration: feature.totalDuration,
|
||||
testCaseReports: [
|
||||
...feature.testCaseReports,
|
||||
TestCaseReport(
|
||||
testCaseName: testName,
|
||||
steps: [],
|
||||
success: passed,
|
||||
duration: Duration.zero,
|
||||
errorMessage: error,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 기능 리포트 추가
|
||||
void addFeatureReport(String feature, FeatureReport report) {
|
||||
_features[feature] = report;
|
||||
}
|
||||
|
||||
/// 테스트 결과 생성
|
||||
TestResult generateTestResult() {
|
||||
int totalTests = 0;
|
||||
int passedTests = 0;
|
||||
int failedTests = 0;
|
||||
int skippedTests = 0;
|
||||
final List<TestFailure> failures = [];
|
||||
|
||||
// 각 기능별 테스트 결과 집계
|
||||
_features.forEach((feature, report) {
|
||||
totalTests += report.totalTests;
|
||||
passedTests += report.passedTests;
|
||||
failedTests += report.failedTests;
|
||||
|
||||
// 실패한 테스트 케이스들을 TestFailure로 변환
|
||||
for (final testCase in report.testCaseReports) {
|
||||
if (!testCase.success && testCase.errorMessage != null) {
|
||||
failures.add(TestFailure(
|
||||
feature: feature,
|
||||
message: testCase.errorMessage!,
|
||||
stackTrace: testCase.stackTrace,
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 단계별 실패도 집계
|
||||
for (final step in _steps) {
|
||||
if (!step.success && step.message.contains('실패')) {
|
||||
failures.add(TestFailure(
|
||||
feature: step.stepName,
|
||||
message: step.message,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return TestResult(
|
||||
totalTests: totalTests == 0 ? _steps.length : totalTests,
|
||||
passedTests: passedTests == 0 ? _steps.where((s) => s.success).length : passedTests,
|
||||
failedTests: failedTests == 0 ? _steps.where((s) => !s.success).length : failedTests,
|
||||
skippedTests: skippedTests,
|
||||
failures: failures,
|
||||
);
|
||||
}
|
||||
|
||||
/// 전체 리포트 생성 (BasicTestReport 사용)
|
||||
BasicTestReport generateReport() {
|
||||
final endTime = DateTime.now();
|
||||
final duration = endTime.difference(_startTime);
|
||||
|
||||
return BasicTestReport(
|
||||
reportId: 'TEST-${DateTime.now().millisecondsSinceEpoch}',
|
||||
testName: 'Automated Test Suite',
|
||||
startTime: _startTime,
|
||||
endTime: endTime,
|
||||
duration: duration,
|
||||
environment: {
|
||||
'platform': 'Flutter',
|
||||
'dartVersion': '3.0',
|
||||
'testFramework': 'flutter_test',
|
||||
},
|
||||
testResult: generateTestResult(),
|
||||
steps: List.from(_steps),
|
||||
errors: List.from(_errors),
|
||||
autoFixes: List.from(_autoFixes),
|
||||
features: Map.from(_features),
|
||||
apiCalls: Map.from(_apiCalls),
|
||||
summary: _generateSummary(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 자동 수정 목록 조회
|
||||
List<AutoFixReport> getAutoFixes() {
|
||||
return List.from(_autoFixes);
|
||||
}
|
||||
|
||||
/// 에러 목록 조회
|
||||
List<ErrorReport> getErrors() {
|
||||
return List.from(_errors);
|
||||
}
|
||||
|
||||
/// 기능별 리포트 조회
|
||||
Map<String, FeatureReport> getFeatureReports() {
|
||||
return Map.from(_features);
|
||||
}
|
||||
|
||||
/// 요약 생성
|
||||
String _generateSummary() {
|
||||
final buffer = StringBuffer();
|
||||
final testResult = generateTestResult();
|
||||
|
||||
buffer.writeln('테스트 실행 요약:');
|
||||
buffer.writeln('- 전체 테스트: ${testResult.totalTests}개');
|
||||
buffer.writeln('- 성공: ${testResult.passedTests}개');
|
||||
buffer.writeln('- 실패: ${testResult.failedTests}개');
|
||||
|
||||
if (_autoFixes.isNotEmpty) {
|
||||
buffer.writeln('\n자동 수정 요약:');
|
||||
buffer.writeln('- 총 ${_autoFixes.length}개 항목 자동 수정됨');
|
||||
final fixTypes = _autoFixes.map((f) => f.errorType).toSet();
|
||||
for (final type in fixTypes) {
|
||||
final count = _autoFixes.where((f) => f.errorType == type).length;
|
||||
buffer.writeln(' - $type: $count개');
|
||||
}
|
||||
}
|
||||
|
||||
if (_errors.isNotEmpty) {
|
||||
buffer.writeln('\n에러 요약:');
|
||||
buffer.writeln('- 총 ${_errors.length}개 에러 발생');
|
||||
final errorTypes = _errors.map((e) => e.errorType).toSet();
|
||||
for (final type in errorTypes) {
|
||||
final count = _errors.where((e) => e.errorType == type).length;
|
||||
buffer.writeln(' - $type: $count개');
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/// 리포트 초기화
|
||||
void clear() {
|
||||
_steps.clear();
|
||||
_errors.clear();
|
||||
_autoFixes.clear();
|
||||
_features.clear();
|
||||
_apiCalls.clear();
|
||||
}
|
||||
|
||||
/// 통계 정보 조회
|
||||
Map<String, dynamic> getStatistics() {
|
||||
return {
|
||||
'totalSteps': _steps.length,
|
||||
'successfulSteps': _steps.where((s) => s.success).length,
|
||||
'failedSteps': _steps.where((s) => !s.success).length,
|
||||
'totalErrors': _errors.length,
|
||||
'totalAutoFixes': _autoFixes.length,
|
||||
'totalFeatures': _features.length,
|
||||
'totalApiCalls': _apiCalls.values.expand((calls) => calls).length,
|
||||
'duration': DateTime.now().difference(_startTime).inSeconds,
|
||||
};
|
||||
}
|
||||
|
||||
/// HTML 리포트 생성
|
||||
Future<String> generateHtmlReport() async {
|
||||
final report = generateReport();
|
||||
final generator = HtmlReportGenerator();
|
||||
return await generator.generateReport(report);
|
||||
}
|
||||
|
||||
/// Markdown 리포트 생성
|
||||
Future<String> generateMarkdownReport() async {
|
||||
final report = generateReport();
|
||||
final buffer = StringBuffer();
|
||||
|
||||
// 헤더
|
||||
buffer.writeln('# ${report.testName}');
|
||||
buffer.writeln();
|
||||
buffer.writeln('## 📊 테스트 실행 결과');
|
||||
buffer.writeln();
|
||||
buffer.writeln('- **실행 시간**: ${report.startTime.toLocal()} ~ ${report.endTime.toLocal()}');
|
||||
buffer.writeln('- **소요 시간**: ${_formatDuration(report.duration)}');
|
||||
buffer.writeln('- **환경**: ${report.environment['platform']} (${report.environment['api']})');
|
||||
buffer.writeln();
|
||||
|
||||
// 요약
|
||||
buffer.writeln('## 📈 테스트 요약');
|
||||
buffer.writeln();
|
||||
buffer.writeln('| 항목 | 수치 |');
|
||||
buffer.writeln('|------|------|');
|
||||
buffer.writeln('| 전체 테스트 | ${report.testResult.totalTests} |');
|
||||
buffer.writeln('| ✅ 성공 | ${report.testResult.passedTests} |');
|
||||
buffer.writeln('| ❌ 실패 | ${report.testResult.failedTests} |');
|
||||
buffer.writeln('| ⏭️ 건너뜀 | ${report.testResult.skippedTests} |');
|
||||
|
||||
final successRate = report.testResult.totalTests > 0
|
||||
? (report.testResult.passedTests / report.testResult.totalTests * 100).toStringAsFixed(1)
|
||||
: '0.0';
|
||||
buffer.writeln('| 성공률 | $successRate% |');
|
||||
buffer.writeln();
|
||||
|
||||
// 기능별 결과
|
||||
if (report.features.isNotEmpty) {
|
||||
buffer.writeln('## 🎯 기능별 테스트 결과');
|
||||
buffer.writeln();
|
||||
buffer.writeln('| 기능 | 전체 | 성공 | 실패 | 성공률 |');
|
||||
buffer.writeln('|------|------|------|------|--------|');
|
||||
|
||||
report.features.forEach((name, feature) {
|
||||
final featureSuccessRate = feature.totalTests > 0
|
||||
? (feature.passedTests / feature.totalTests * 100).toStringAsFixed(1)
|
||||
: '0.0';
|
||||
buffer.writeln('| $name | ${feature.totalTests} | ${feature.passedTests} | ${feature.failedTests} | $featureSuccessRate% |');
|
||||
});
|
||||
buffer.writeln();
|
||||
}
|
||||
|
||||
// 실패 상세
|
||||
if (report.testResult.failures.isNotEmpty) {
|
||||
buffer.writeln('## ❌ 실패한 테스트');
|
||||
buffer.writeln();
|
||||
|
||||
for (final failure in report.testResult.failures) {
|
||||
buffer.writeln('### ${failure.feature}');
|
||||
buffer.writeln();
|
||||
buffer.writeln('```');
|
||||
buffer.writeln(failure.message);
|
||||
buffer.writeln('```');
|
||||
buffer.writeln();
|
||||
}
|
||||
}
|
||||
|
||||
// 자동 수정
|
||||
if (report.autoFixes.isNotEmpty) {
|
||||
buffer.writeln('## 🔧 자동 수정 내역');
|
||||
buffer.writeln();
|
||||
|
||||
for (final fix in report.autoFixes) {
|
||||
final status = fix.success ? '✅' : '❌';
|
||||
buffer.writeln('- $status **${fix.errorType}**: ${fix.cause} → ${fix.solution}');
|
||||
}
|
||||
buffer.writeln();
|
||||
}
|
||||
|
||||
buffer.writeln('---');
|
||||
buffer.writeln('*이 리포트는 ${DateTime.now().toLocal()}에 자동 생성되었습니다.*');
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/// JSON 리포트 생성
|
||||
Future<String> generateJsonReport() async {
|
||||
final report = generateReport();
|
||||
final stats = getStatistics();
|
||||
|
||||
final jsonData = {
|
||||
'reportId': report.reportId,
|
||||
'testName': report.testName,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
'duration': report.duration.inMilliseconds,
|
||||
'environment': report.environment,
|
||||
'summary': {
|
||||
'totalTests': report.testResult.totalTests,
|
||||
'passedTests': report.testResult.passedTests,
|
||||
'failedTests': report.testResult.failedTests,
|
||||
'skippedTests': report.testResult.skippedTests,
|
||||
'successRate': report.testResult.totalTests > 0
|
||||
? (report.testResult.passedTests / report.testResult.totalTests * 100).toStringAsFixed(1)
|
||||
: '0.0',
|
||||
},
|
||||
'statistics': stats,
|
||||
'features': report.features.map((key, value) => MapEntry(key, {
|
||||
'totalTests': value.totalTests,
|
||||
'passedTests': value.passedTests,
|
||||
'failedTests': value.failedTests,
|
||||
})),
|
||||
'failures': report.testResult.failures.map((f) => {
|
||||
'feature': f.feature,
|
||||
'message': f.message,
|
||||
'stackTrace': f.stackTrace,
|
||||
}).toList(),
|
||||
'autoFixes': report.autoFixes.map((f) => {
|
||||
'errorType': f.errorType,
|
||||
'cause': f.cause,
|
||||
'solution': f.solution,
|
||||
'success': f.success,
|
||||
'beforeData': f.beforeData,
|
||||
'afterData': f.afterData,
|
||||
}).toList(),
|
||||
};
|
||||
|
||||
return const JsonEncoder.withIndent(' ').convert(jsonData);
|
||||
}
|
||||
|
||||
/// 리포트 파일로 저장
|
||||
Future<void> saveReport(String content, String filePath) async {
|
||||
final file = File(filePath);
|
||||
final directory = file.parent;
|
||||
|
||||
if (!await directory.exists()) {
|
||||
await directory.create(recursive: true);
|
||||
}
|
||||
|
||||
await file.writeAsString(content);
|
||||
}
|
||||
|
||||
/// Duration 포맷팅
|
||||
String _formatDuration(Duration duration) {
|
||||
if (duration.inHours > 0) {
|
||||
return '${duration.inHours}시간 ${duration.inMinutes % 60}분 ${duration.inSeconds % 60}초';
|
||||
} else if (duration.inMinutes > 0) {
|
||||
return '${duration.inMinutes}분 ${duration.inSeconds % 60}초';
|
||||
} else {
|
||||
return '${duration.inSeconds}초';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/// 테스트 컨텍스트 - 테스트 실행 중 상태와 데이터를 관리
|
||||
class TestContext {
|
||||
final Map<String, dynamic> _data = {};
|
||||
final List<String> _createdResourceIds = [];
|
||||
final Map<String, List<String>> _resourcesByType = {};
|
||||
final Map<String, dynamic> _config = {};
|
||||
String? currentScreen;
|
||||
|
||||
/// 데이터 저장
|
||||
void setData(String key, dynamic value) {
|
||||
_data[key] = value;
|
||||
}
|
||||
|
||||
/// 데이터 조회
|
||||
dynamic getData(String key) {
|
||||
return _data[key];
|
||||
}
|
||||
|
||||
/// 모든 데이터 조회
|
||||
Map<String, dynamic> getAllData() {
|
||||
return Map.from(_data);
|
||||
}
|
||||
|
||||
/// 생성된 리소스 ID 추가
|
||||
void addCreatedResourceId(String resourceType, String id) {
|
||||
_createdResourceIds.add('$resourceType:$id');
|
||||
_resourcesByType.putIfAbsent(resourceType, () => []).add(id);
|
||||
}
|
||||
|
||||
/// 특정 타입의 생성된 리소스 ID 목록 조회
|
||||
List<String> getCreatedResourceIds(String resourceType) {
|
||||
return _resourcesByType[resourceType] ?? [];
|
||||
}
|
||||
|
||||
/// 모든 생성된 리소스 ID 조회
|
||||
List<String> getAllCreatedResourceIds() {
|
||||
return List.from(_createdResourceIds);
|
||||
}
|
||||
|
||||
/// 생성된 리소스 ID 제거
|
||||
void removeCreatedResourceId(String resourceType, String id) {
|
||||
final resourceKey = '$resourceType:$id';
|
||||
_createdResourceIds.remove(resourceKey);
|
||||
_resourcesByType[resourceType]?.remove(id);
|
||||
}
|
||||
|
||||
/// 컨텍스트 초기화
|
||||
void clear() {
|
||||
_data.clear();
|
||||
_createdResourceIds.clear();
|
||||
_resourcesByType.clear();
|
||||
}
|
||||
|
||||
/// 특정 키의 데이터 존재 여부 확인
|
||||
bool hasData(String key) {
|
||||
return _data.containsKey(key);
|
||||
}
|
||||
|
||||
/// 특정 키의 데이터 제거
|
||||
void removeData(String key) {
|
||||
_data.remove(key);
|
||||
}
|
||||
|
||||
/// 현재 상태 스냅샷
|
||||
Map<String, dynamic> snapshot() {
|
||||
return {
|
||||
'data': Map.from(_data),
|
||||
'createdResources': List.from(_createdResourceIds),
|
||||
'resourcesByType': Map.from(_resourcesByType),
|
||||
};
|
||||
}
|
||||
|
||||
/// 스냅샷에서 복원
|
||||
void restore(Map<String, dynamic> snapshot) {
|
||||
_data.clear();
|
||||
_data.addAll(snapshot['data'] ?? {});
|
||||
|
||||
_createdResourceIds.clear();
|
||||
_createdResourceIds.addAll(List<String>.from(snapshot['createdResources'] ?? []));
|
||||
|
||||
_resourcesByType.clear();
|
||||
final resourcesByType = snapshot['resourcesByType'] as Map<String, dynamic>? ?? {};
|
||||
resourcesByType.forEach((key, value) {
|
||||
_resourcesByType[key] = List<String>.from(value as List);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// 설정값 저장
|
||||
void setConfig(String key, dynamic value) {
|
||||
_config[key] = value;
|
||||
}
|
||||
|
||||
/// 설정값 조회
|
||||
dynamic getConfig(String key) {
|
||||
return _config[key];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user