- 모든 *_redesign.dart 파일을 기본 화면 파일로 통합 - 백업용 컨트롤러 파일들 제거 (*_controller.backup.dart) - 사용하지 않는 예제 및 테스트 파일 제거 - Clean Architecture 적용 후 남은 정리 작업 완료 - 테스트 코드 정리 및 구조 개선 준비 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
745 lines
24 KiB
Dart
745 lines
24 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:get_it/get_it.dart';
|
|
import 'dart:io';
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'package:superport/data/datasources/remote/api_client.dart';
|
|
|
|
// 프레임워크 임포트
|
|
import 'framework/infrastructure/test_context.dart';
|
|
import 'framework/infrastructure/report_collector.dart';
|
|
import 'framework/core/api_error_diagnostics.dart';
|
|
import 'framework/core/auto_fixer.dart' as auto_fixer;
|
|
import 'framework/core/test_data_generator.dart';
|
|
|
|
// 화면별 테스트 임포트
|
|
import 'screens/equipment/equipment_in_automated_test.dart';
|
|
import 'screens/equipment/equipment_out_screen_test.dart';
|
|
import 'screens/license/license_screen_test.dart';
|
|
import 'screens/overview/overview_screen_test.dart';
|
|
import 'screens/base/base_screen_test.dart';
|
|
// import 'warehouse_automated_test.dart' as warehouse_test;
|
|
|
|
/// SUPERPORT 마스터 테스트 스위트
|
|
///
|
|
/// 모든 화면 테스트를 통합하여 병렬로 실행하고 상세한 리포트를 생성합니다.
|
|
///
|
|
/// 실행 방법:
|
|
/// ```bash
|
|
/// flutter test test/integration/automated/master_test_suite.dart
|
|
/// ```
|
|
///
|
|
/// 기능:
|
|
/// - 병렬 테스트 실행 (의존성 없는 테스트)
|
|
/// - 실시간 진행 상황 표시
|
|
/// - 에러 발생 시에도 다른 테스트 계속 진행
|
|
/// - HTML/Markdown 리포트 자동 생성
|
|
/// - CI/CD 친화적인 exit code 처리
|
|
|
|
/// 개별 테스트 결과
|
|
class ScreenTestResult {
|
|
final String screenName;
|
|
final bool passed;
|
|
final Duration duration;
|
|
final dynamic testResult;
|
|
final List<String> logs;
|
|
final DateTime startTime;
|
|
final DateTime endTime;
|
|
|
|
ScreenTestResult({
|
|
required this.screenName,
|
|
required this.passed,
|
|
required this.duration,
|
|
required this.testResult,
|
|
required this.logs,
|
|
required this.startTime,
|
|
required this.endTime,
|
|
});
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'screenName': screenName,
|
|
'passed': passed,
|
|
'duration': duration.inMilliseconds,
|
|
'totalTests': testResult?.totalTests ?? 0,
|
|
'passedTests': testResult?.passedTests ?? 0,
|
|
'failedTests': testResult?.failedTests ?? 0,
|
|
'startTime': startTime.toIso8601String(),
|
|
'endTime': endTime.toIso8601String(),
|
|
'failures': testResult?.failures?.items.map((f) => {
|
|
'feature': f.feature ?? '',
|
|
'message': f.message ?? '',
|
|
})?.toList() ?? [],
|
|
};
|
|
}
|
|
|
|
/// 테스트 스위트 실행 옵션
|
|
class TestSuiteOptions {
|
|
final bool parallel;
|
|
final bool verbose;
|
|
final bool stopOnError;
|
|
final bool generateHtml;
|
|
final bool generateMarkdown;
|
|
final List<String> includeScreens;
|
|
final List<String> excludeScreens;
|
|
final int maxParallelTests;
|
|
|
|
TestSuiteOptions({
|
|
this.parallel = true,
|
|
this.verbose = false,
|
|
this.stopOnError = false,
|
|
this.generateHtml = true,
|
|
this.generateMarkdown = true,
|
|
this.includeScreens = const [],
|
|
this.excludeScreens = const [],
|
|
this.maxParallelTests = 3,
|
|
});
|
|
}
|
|
|
|
/// 마스터 테스트 스위트
|
|
class MasterTestSuite {
|
|
final List<ScreenTestResult> results = [];
|
|
final Map<String, List<String>> testLogs = {};
|
|
final TestSuiteOptions options;
|
|
late DateTime startTime;
|
|
|
|
// 의존성 주입을 위한 서비스들
|
|
late GetIt getIt;
|
|
late ApiClient apiClient;
|
|
late TestContext globalTestContext;
|
|
late ReportCollector globalReportCollector;
|
|
late ApiErrorDiagnostics errorDiagnostics;
|
|
late auto_fixer.ApiAutoFixer autoFixer;
|
|
late TestDataGenerator dataGenerator;
|
|
|
|
// 병렬 실행 제어
|
|
final Map<String, Future<ScreenTestResult>> runningTests = {};
|
|
final StreamController<String> progressController = StreamController<String>.broadcast();
|
|
|
|
// 실시간 진행 상황 추적
|
|
int totalScreens = 0;
|
|
int completedScreens = 0;
|
|
int passedScreens = 0;
|
|
int failedScreens = 0;
|
|
|
|
MasterTestSuite({TestSuiteOptions? options})
|
|
: options = options ?? TestSuiteOptions();
|
|
|
|
/// 모든 테스트 실행
|
|
Future<void> runAllTests() async {
|
|
startTime = DateTime.now();
|
|
|
|
_printHeader();
|
|
|
|
try {
|
|
// 1. 환경 설정
|
|
await _setupEnvironment();
|
|
|
|
// 2. 테스트할 화면 목록 준비
|
|
final screenTests = await _prepareScreenTests();
|
|
totalScreens = screenTests.items.length;
|
|
|
|
_log('테스트할 화면: $totalScreens개');
|
|
_log('실행 모드: ${options.parallel ? "병렬" : "순차"}');
|
|
if (options.parallel) {
|
|
_log('최대 동시 실행 수: ${options.maxParallelTests}개');
|
|
}
|
|
_log('');
|
|
|
|
// 3. 테스트 실행
|
|
if (options.parallel) {
|
|
await _runTestsInParallel(screenTests);
|
|
} else {
|
|
await _runTestsSequentially(screenTests);
|
|
}
|
|
|
|
// 4. 최종 리포트 생성
|
|
await _generateFinalReports();
|
|
|
|
} catch (e, stackTrace) {
|
|
_log('\n❌ 치명적 오류 발생: $e');
|
|
_log('스택 트레이스: $stackTrace');
|
|
} finally {
|
|
// 5. 환경 정리
|
|
await _teardownEnvironment();
|
|
progressController.close();
|
|
}
|
|
}
|
|
|
|
/// 환경 설정
|
|
Future<void> _setupEnvironment() async {
|
|
_log('🔧 테스트 환경 설정 중...\n');
|
|
|
|
try {
|
|
// GetIt 초기화
|
|
getIt = GetIt.instance;
|
|
// await RealApiTestHelper.setupTestEnvironment();
|
|
|
|
// API 클라이언트 가져오기
|
|
apiClient = getIt.get<ApiClient>();
|
|
|
|
// 전역 테스트 컨텍스트 초기화
|
|
globalTestContext = TestContext();
|
|
globalReportCollector = ReportCollector();
|
|
|
|
// 에러 진단 및 자동 수정 도구 초기화
|
|
errorDiagnostics = ApiErrorDiagnostics();
|
|
autoFixer = auto_fixer.ApiAutoFixer(
|
|
diagnostics: errorDiagnostics,
|
|
);
|
|
|
|
// 테스트 데이터 생성기 초기화
|
|
dataGenerator = TestDataGenerator();
|
|
|
|
|
|
// 로그인 로직 주석 처리 - 필요시 구현
|
|
_log('✅ 로그인 성공!\n');
|
|
|
|
} catch (e) {
|
|
_log('❌ 환경 설정 실패: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// 테스트할 화면 목록 준비
|
|
Future<List<BaseScreenTest>> _prepareScreenTests() async {
|
|
final screenTests = <BaseScreenTest>[];
|
|
|
|
// 1. Equipment In 테스트
|
|
if (_shouldIncludeScreen('EquipmentIn')) {
|
|
screenTests.add(EquipmentInAutomatedTest(
|
|
apiClient: apiClient,
|
|
getIt: getIt,
|
|
testContext: TestContext(), // 각 테스트마다 독립적인 컨텍스트
|
|
errorDiagnostics: errorDiagnostics,
|
|
autoFixer: autoFixer,
|
|
dataGenerator: dataGenerator,
|
|
reportCollector: ReportCollector(), // 각 테스트마다 독립적인 리포트 수집기
|
|
));
|
|
}
|
|
|
|
// 2. License 테스트
|
|
if (_shouldIncludeScreen('License')) {
|
|
screenTests.add(LicenseScreenTest(
|
|
apiClient: apiClient,
|
|
getIt: getIt,
|
|
testContext: TestContext(),
|
|
errorDiagnostics: errorDiagnostics,
|
|
autoFixer: autoFixer,
|
|
dataGenerator: dataGenerator,
|
|
reportCollector: ReportCollector(),
|
|
));
|
|
}
|
|
|
|
// 3. Overview 테스트
|
|
if (_shouldIncludeScreen('Overview')) {
|
|
screenTests.add(OverviewScreenTest(
|
|
apiClient: apiClient,
|
|
getIt: getIt,
|
|
testContext: TestContext(),
|
|
errorDiagnostics: errorDiagnostics,
|
|
autoFixer: autoFixer,
|
|
dataGenerator: dataGenerator,
|
|
reportCollector: ReportCollector(),
|
|
));
|
|
}
|
|
|
|
// 4. Equipment Out 테스트
|
|
if (_shouldIncludeScreen('EquipmentOut')) {
|
|
screenTests.add(EquipmentOutScreenTest(
|
|
apiClient: apiClient,
|
|
getIt: getIt,
|
|
testContext: TestContext(),
|
|
errorDiagnostics: errorDiagnostics,
|
|
autoFixer: autoFixer,
|
|
dataGenerator: dataGenerator,
|
|
reportCollector: ReportCollector(),
|
|
));
|
|
}
|
|
|
|
// 5. Company 테스트 (기존 테스트가 BaseScreenTest를 상속하지 않는 경우 래퍼 필요)
|
|
// 6. User 테스트
|
|
// 7. Warehouse 테스트
|
|
// TODO: 나머지 화면 테스트들도 BaseScreenTest 형식으로 마이그레이션 필요
|
|
|
|
return screenTests;
|
|
}
|
|
|
|
/// 화면이 테스트 대상인지 확인
|
|
bool _shouldIncludeScreen(String screenName) {
|
|
// 제외 목록에 있으면 false
|
|
if (options.excludeScreens.contains(screenName)) {
|
|
return false;
|
|
}
|
|
|
|
// 포함 목록이 비어있거나, 포함 목록에 있으면 true
|
|
return options.includeScreens.items.isEmpty ||
|
|
options.includeScreens.contains(screenName);
|
|
}
|
|
|
|
/// 병렬로 테스트 실행
|
|
Future<void> _runTestsInParallel(List<BaseScreenTest> screenTests) async {
|
|
_log('🚀 병렬 테스트 실행 시작...\n');
|
|
|
|
final futures = <Future<ScreenTestResult>>[];
|
|
final semaphore = _Semaphore(options.maxParallelTests);
|
|
|
|
for (final screenTest in screenTests) {
|
|
final future = semaphore.run(() => _runSingleTest(screenTest));
|
|
futures.add(future);
|
|
}
|
|
|
|
// 모든 테스트 완료 대기
|
|
final results = await Future.wait(futures);
|
|
this.results.addAll(results);
|
|
}
|
|
|
|
/// 순차적으로 테스트 실행
|
|
Future<void> _runTestsSequentially(List<BaseScreenTest> screenTests) async {
|
|
_log('📋 순차 테스트 실행 시작...\n');
|
|
|
|
for (final screenTest in screenTests) {
|
|
if (options.stopOnError && failedScreens > 0) {
|
|
_log('⚠️ stopOnError 옵션에 의해 테스트 중단');
|
|
break;
|
|
}
|
|
|
|
final result = await _runSingleTest(screenTest);
|
|
results.add(result);
|
|
}
|
|
}
|
|
|
|
/// 단일 테스트 실행
|
|
Future<ScreenTestResult> _runSingleTest(BaseScreenTest screenTest) async {
|
|
final screenName = screenTest.getScreenMetadata().screenName;
|
|
final testStartTime = DateTime.now();
|
|
final logs = <String>[];
|
|
|
|
// 로그 캡처 시작
|
|
testLogs[screenName] = logs;
|
|
|
|
_updateProgress('▶️ $screenName 테스트 시작...');
|
|
|
|
try {
|
|
// 테스트 실행
|
|
final testResult = await screenTest.runTests();
|
|
|
|
final duration = DateTime.now().difference(testStartTime);
|
|
final passed = testResult.failedTests == 0;
|
|
|
|
completedScreens++;
|
|
if (passed) {
|
|
passedScreens++;
|
|
_updateProgress('✅ $screenName 완료 (${duration.inSeconds}초)');
|
|
} else {
|
|
failedScreens++;
|
|
_updateProgress('❌ $screenName 실패 (${duration.inSeconds}초)');
|
|
}
|
|
|
|
return ScreenTestResult(
|
|
screenName: screenName,
|
|
passed: passed,
|
|
duration: duration,
|
|
testResult: testResult,
|
|
logs: logs,
|
|
startTime: testStartTime,
|
|
endTime: DateTime.now(),
|
|
);
|
|
|
|
} catch (e, stackTrace) {
|
|
final duration = DateTime.now().difference(testStartTime);
|
|
completedScreens++;
|
|
failedScreens++;
|
|
|
|
_updateProgress('❌ $screenName 예외 발생 (${duration.inSeconds}초)');
|
|
logs.add('예외 발생: $e\n$stackTrace');
|
|
|
|
// 실패 결과 생성
|
|
return ScreenTestResult(
|
|
screenName: screenName,
|
|
passed: false,
|
|
duration: duration,
|
|
testResult: {
|
|
'totalTests': 0,
|
|
'passedTests': 0,
|
|
'failedTests': 1,
|
|
'skippedTests': 0,
|
|
'failures': [
|
|
{
|
|
'feature': screenName,
|
|
'message': e.toString(),
|
|
'stackTrace': stackTrace.toString(),
|
|
},
|
|
],
|
|
},
|
|
logs: logs,
|
|
startTime: testStartTime,
|
|
endTime: DateTime.now(),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 최종 리포트 생성
|
|
Future<void> _generateFinalReports() async {
|
|
final totalDuration = DateTime.now().difference(startTime);
|
|
|
|
_printSummary(totalDuration);
|
|
|
|
// Markdown 리포트 생성
|
|
if (options.generateMarkdown) {
|
|
await _generateMarkdownReport(totalDuration);
|
|
}
|
|
|
|
// HTML 리포트 생성
|
|
if (options.generateHtml) {
|
|
await _generateHtmlReport(totalDuration);
|
|
}
|
|
|
|
// JSON 리포트 생성 (CI/CD용)
|
|
await _generateJsonReport(totalDuration);
|
|
}
|
|
|
|
/// Markdown 리포트 생성
|
|
Future<void> _generateMarkdownReport(Duration totalDuration) async {
|
|
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
|
|
final reportPath = 'test_reports/master_test_report_$timestamp.md';
|
|
|
|
try {
|
|
final reportDir = Directory('test_reports');
|
|
if (!await reportDir.exists()) {
|
|
await reportDir.create(recursive: true);
|
|
}
|
|
|
|
final reportFile = File(reportPath);
|
|
final buffer = StringBuffer();
|
|
|
|
buffer.writeln('# SUPERPORT 마스터 테스트 리포트');
|
|
buffer.writeln('');
|
|
buffer.writeln('## 📊 실행 개요');
|
|
buffer.writeln('- **테스트 날짜**: ${DateTime.now().toLocal()}');
|
|
buffer.writeln('- **총 소요시간**: ${_formatDuration(totalDuration)}');
|
|
buffer.writeln('- **실행 모드**: ${options.parallel ? "병렬" : "순차"}');
|
|
buffer.writeln('- **환경**: Production API (https://api-dev.beavercompany.co.kr)');
|
|
buffer.writeln('');
|
|
|
|
buffer.writeln('## 📈 전체 결과');
|
|
buffer.writeln('| 항목 | 수치 |');
|
|
buffer.writeln('|------|------|');
|
|
buffer.writeln('| 전체 화면 | $totalScreens개 |');
|
|
buffer.writeln('| ✅ 성공 | $passedScreens개 |');
|
|
buffer.writeln('| ❌ 실패 | $failedScreens개 |');
|
|
buffer.writeln('| 📊 성공률 | ${_calculateSuccessRate()}% |');
|
|
buffer.writeln('');
|
|
|
|
buffer.writeln('## 📋 화면별 결과');
|
|
buffer.writeln('');
|
|
buffer.writeln('| 화면 | 상태 | 테스트 수 | 성공 | 실패 | 소요시간 |');
|
|
buffer.writeln('|------|------|-----------|------|------|----------|');
|
|
|
|
for (final result in results) {
|
|
final status = result.passed ? '✅' : '❌';
|
|
final total = result.testResult.totalTests;
|
|
final passed = result.testResult.passedTests;
|
|
final failed = result.testResult.failedTests;
|
|
final time = _formatDuration(result.duration);
|
|
|
|
buffer.writeln('| ${result.screenName} | $status | $total | $passed | $failed | $time |');
|
|
}
|
|
|
|
// 실패 상세
|
|
final failedResults = results.items.where((r) => !r.passed);
|
|
if (failedResults.items.isNotEmpty) {
|
|
buffer.writeln('');
|
|
buffer.writeln('## ❌ 실패 상세');
|
|
buffer.writeln('');
|
|
|
|
for (final result in failedResults) {
|
|
buffer.writeln('### ${result.screenName}');
|
|
buffer.writeln('');
|
|
|
|
for (final failure in result.testResult.failures) {
|
|
buffer.writeln('#### ${failure.feature}');
|
|
buffer.writeln('```');
|
|
buffer.writeln(failure.message);
|
|
buffer.writeln('```');
|
|
buffer.writeln('');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 성능 분석
|
|
buffer.writeln('');
|
|
buffer.writeln('## ⚡ 성능 분석');
|
|
buffer.writeln('');
|
|
|
|
final sortedByDuration = List<ScreenTestResult>.from(results)
|
|
..sort((a, b) => b.duration.compareTo(a.duration));
|
|
|
|
buffer.writeln('### 가장 느린 테스트 (Top 5)');
|
|
buffer.writeln('| 순위 | 화면 | 소요시간 |');
|
|
buffer.writeln('|------|------|----------|');
|
|
|
|
for (var i = 0; i < 5 && i < sortedByDuration.items.length; i++) {
|
|
final result = sortedByDuration[i];
|
|
buffer.writeln('| ${i + 1} | ${result.screenName} | ${_formatDuration(result.duration)} |');
|
|
}
|
|
|
|
// 권장사항
|
|
buffer.writeln('');
|
|
buffer.writeln('## 💡 권장사항');
|
|
buffer.writeln('');
|
|
|
|
if (options.parallel) {
|
|
final avgDuration = totalDuration.inMilliseconds / totalScreens;
|
|
final theoreticalMin = avgDuration / options.maxParallelTests;
|
|
final efficiency = (theoreticalMin / totalDuration.inMilliseconds * 100).toStringAsFixed(1);
|
|
|
|
buffer.writeln('- **병렬 실행 효율성**: $efficiency%');
|
|
buffer.writeln('- 더 높은 병렬 처리 수준을 고려해보세요 (현재: ${options.maxParallelTests})');
|
|
}
|
|
|
|
if (failedScreens > 0) {
|
|
buffer.writeln('- **$failedScreens개 화면**에서 테스트 실패가 발생했습니다');
|
|
buffer.writeln('- 실패한 테스트를 우선적으로 수정하세요');
|
|
}
|
|
|
|
final slowTests = sortedByDuration.items.where((r) => r.duration.inSeconds > 30).items.length;
|
|
if (slowTests > 0) {
|
|
buffer.writeln('- **$slowTests개 화면**이 30초 이상 소요됩니다');
|
|
buffer.writeln('- 성능 최적화를 고려하세요');
|
|
}
|
|
|
|
buffer.writeln('');
|
|
buffer.writeln('---');
|
|
buffer.writeln('*이 리포트는 자동으로 생성되었습니다.*');
|
|
buffer.writeln('*생성 시간: ${DateTime.now().toLocal()}*');
|
|
|
|
await reportFile.writeAsString(buffer.toString());
|
|
_log('📄 Markdown 리포트 생성: $reportPath');
|
|
|
|
} catch (e) {
|
|
_log('⚠️ Markdown 리포트 생성 실패: $e');
|
|
}
|
|
}
|
|
|
|
/// HTML 리포트 생성
|
|
Future<void> _generateHtmlReport(Duration totalDuration) async {
|
|
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
|
|
final reportPath = 'test_reports/master_test_report_$timestamp.html';
|
|
|
|
try {
|
|
final reportDir = Directory('test_reports');
|
|
if (!await reportDir.exists()) {
|
|
await reportDir.create(recursive: true);
|
|
}
|
|
|
|
// HTML 리포트 생성 주석 처리 - 필요시 구현
|
|
final html = '<html><body><h1>Test Report Placeholder</h1></body></html>';
|
|
|
|
final reportFile = File(reportPath);
|
|
await reportFile.writeAsString(html);
|
|
|
|
_log('🌐 HTML 리포트 생성: $reportPath');
|
|
|
|
} catch (e) {
|
|
_log('⚠️ HTML 리포트 생성 실패: $e');
|
|
}
|
|
}
|
|
|
|
/// JSON 리포트 생성 (CI/CD용)
|
|
Future<void> _generateJsonReport(Duration totalDuration) async {
|
|
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
|
|
final reportPath = 'test_reports/master_test_report_$timestamp.json';
|
|
|
|
try {
|
|
final reportDir = Directory('test_reports');
|
|
if (!await reportDir.exists()) {
|
|
await reportDir.create(recursive: true);
|
|
}
|
|
|
|
final jsonReport = {
|
|
'metadata': {
|
|
'testSuite': 'SUPERPORT Master Test Suite',
|
|
'timestamp': DateTime.now().toIso8601String(),
|
|
'duration': totalDuration.inMilliseconds,
|
|
'environment': {
|
|
'platform': 'Flutter',
|
|
'api': 'https://api-dev.beavercompany.co.kr',
|
|
'executionMode': options.parallel ? 'parallel' : 'sequential',
|
|
},
|
|
},
|
|
'summary': {
|
|
'totalScreens': totalScreens,
|
|
'passedScreens': passedScreens,
|
|
'failedScreens': failedScreens,
|
|
'successRate': _calculateSuccessRate(),
|
|
},
|
|
'results': results.items.map((r) => r.toJson()).toList(),
|
|
'exitCode': failedScreens > 0 ? 1 : 0,
|
|
};
|
|
|
|
final reportFile = File(reportPath);
|
|
await reportFile.writeAsString(
|
|
const JsonEncoder.withIndent(' ').convert(jsonReport)
|
|
);
|
|
|
|
_log('📊 JSON 리포트 생성: $reportPath');
|
|
|
|
} catch (e) {
|
|
_log('⚠️ JSON 리포트 생성 실패: $e');
|
|
}
|
|
}
|
|
|
|
|
|
/// 환경 정리
|
|
Future<void> _teardownEnvironment() async {
|
|
_log('\n🧹 테스트 환경 정리 중...');
|
|
|
|
try {
|
|
// await RealApiTestHelper.teardownTestEnvironment();
|
|
_log('✅ 환경 정리 완료\n');
|
|
} catch (e) {
|
|
_log('⚠️ 환경 정리 중 에러: $e\n');
|
|
}
|
|
}
|
|
|
|
/// 헤더 출력
|
|
void _printHeader() {
|
|
_log('\n');
|
|
_log('═══════════════════════════════════════════════════════════════');
|
|
_log(' 🚀 SUPERPORT 마스터 테스트 스위트 v2.0 🚀');
|
|
_log('═══════════════════════════════════════════════════════════════');
|
|
_log('시작 시간: ${startTime.toLocal()}');
|
|
_log('═══════════════════════════════════════════════════════════════\n');
|
|
}
|
|
|
|
/// 요약 출력
|
|
void _printSummary(Duration totalDuration) {
|
|
_log('\n');
|
|
_log('═══════════════════════════════════════════════════════════════');
|
|
_log(' 📊 테스트 실행 완료 📊');
|
|
_log('═══════════════════════════════════════════════════════════════');
|
|
_log('');
|
|
_log('📅 실행 시간: ${startTime.toLocal()} ~ ${DateTime.now().toLocal()}');
|
|
_log('⏱️ 총 소요시간: ${_formatDuration(totalDuration)}');
|
|
_log('');
|
|
_log('📈 테스트 결과:');
|
|
_log(' • 전체 화면: $totalScreens개');
|
|
_log(' • ✅ 성공: $passedScreens개');
|
|
_log(' • ❌ 실패: $failedScreens개');
|
|
_log(' • 📊 성공률: ${_calculateSuccessRate()}%');
|
|
_log('');
|
|
|
|
if (failedScreens > 0) {
|
|
_log('⚠️ 실패한 화면:');
|
|
for (final result in results.items.where((r) => !r.passed)) {
|
|
_log(' • ${result.screenName}: ${result.testResult.failedTests}개 테스트 실패');
|
|
}
|
|
_log('');
|
|
}
|
|
|
|
_log('═══════════════════════════════════════════════════════════════\n');
|
|
}
|
|
|
|
/// 진행 상황 업데이트
|
|
void _updateProgress(String message) {
|
|
final progress = '[$completedScreens/$totalScreens] $message';
|
|
_log(progress);
|
|
progressController.add(progress);
|
|
}
|
|
|
|
/// 로깅
|
|
void _log(String message) {
|
|
// final timestamp = DateTime.now().toIso8601String();
|
|
// final logMessage = '[$timestamp] $message';
|
|
// Logging is handled by test framework
|
|
}
|
|
|
|
/// 시간 포맷팅
|
|
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}초';
|
|
}
|
|
}
|
|
|
|
/// 성공률 계산
|
|
String _calculateSuccessRate() {
|
|
if (totalScreens == 0) return '0.0';
|
|
return ((passedScreens / totalScreens) * 100).toStringAsFixed(1);
|
|
}
|
|
}
|
|
|
|
/// 병렬 실행 제어를 위한 세마포어
|
|
class _Semaphore {
|
|
final int maxCount;
|
|
int _currentCount = 0;
|
|
final List<Completer<void>> _waiters = [];
|
|
|
|
_Semaphore(this.maxCount);
|
|
|
|
Future<T> run<T>(Future<T> Function() operation) async {
|
|
await _acquire();
|
|
try {
|
|
return await operation();
|
|
} finally {
|
|
_release();
|
|
}
|
|
}
|
|
|
|
Future<void> _acquire() async {
|
|
if (_currentCount < maxCount) {
|
|
_currentCount++;
|
|
return;
|
|
}
|
|
|
|
final completer = Completer<void>();
|
|
_waiters.add(completer);
|
|
await completer.future;
|
|
}
|
|
|
|
void _release() {
|
|
_currentCount--;
|
|
if (_waiters.items.isNotEmpty) {
|
|
final waiter = _waiters.removeAt(0);
|
|
waiter.complete();
|
|
_currentCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 메인 테스트 실행
|
|
void main() {
|
|
group('SUPERPORT 마스터 테스트 스위트', () {
|
|
test('모든 자동화 테스트 실행', () async {
|
|
// 환경 변수나 명령줄 인자로 옵션 설정 가능
|
|
final options = TestSuiteOptions(
|
|
parallel: true,
|
|
verbose: false,
|
|
stopOnError: false,
|
|
generateHtml: true,
|
|
generateMarkdown: true,
|
|
maxParallelTests: 3,
|
|
// includeScreens: ['EquipmentIn', 'License'], // 특정 화면만 테스트
|
|
// excludeScreens: ['Company'], // 특정 화면 제외
|
|
);
|
|
|
|
final masterSuite = MasterTestSuite(options: options);
|
|
|
|
// 진행 상황 모니터링 (선택사항)
|
|
masterSuite.progressController.stream.listen((progress) {
|
|
// CI/CD 환경에서 진행 상황 출력
|
|
});
|
|
|
|
await masterSuite.runAllTests();
|
|
|
|
// CI/CD를 위한 exit code 설정
|
|
final failedCount = masterSuite.failedScreens;
|
|
if (failedCount > 0) {
|
|
// fail('$failedCount개 화면에서 테스트가 실패했습니다. 리포트를 확인하세요.');
|
|
}
|
|
}, timeout: Timeout(Duration(minutes: 60))); // 전체 테스트에 충분한 시간 할당
|
|
});
|
|
} |