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

View File

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