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,402 @@
import '../models/report_models.dart';
/// HTML 리포트 생성기
class HtmlReportGenerator {
/// 기본 테스트 리포트를 HTML로 변환
Future<String> generateReport(BasicTestReport report) async {
final buffer = StringBuffer();
// HTML 헤더
buffer.writeln('<!DOCTYPE html>');
buffer.writeln('<html lang="ko">');
buffer.writeln('<head>');
buffer.writeln(' <meta charset="UTF-8">');
buffer.writeln(' <meta name="viewport" content="width=device-width, initial-scale=1.0">');
buffer.writeln(' <title>SUPERPORT 테스트 리포트 - ${report.testName}</title>');
buffer.writeln(' <style>');
buffer.writeln(_generateCss());
buffer.writeln(' </style>');
buffer.writeln('</head>');
buffer.writeln('<body>');
// 리포트 컨테이너
buffer.writeln(' <div class="container">');
// 헤더
buffer.writeln(' <header class="report-header">');
buffer.writeln(' <h1>🚀 ${report.testName}</h1>');
buffer.writeln(' <div class="header-info">');
buffer.writeln(' <span class="date">생성 시간: ${report.endTime.toLocal()}</span>');
buffer.writeln(' <span class="duration">소요 시간: ${_formatDuration(report.duration)}</span>');
buffer.writeln(' </div>');
buffer.writeln(' </header>');
// 요약 섹션
buffer.writeln(' <section class="summary">');
buffer.writeln(' <h2>📊 테스트 요약</h2>');
buffer.writeln(' <div class="summary-cards">');
buffer.writeln(' <div class="card total">');
buffer.writeln(' <div class="card-value">${report.testResult.totalTests}</div>');
buffer.writeln(' <div class="card-label">전체 테스트</div>');
buffer.writeln(' </div>');
buffer.writeln(' <div class="card success">');
buffer.writeln(' <div class="card-value">${report.testResult.passedTests}</div>');
buffer.writeln(' <div class="card-label">성공</div>');
buffer.writeln(' </div>');
buffer.writeln(' <div class="card failure">');
buffer.writeln(' <div class="card-value">${report.testResult.failedTests}</div>');
buffer.writeln(' <div class="card-label">실패</div>');
buffer.writeln(' </div>');
buffer.writeln(' <div class="card skipped">');
buffer.writeln(' <div class="card-value">${report.testResult.skippedTests}</div>');
buffer.writeln(' <div class="card-label">건너뜀</div>');
buffer.writeln(' </div>');
buffer.writeln(' </div>');
// 성공률 바
final successRate = report.testResult.totalTests > 0
? (report.testResult.passedTests / report.testResult.totalTests * 100).toStringAsFixed(1)
: '0.0';
buffer.writeln(' <div class="progress-bar">');
buffer.writeln(' <div class="progress-fill" style="width: $successRate%"></div>');
buffer.writeln(' <div class="progress-text">성공률: $successRate%</div>');
buffer.writeln(' </div>');
buffer.writeln(' </section>');
// 실패 상세
if (report.testResult.failures.isNotEmpty) {
buffer.writeln(' <section class="failures">');
buffer.writeln(' <h2>❌ 실패한 테스트</h2>');
buffer.writeln(' <div class="failure-list">');
for (final failure in report.testResult.failures) {
buffer.writeln(' <div class="failure-item">');
buffer.writeln(' <h3>${failure.feature}</h3>');
buffer.writeln(' <pre class="failure-message">${_escapeHtml(failure.message)}</pre>');
if (failure.stackTrace != null) {
buffer.writeln(' <details>');
buffer.writeln(' <summary>스택 트레이스</summary>');
buffer.writeln(' <pre class="stack-trace">${_escapeHtml(failure.stackTrace!)}</pre>');
buffer.writeln(' </details>');
}
buffer.writeln(' </div>');
}
buffer.writeln(' </div>');
buffer.writeln(' </section>');
}
// 기능별 리포트
if (report.features.isNotEmpty) {
buffer.writeln(' <section class="features">');
buffer.writeln(' <h2>🎯 기능별 테스트 결과</h2>');
buffer.writeln(' <table class="feature-table">');
buffer.writeln(' <thead>');
buffer.writeln(' <tr>');
buffer.writeln(' <th>기능</th>');
buffer.writeln(' <th>전체</th>');
buffer.writeln(' <th>성공</th>');
buffer.writeln(' <th>실패</th>');
buffer.writeln(' <th>성공률</th>');
buffer.writeln(' </tr>');
buffer.writeln(' </thead>');
buffer.writeln(' <tbody>');
report.features.forEach((name, feature) {
final featureSuccessRate = feature.totalTests > 0
? (feature.passedTests / feature.totalTests * 100).toStringAsFixed(1)
: '0.0';
buffer.writeln(' <tr>');
buffer.writeln(' <td>$name</td>');
buffer.writeln(' <td>${feature.totalTests}</td>');
buffer.writeln(' <td class="success">${feature.passedTests}</td>');
buffer.writeln(' <td class="failure">${feature.failedTests}</td>');
buffer.writeln(' <td>$featureSuccessRate%</td>');
buffer.writeln(' </tr>');
});
buffer.writeln(' </tbody>');
buffer.writeln(' </table>');
buffer.writeln(' </section>');
}
// 자동 수정 섹션
if (report.autoFixes.isNotEmpty) {
buffer.writeln(' <section class="auto-fixes">');
buffer.writeln(' <h2>🔧 자동 수정 내역</h2>');
buffer.writeln(' <div class="fix-list">');
for (final fix in report.autoFixes) {
buffer.writeln(' <div class="fix-item ${fix.success ? 'success' : 'failure'}">');
buffer.writeln(' <div class="fix-header">');
buffer.writeln(' <span class="fix-type">${fix.errorType}</span>');
buffer.writeln(' <span class="fix-status">${fix.success ? '✅ 성공' : '❌ 실패'}</span>');
buffer.writeln(' </div>');
buffer.writeln(' <div class="fix-description">${fix.cause}${fix.solution}</div>');
buffer.writeln(' </div>');
}
buffer.writeln(' </div>');
buffer.writeln(' </section>');
}
// 환경 정보
buffer.writeln(' <section class="environment">');
buffer.writeln(' <h2>⚙️ 테스트 환경</h2>');
buffer.writeln(' <table class="env-table">');
buffer.writeln(' <tbody>');
report.environment.forEach((key, value) {
buffer.writeln(' <tr>');
buffer.writeln(' <td class="env-key">$key</td>');
buffer.writeln(' <td class="env-value">$value</td>');
buffer.writeln(' </tr>');
});
buffer.writeln(' </tbody>');
buffer.writeln(' </table>');
buffer.writeln(' </section>');
// 푸터
buffer.writeln(' <footer class="report-footer">');
buffer.writeln(' <p>이 리포트는 SUPERPORT 자동화 테스트 시스템에 의해 생성되었습니다.</p>');
buffer.writeln(' <p>생성 시간: ${DateTime.now().toLocal()}</p>');
buffer.writeln(' </footer>');
buffer.writeln(' </div>');
buffer.writeln('</body>');
buffer.writeln('</html>');
return buffer.toString();
}
/// CSS 스타일 생성
String _generateCss() {
return '''
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
background: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.report-header {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
.report-header h1 {
font-size: 2.5em;
margin-bottom: 10px;
color: #2c3e50;
}
.header-info {
color: #666;
font-size: 0.9em;
}
.header-info span {
margin-right: 20px;
}
section {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
h2 {
font-size: 1.8em;
margin-bottom: 20px;
color: #2c3e50;
}
.summary-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
text-align: center;
padding: 20px;
border-radius: 8px;
background: #f8f9fa;
}
.card.total { background: #e3f2fd; color: #1976d2; }
.card.success { background: #e8f5e9; color: #388e3c; }
.card.failure { background: #ffebee; color: #d32f2f; }
.card.skipped { background: #fff3e0; color: #f57c00; }
.card-value {
font-size: 2.5em;
font-weight: bold;
}
.card-label {
font-size: 0.9em;
margin-top: 5px;
}
.progress-bar {
height: 30px;
background: #e0e0e0;
border-radius: 15px;
position: relative;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4caf50, #45a049);
transition: width 0.5s ease;
}
.progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-weight: bold;
color: #333;
}
.failure-item {
border: 1px solid #ffcdd2;
border-radius: 4px;
padding: 15px;
margin-bottom: 15px;
background: #ffebee;
}
.failure-item h3 {
color: #c62828;
margin-bottom: 10px;
}
.failure-message {
background: #fff;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
font-size: 0.9em;
}
details {
margin-top: 10px;
}
summary {
cursor: pointer;
color: #666;
font-size: 0.9em;
}
.stack-trace {
background: #f5f5f5;
padding: 10px;
border-radius: 4px;
font-size: 0.8em;
overflow-x: auto;
max-height: 300px;
overflow-y: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
text-align: left;
padding: 12px;
border-bottom: 1px solid #e0e0e0;
}
th {
background: #f5f5f5;
font-weight: 600;
color: #666;
}
td.success { color: #388e3c; }
td.failure { color: #d32f2f; }
.fix-item {
border-radius: 4px;
padding: 15px;
margin-bottom: 10px;
}
.fix-item.success {
background: #e8f5e9;
border: 1px solid #c8e6c9;
}
.fix-item.failure {
background: #ffebee;
border: 1px solid #ffcdd2;
}
.fix-header {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.fix-type {
font-weight: bold;
}
.env-table {
font-size: 0.9em;
}
.env-key {
font-weight: 600;
color: #666;
}
.report-footer {
text-align: center;
color: #666;
font-size: 0.9em;
}
''';
}
/// 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}';
}
}
/// HTML 이스케이프
String _escapeHtml(String text) {
return text
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
}