- 모든 서비스 메서드 시그니처를 실제 구현에 맞게 수정 - 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
695 lines
20 KiB
Markdown
695 lines
20 KiB
Markdown
# SuperPort Real API 자동화 테스트 계획서
|
|
|
|
## 📋 개요
|
|
|
|
본 문서는 SuperPort 애플리케이션의 모든 화면과 기능에 대한 Real API 기반 자동화 테스트 계획을 정의합니다.
|
|
|
|
### 핵심 원칙
|
|
- ✅ **Real API 사용**: Mock 데이터 사용 금지, 실제 서버와 통신
|
|
- ✅ **자동 오류 복구**: 에러 발생시 자동 진단 및 수정
|
|
- ✅ **전체 기능 커버리지**: 모든 화면의 모든 기능 테스트
|
|
- ✅ **재사용 가능한 프레임워크**: 확장 가능한 테스트 구조
|
|
|
|
## 🏗️ 테스트 프레임워크 아키텍처
|
|
|
|
### 1. 기본 구조
|
|
```
|
|
test/
|
|
├── integration/
|
|
│ ├── automated/
|
|
│ │ ├── framework/
|
|
│ │ │ ├── screen_test_framework.dart # 핵심 프레임워크
|
|
│ │ │ ├── api_error_diagnostics.dart # 에러 진단 시스템
|
|
│ │ │ ├── auto_fixer.dart # 자동 수정 시스템
|
|
│ │ │ └── test_data_generator.dart # 데이터 생성기
|
|
│ │ ├── screens/
|
|
│ │ │ ├── login_automated_test.dart
|
|
│ │ │ ├── dashboard_automated_test.dart
|
|
│ │ │ ├── equipment_automated_test.dart
|
|
│ │ │ ├── company_automated_test.dart
|
|
│ │ │ ├── user_automated_test.dart
|
|
│ │ │ ├── warehouse_automated_test.dart
|
|
│ │ │ └── license_automated_test.dart
|
|
│ │ └── test_runner.dart # 통합 실행기
|
|
```
|
|
|
|
### 2. 핵심 컴포넌트
|
|
|
|
#### ScreenTestFramework
|
|
```dart
|
|
class ScreenTestFramework {
|
|
// 화면 분석 및 테스트 가능한 액션 추출
|
|
static Future<List<TestableAction>> analyzeScreen(Widget screen);
|
|
|
|
// 필수 필드 자동 감지 및 입력
|
|
static Future<Map<String, dynamic>> generateRequiredData(String endpoint);
|
|
|
|
// API 호출 및 응답 검증
|
|
static Future<TestResult> executeApiCall(ApiRequest request);
|
|
|
|
// 에러 처리 및 재시도
|
|
static Future<TestResult> executeWithRetry(
|
|
Future<dynamic> Function() action,
|
|
{int maxRetries = 3}
|
|
);
|
|
}
|
|
```
|
|
|
|
#### ApiErrorDiagnostics
|
|
```dart
|
|
class ApiErrorDiagnostics {
|
|
static ErrorDiagnosis diagnose(DioException error) {
|
|
// 에러 타입 분류
|
|
// - 400: 검증 오류 (필수 필드, 타입 불일치)
|
|
// - 401: 인증 오류
|
|
// - 403: 권한 오류
|
|
// - 404: 리소스 없음
|
|
// - 409: 중복 오류
|
|
// - 422: 비즈니스 로직 오류
|
|
// - 500: 서버 오류
|
|
}
|
|
}
|
|
```
|
|
|
|
#### AutoFixer
|
|
```dart
|
|
class AutoFixer {
|
|
static Future<Map<String, dynamic>> fix(
|
|
ErrorDiagnosis diagnosis,
|
|
Map<String, dynamic> originalData,
|
|
) {
|
|
// 에러 타입별 자동 수정 로직
|
|
// - 필수 필드 누락: 기본값 추가
|
|
// - 타입 불일치: 타입 변환
|
|
// - 참조 무결성: 관련 데이터 생성
|
|
// - 중복 오류: 고유값 재생성
|
|
}
|
|
}
|
|
```
|
|
|
|
## 📱 화면별 테스트 계획
|
|
|
|
### 1. 🔐 로그인 화면 (Login Screen)
|
|
|
|
#### 테스트 시나리오
|
|
1. **정상 로그인**
|
|
- 유효한 자격증명으로 로그인
|
|
- 액세스 토큰 저장 확인
|
|
- 대시보드 이동 확인
|
|
|
|
2. **로그인 실패 처리**
|
|
- 잘못된 이메일/비밀번호
|
|
- 비활성 계정
|
|
- 네트워크 오류
|
|
|
|
3. **토큰 관리**
|
|
- 토큰 만료시 자동 갱신
|
|
- 로그아웃 후 토큰 삭제
|
|
|
|
#### 자동화 테스트 코드
|
|
```dart
|
|
test('로그인 전체 프로세스 자동화 테스트', () async {
|
|
// 1. 테스트 데이터 준비
|
|
final testCredentials = {
|
|
'email': 'admin@superport.kr',
|
|
'password': 'admin123!',
|
|
};
|
|
|
|
// 2. 로그인 시도
|
|
try {
|
|
final response = await authService.login(testCredentials);
|
|
expect(response.accessToken, isNotEmpty);
|
|
} catch (error) {
|
|
// 3. 에러 진단
|
|
final diagnosis = ApiErrorDiagnostics.diagnose(error);
|
|
|
|
// 4. 자동 수정 (예: 비밀번호 재설정 필요)
|
|
if (diagnosis.errorType == ErrorType.invalidCredentials) {
|
|
// 관리자 계정으로 비밀번호 재설정 API 호출
|
|
await resetTestUserPassword();
|
|
|
|
// 5. 재시도
|
|
final response = await authService.login(testCredentials);
|
|
expect(response.accessToken, isNotEmpty);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### 2. 📊 대시보드 화면 (Dashboard Screen)
|
|
|
|
#### 테스트 시나리오
|
|
1. **통계 데이터 조회**
|
|
- 전체 통계 로딩
|
|
- 각 카드별 데이터 검증
|
|
- 실시간 업데이트 확인
|
|
|
|
2. **최근 활동 표시**
|
|
- 최근 활동 목록 조회
|
|
- 페이지네이션
|
|
- 필터링
|
|
|
|
3. **차트 및 그래프**
|
|
- 장비 상태 분포
|
|
- 월별 입출고 현황
|
|
- 라이선스 만료 예정
|
|
|
|
#### 자동화 테스트 코드
|
|
```dart
|
|
test('대시보드 데이터 로딩 자동화 테스트', () async {
|
|
// 1. 로그인
|
|
await RealApiTestHelper.loginAndGetToken();
|
|
|
|
// 2. 대시보드 데이터 조회
|
|
try {
|
|
final stats = await dashboardService.getOverviewStats();
|
|
expect(stats.totalEquipment, greaterThanOrEqualTo(0));
|
|
expect(stats.totalUsers, greaterThanOrEqualTo(0));
|
|
} catch (error) {
|
|
// 3. 에러 진단
|
|
final diagnosis = ApiErrorDiagnostics.diagnose(error);
|
|
|
|
// 4. 자동 수정 (예: 초기 데이터 생성)
|
|
if (diagnosis.errorType == ErrorType.noData) {
|
|
await createInitialTestData();
|
|
|
|
// 5. 재시도
|
|
final stats = await dashboardService.getOverviewStats();
|
|
expect(stats, isNotNull);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### 3. 🛠 장비 관리 화면 (Equipment Management)
|
|
|
|
#### 테스트 시나리오
|
|
|
|
##### 3.1 장비 입고
|
|
```dart
|
|
test('장비 입고 전체 프로세스 자동화 테스트', () async {
|
|
// 1. 사전 데이터 준비
|
|
final company = await prepareTestCompany();
|
|
final warehouse = await prepareTestWarehouse(company.id);
|
|
|
|
// 2. 장비 입고 데이터 생성
|
|
final equipmentData = TestDataGenerator.generateEquipmentInData(
|
|
companyId: company.id,
|
|
warehouseId: warehouse.id,
|
|
);
|
|
|
|
// 3. 입고 실행
|
|
try {
|
|
final response = await equipmentService.createEquipment(equipmentData);
|
|
expect(response.id, isNotNull);
|
|
expect(response.status, equals('I'));
|
|
} catch (error) {
|
|
// 4. 에러 진단 및 수정
|
|
final diagnosis = ApiErrorDiagnostics.diagnose(error);
|
|
final fixedData = await AutoFixer.fix(diagnosis, equipmentData);
|
|
|
|
// 5. 재시도
|
|
final response = await equipmentService.createEquipment(fixedData);
|
|
expect(response.id, isNotNull);
|
|
}
|
|
});
|
|
```
|
|
|
|
##### 3.2 장비 출고
|
|
```dart
|
|
test('장비 출고 전체 프로세스 자동화 테스트', () async {
|
|
// 1. 입고된 장비 준비
|
|
final equipment = await prepareInStockEquipment();
|
|
|
|
// 2. 출고 데이터 생성
|
|
final outData = {
|
|
'equipment_id': equipment.id,
|
|
'transaction_type': 'O',
|
|
'quantity': 1,
|
|
'out_company_id': await getOrCreateTestCompany().id,
|
|
'out_date': DateTime.now().toIso8601String(),
|
|
};
|
|
|
|
// 3. 출고 실행
|
|
try {
|
|
final response = await equipmentService.createEquipmentHistory(outData);
|
|
expect(response.transactionType, equals('O'));
|
|
} catch (error) {
|
|
// 4. 에러 처리
|
|
final diagnosis = ApiErrorDiagnostics.diagnose(error);
|
|
|
|
if (diagnosis.errorType == ErrorType.insufficientStock) {
|
|
// 재고 부족시 입고 먼저 실행
|
|
await createAdditionalStock(equipment.id);
|
|
|
|
// 재시도
|
|
final response = await equipmentService.createEquipmentHistory(outData);
|
|
expect(response, isNotNull);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
##### 3.3 장비 목록 조회
|
|
```dart
|
|
test('장비 목록 필터링 및 검색 테스트', () async {
|
|
// 1. 다양한 상태의 테스트 장비 생성
|
|
await createEquipmentsWithVariousStatuses();
|
|
|
|
// 2. 필터별 조회
|
|
final filters = [
|
|
{'status': 'I'}, // 입고
|
|
{'status': 'O'}, // 출고
|
|
{'status': 'R'}, // 대여
|
|
{'company_id': 1}, // 회사별
|
|
{'search': '노트북'}, // 검색어
|
|
];
|
|
|
|
for (final filter in filters) {
|
|
try {
|
|
final equipments = await equipmentService.getEquipments(filter);
|
|
expect(equipments, isNotEmpty);
|
|
|
|
// 필터 조건 검증
|
|
if (filter['status'] != null) {
|
|
expect(equipments.every((e) => e.status == filter['status']), isTrue);
|
|
}
|
|
} catch (error) {
|
|
// 에러시 테스트 데이터 생성 후 재시도
|
|
await createTestDataForFilter(filter);
|
|
final equipments = await equipmentService.getEquipments(filter);
|
|
expect(equipments, isNotEmpty);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### 4. 🏢 회사 관리 화면 (Company Management)
|
|
|
|
#### 테스트 시나리오
|
|
```dart
|
|
test('회사 및 지점 관리 전체 프로세스', () async {
|
|
// 1. 회사 생성
|
|
final companyData = TestDataGenerator.generateCompanyData();
|
|
|
|
try {
|
|
final company = await companyService.createCompany(companyData);
|
|
expect(company.id, isNotNull);
|
|
|
|
// 2. 지점 추가
|
|
final branchData = TestDataGenerator.generateBranchData(company.id);
|
|
final branch = await companyService.createBranch(branchData);
|
|
expect(branch.companyId, equals(company.id));
|
|
|
|
// 3. 연락처 정보 추가
|
|
final contactData = {
|
|
'company_id': company.id,
|
|
'name': '담당자',
|
|
'phone': '010-1234-5678',
|
|
'email': 'contact@test.com',
|
|
};
|
|
await companyService.addContact(contactData);
|
|
|
|
// 4. 회사 정보 수정
|
|
company.name = '${company.name} (수정됨)';
|
|
final updated = await companyService.updateCompany(company);
|
|
expect(updated.name, contains('수정됨'));
|
|
|
|
} catch (error) {
|
|
final diagnosis = ApiErrorDiagnostics.diagnose(error);
|
|
|
|
// 중복 회사명 오류시 처리
|
|
if (diagnosis.errorType == ErrorType.duplicate) {
|
|
companyData['name'] = '${companyData['name']}_${DateTime.now().millisecondsSinceEpoch}';
|
|
final company = await companyService.createCompany(companyData);
|
|
expect(company.id, isNotNull);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### 5. 👥 사용자 관리 화면 (User Management)
|
|
|
|
#### 테스트 시나리오
|
|
```dart
|
|
test('사용자 CRUD 및 권한 관리 테스트', () async {
|
|
// 1. 사용자 생성
|
|
final company = await prepareTestCompany();
|
|
final userData = TestDataGenerator.generateUserData(
|
|
companyId: company.id,
|
|
role: 'M', // Member
|
|
);
|
|
|
|
try {
|
|
final user = await userService.createUser(userData);
|
|
expect(user.id, isNotNull);
|
|
|
|
// 2. 권한 변경 (Member -> Admin)
|
|
user.role = 'A';
|
|
await userService.updateUser(user);
|
|
|
|
// 3. 비밀번호 변경
|
|
await userService.changePassword(user.id, {
|
|
'current_password': userData['password'],
|
|
'new_password': 'NewPassword123!',
|
|
});
|
|
|
|
// 4. 계정 비활성화
|
|
await userService.changeUserStatus(user.id, false);
|
|
|
|
// 5. 삭제
|
|
await userService.deleteUser(user.id);
|
|
|
|
} catch (error) {
|
|
final diagnosis = ApiErrorDiagnostics.diagnose(error);
|
|
|
|
// 이메일 중복 오류시
|
|
if (diagnosis.errorType == ErrorType.duplicateEmail) {
|
|
userData['email'] = TestDataGenerator.generateUniqueEmail();
|
|
final user = await userService.createUser(userData);
|
|
expect(user.id, isNotNull);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### 6. 📍 창고 위치 관리 (Warehouse Location)
|
|
|
|
#### 테스트 시나리오
|
|
```dart
|
|
test('창고 위치 계층 구조 관리 테스트', () async {
|
|
// 1. 메인 창고 생성
|
|
final mainWarehouse = await warehouseService.createLocation({
|
|
'name': '메인 창고',
|
|
'code': 'MAIN',
|
|
'level': 1,
|
|
});
|
|
|
|
// 2. 하위 구역 생성
|
|
final zones = ['A', 'B', 'C'];
|
|
for (final zone in zones) {
|
|
try {
|
|
final subLocation = await warehouseService.createLocation({
|
|
'name': '구역 $zone',
|
|
'code': 'MAIN-$zone',
|
|
'parent_id': mainWarehouse.id,
|
|
'level': 2,
|
|
});
|
|
|
|
// 3. 선반 생성
|
|
for (int shelf = 1; shelf <= 5; shelf++) {
|
|
await warehouseService.createLocation({
|
|
'name': '선반 $shelf',
|
|
'code': 'MAIN-$zone-$shelf',
|
|
'parent_id': subLocation.id,
|
|
'level': 3,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
// 코드 중복시 자동 수정
|
|
final diagnosis = ApiErrorDiagnostics.diagnose(error);
|
|
if (diagnosis.errorType == ErrorType.duplicateCode) {
|
|
// 타임스탬프 추가하여 유니크하게
|
|
const newCode = 'MAIN-$zone-${DateTime.now().millisecondsSinceEpoch}';
|
|
await warehouseService.createLocation({
|
|
'name': '구역 $zone',
|
|
'code': newCode,
|
|
'parent_id': mainWarehouse.id,
|
|
'level': 2,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### 7. 📜 라이선스 관리 (License Management)
|
|
|
|
#### 테스트 시나리오
|
|
```dart
|
|
test('라이선스 생명주기 관리 테스트', () async {
|
|
// 1. 라이선스 생성
|
|
final company = await prepareTestCompany();
|
|
final licenseData = TestDataGenerator.generateLicenseData(
|
|
companyId: company.id,
|
|
expiryDays: 30, // 30일 후 만료
|
|
);
|
|
|
|
try {
|
|
final license = await licenseService.createLicense(licenseData);
|
|
expect(license.id, isNotNull);
|
|
|
|
// 2. 사용자에게 할당
|
|
final user = await prepareTestUser(company.id);
|
|
await licenseService.assignLicense(license.id, user.id);
|
|
|
|
// 3. 만료 예정 라이선스 조회
|
|
final expiringLicenses = await licenseService.getExpiringLicenses(days: 30);
|
|
expect(expiringLicenses.any((l) => l.id == license.id), isTrue);
|
|
|
|
// 4. 라이선스 갱신
|
|
license.expiryDate = DateTime.now().add(Duration(days: 365));
|
|
await licenseService.updateLicense(license);
|
|
|
|
// 5. 할당 해제
|
|
await licenseService.unassignLicense(license.id);
|
|
|
|
} catch (error) {
|
|
final diagnosis = ApiErrorDiagnostics.diagnose(error);
|
|
|
|
// 라이선스 키 중복시
|
|
if (diagnosis.errorType == ErrorType.duplicateLicenseKey) {
|
|
licenseData['license_key'] = TestDataGenerator.generateUniqueLicenseKey();
|
|
final license = await licenseService.createLicense(licenseData);
|
|
expect(license.id, isNotNull);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
## 🔧 에러 자동 진단 및 수정 시스템
|
|
|
|
### 에러 타입별 자동 수정 전략
|
|
|
|
#### 1. 필수 필드 누락 (400 Bad Request)
|
|
```dart
|
|
if (error.response?.data['missing_fields'] != null) {
|
|
final missingFields = error.response.data['missing_fields'] as List;
|
|
for (final field in missingFields) {
|
|
switch (field) {
|
|
case 'equipment_number':
|
|
data['equipment_number'] = 'EQ-${DateTime.now().millisecondsSinceEpoch}';
|
|
break;
|
|
case 'manufacturer':
|
|
data['manufacturer'] = await getOrCreateManufacturer('기본제조사');
|
|
break;
|
|
case 'warehouse_location_id':
|
|
data['warehouse_location_id'] = await getOrCreateWarehouse();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 2. 타입 불일치 (422 Unprocessable Entity)
|
|
```dart
|
|
if (error.response?.data['type_errors'] != null) {
|
|
final typeErrors = error.response.data['type_errors'] as Map;
|
|
typeErrors.forEach((field, expectedType) {
|
|
switch (expectedType) {
|
|
case 'integer':
|
|
data[field] = int.tryParse(data[field].toString()) ?? 0;
|
|
break;
|
|
case 'datetime':
|
|
data[field] = DateTime.parse(data[field].toString()).toIso8601String();
|
|
break;
|
|
case 'boolean':
|
|
data[field] = data[field].toString().toLowerCase() == 'true';
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
```
|
|
|
|
#### 3. 참조 무결성 오류 (409 Conflict)
|
|
```dart
|
|
if (error.response?.data['foreign_key_error'] != null) {
|
|
final fkError = error.response.data['foreign_key_error'];
|
|
switch (fkError['table']) {
|
|
case 'companies':
|
|
data[fkError['field']] = await createTestCompany().id;
|
|
break;
|
|
case 'warehouse_locations':
|
|
data[fkError['field']] = await createTestWarehouse().id;
|
|
break;
|
|
case 'users':
|
|
data[fkError['field']] = await createTestUser().id;
|
|
break;
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 4. 중복 데이터 (409 Conflict)
|
|
```dart
|
|
if (error.response?.data['duplicate_field'] != null) {
|
|
final duplicateField = error.response.data['duplicate_field'];
|
|
switch (duplicateField) {
|
|
case 'email':
|
|
data['email'] = '${data['email'].split('@')[0]}_${DateTime.now().millisecondsSinceEpoch}@test.com';
|
|
break;
|
|
case 'serial_number':
|
|
data['serial_number'] = 'SN-${DateTime.now().millisecondsSinceEpoch}-${Random().nextInt(9999)}';
|
|
break;
|
|
case 'license_key':
|
|
data['license_key'] = UUID.v4();
|
|
break;
|
|
}
|
|
}
|
|
```
|
|
|
|
## 📊 테스트 실행 및 리포팅
|
|
|
|
### 통합 테스트 실행기
|
|
```dart
|
|
// test/integration/automated/test_runner.dart
|
|
class AutomatedTestRunner {
|
|
static Future<TestReport> runAllTests() async {
|
|
final results = <ScreenTestResult>[];
|
|
|
|
// 모든 화면 테스트 실행
|
|
results.add(await LoginAutomatedTest.run());
|
|
results.add(await DashboardAutomatedTest.run());
|
|
results.add(await EquipmentAutomatedTest.run());
|
|
results.add(await CompanyAutomatedTest.run());
|
|
results.add(await UserAutomatedTest.run());
|
|
results.add(await WarehouseAutomatedTest.run());
|
|
results.add(await LicenseAutomatedTest.run());
|
|
|
|
// 리포트 생성
|
|
return TestReport(
|
|
totalTests: results.length,
|
|
passed: results.where((r) => r.passed).length,
|
|
failed: results.where((r) => !r.passed).length,
|
|
autoFixed: results.expand((r) => r.autoFixedErrors).length,
|
|
duration: results.fold(Duration.zero, (sum, r) => sum + r.duration),
|
|
details: results,
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 테스트 리포트 형식
|
|
```json
|
|
{
|
|
"timestamp": "2025-01-21T10:30:00Z",
|
|
"environment": "test",
|
|
"summary": {
|
|
"totalScreens": 7,
|
|
"totalTests": 156,
|
|
"passed": 148,
|
|
"failed": 8,
|
|
"autoFixed": 23,
|
|
"duration": "5m 32s"
|
|
},
|
|
"screens": [
|
|
{
|
|
"name": "Equipment Management",
|
|
"tests": 32,
|
|
"passed": 29,
|
|
"failed": 3,
|
|
"autoFixed": 7,
|
|
"errors": [
|
|
{
|
|
"test": "장비 입고 - 필수 필드 누락",
|
|
"error": "Missing required field: manufacturer",
|
|
"fixed": true,
|
|
"fixApplied": "Added default manufacturer"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## 🚀 CI/CD 통합
|
|
|
|
### GitHub Actions 설정
|
|
```yaml
|
|
name: Automated API Tests
|
|
|
|
on:
|
|
push:
|
|
branches: [main, develop]
|
|
pull_request:
|
|
branches: [main]
|
|
schedule:
|
|
- cron: '0 0 * * *' # 매일 자정 실행
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- name: Setup Flutter
|
|
uses: subosito/flutter-action@v2
|
|
with:
|
|
flutter-version: '3.x'
|
|
|
|
- name: Install dependencies
|
|
run: flutter pub get
|
|
|
|
- name: Run automated tests
|
|
run: flutter test test/integration/automated/test_runner.dart
|
|
env:
|
|
API_BASE_URL: ${{ secrets.TEST_API_URL }}
|
|
TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
|
|
TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
|
|
|
|
- name: Upload test report
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: test-report
|
|
path: test-report.json
|
|
|
|
- name: Notify on failure
|
|
if: failure()
|
|
uses: 8398a7/action-slack@v3
|
|
with:
|
|
status: ${{ job.status }}
|
|
text: 'Automated API tests failed!'
|
|
```
|
|
|
|
## 📈 성공 지표
|
|
|
|
1. **테스트 커버리지**: 모든 화면의 95% 이상 기능 커버
|
|
2. **자동 복구율**: 발생한 에러의 80% 이상 자동 수정
|
|
3. **실행 시간**: 전체 테스트 10분 이내 완료
|
|
4. **안정성**: 연속 100회 실행시 95% 이상 성공률
|
|
|
|
## 🔄 향후 확장 계획
|
|
|
|
1. **성능 테스트 추가**
|
|
- 부하 테스트
|
|
- 응답 시간 측정
|
|
- 동시성 테스트
|
|
|
|
2. **보안 테스트 통합**
|
|
- SQL Injection 테스트
|
|
- XSS 방어 테스트
|
|
- 권한 우회 시도
|
|
|
|
3. **UI 자동화 연동**
|
|
- Widget 테스트와 통합
|
|
- 스크린샷 비교
|
|
- 시각적 회귀 테스트
|
|
|
|
4. **AI 기반 테스트 생성**
|
|
- 사용 패턴 학습
|
|
- 엣지 케이스 자동 발견
|
|
- 테스트 시나리오 추천
|
|
|
|
---
|
|
|
|
본 계획서는 SuperPort 애플리케이션의 품질 보증을 위한 포괄적인 자동화 테스트 전략을 제공합니다. 모든 테스트는 실제 API를 사용하며, 발생하는 오류를 자동으로 진단하고 수정하여 안정적인 테스트 환경을 보장합니다. |