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,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('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user