반복·수작업이던 배포 전 테스트 시나리오 작성을 생성형 AI + RAG로 자동화하여 업무 효율성을 높임
생성형 AI와 RAG 기술을 활용해, 사내 보안 규칙을 반영한 테스트 시나리오 자동 생성 도우미를 구축
- RAG 기반 보안 규칙 검색 및 적용
- 프론트 코드 분석 → UI 요소별 동작 파싱
- 백엔드 DTO 분석 → 유효성 시나리오 생성
- 테스트 템플릿 맞춤 적용
업무 효율성 향상
- 테스트 시나리오 작성 시간 70%↓ (수동 3시간 → 자동 30분)
- 배포 전 테스트 커버리지 30%↑ (누락 케이스 자동 발견)
품질 표준화
- 회사 보안 정책 100% 반영된 일관성 있는 테스트 케이스
- 신규 개발자도 베테랑 수준의 테스트 시나리오 생성 가능
- 프로젝트별 테스트 품질 편차 해소
비용 절감 효과
- 배포 후 버그 발견 비용 대비 사전 예방 효과 (1:10 비율)
- 반복 작업 최소화로 번아웃 감소
- GPT-4.1: 코드 분석 및 테스트 시나리오 생성
- text-embedding-ada-002: 보안 규칙 문서 임베딩 생성
- 벡터 검색 엔진: 보안 규칙 문서 RAG 검색
- 하이브리드 검색: 키워드 + 벡터 검색
- 프론트: React 기반 UI
- 프록시 서버: Node, Express로 프록시 서버 구현
보안 문서 업로드 → 브라우저에서 임베딩 생성 → AI Search 인덱싱 →
코드 업로드 → 브라우저에서 OpenAI 호출 → 관련 보안 규칙 검색 →
컨텍스트 생성 → GPT-4 테스트 시나리오 생성 → 파일 다운로드
- 보안 문서 업로드: PDF/Word/MD 파일 드래그 앤 드롭
- 임베딩: Azure OpenAI API 호출(text-embedding-ada-002)
- 인덱스 구축: Azure AI Search API 호출로 인덱싱
- 템플릿 편집기: 컬럼명, 설명, 예시 입력
- 빈 컬럼 추가: 회사 양식에 맞춘 빈 컬럼 포함
- localStorage 저장: 브라우저에 템플릿 저장
- JSON 내보내기: 팀원들과 템플릿 공유
- 템플릿 선택: 저장된 템플릿 중 선택
- 코드 업로드: 분석할 코드 업로드
- GPT-4 코드 분석: Azure OpenAI API 호출로 보안 키워드 추출
- 보안 규칙 검색: GPT 응답 + Azure AI Search 하이브리드 검색 결과 활용
- 템플릿 기반 시나리오 생성: 선택한 템플릿 구조에 맞춰 테스트 케이스 생성
- 커스텀 재생성: 응답 결과를 커스터마이징하는 프롬프트를 붙여서 재생성
- 다운로드: Markdown / CSV / JSON으로 추출
-
비용을 최소화하기 위해 Blob storage를 붙이지 않고 사용
-
DB를 사용하지 않고 Azure AI Search 인덱스를 직접 사용함으로써 운영 비용을 절감하고 구성 간결화
app.get('/api/search/index/status', async (req, res) => { try { const indexName = 'security-docs-index'; console.log('📊 인덱스 상태 확인 요청'); // 1. 인덱스 존재 여부 확인 const indexUrl = `${process.env.AZURE_SEARCH_ENDPOINT}/indexes/${indexName}?api-version=2023-11-01`; const indexResponse = await fetch(indexUrl, { method: 'GET', headers: { 'api-key': process.env.AZURE_SEARCH_API_KEY, }, }); if (!indexResponse.ok) { // 인덱스가 없으면 404 if (indexResponse.status === 404) { console.log('ℹ️ 인덱스가 존재하지 않음'); return res.json({ exists: false, documentCount: 0, embeddingCount: 0, indexSize: 0, lastUpdate: null }); } const errorText = await indexResponse.text(); console.error('인덱스 확인 오류:', indexResponse.status, errorText); return res.status(indexResponse.status).json({ error: errorText }); }
-
브라우저 기반 임베딩 생성 → Azure AI Search에 직접 인덱싱
-
기존 인덱스 상태를 체크하고 재사용 여부를 사용자에게 선택하게 하여 비용 최소화
-
템플릿도 JSON 내보내기 등을 이용하여 DB를 사용하지 않음
-
프론트엔드:
👉 https://dopaminesun-web-app-hxd2dab2d2hwa0d0.eastus2-01.azurewebsites.net
-
프록시 서버:
👉 https://dopaminesun-server-dycxgacfcmbcc2ec.eastus2-01.azurewebsites.net
-
데모 영상:
👉 영상 보기
🔍 용도: analyzeCode() 내에서 코드 파일들을 청크로 나눠서 분석할 때 사용
다음 코드 조각을 분석하여 테스트 시나리오 생성에 필요한 정보를 추출해주세요.
(이것은 전체 코드의 {i + 1}/{chunks.length} 부분입니다)
코드:
[여기에 코드 청크 삽입]
반드시 다음 형태의 유효한 JSON으로만 응답해주세요. 다른 설명이나 텍스트는 포함하지 마세요:
{
"keywords": ["보안 관련 키워드 배열"],
"uiElements": ["UI 컴포넌트나 폼 요소 배열"],
"backendApis": ["API 엔드포인트 배열"],
"securityConcerns": ["보안 관련 우려사항 배열"],
"functions": ["주요 함수명 배열"],
"components": ["React 컴포넌트명 배열"]
}
🚀 용도: generateTestScenarios() 내에서 GPT로 시나리오 생성 요청할 때 사용
다음 정보를 바탕으로 실무에서 사용 가능한 테스트 시나리오를 생성해주세요.
## 템플릿 구조:
- 테스트 케이스 ID: 고유 식별자 (예: TC001)
- 테스트 시나리오: 테스트 내용 (예: 로그인 기능 테스트)
- 보안 규칙: 적용된 보안 규칙 (예: 패스워드 정책)
- 예상 결과: 기대하는 결과 (예: 로그인 성공)
- 사전 조건: 테스트 전 준비 상태 (예: 사용자 계정 존재)
- 테스트 단계: 단계별 절차 (예: ["로그인 페이지 이동", "비밀번호 입력", "로그인 버튼 클릭"])
## 코드 분석 결과:
- 보안 키워드: login, password
- UI 요소: LoginButton, PasswordField
- API 엔드포인트: POST /auth/login
- 보안 우려사항: 인증 미흡, 비밀번호 노출 가능성
- 주요 함수: handleLogin, validatePassword
- 컴포넌트: LoginForm
## 적용할 보안 규칙 (RAG 검색 결과):
### 패스워드 입력 검증
카테고리: 인증
내용: 사용자로부터 입력받은 패스워드는 최소 8자 이상이어야 하며, 대소문자, 숫자, 특수문자를 포함해야 한다...
### 로그인 시도 횟수 제한
카테고리: 접근 제어
내용: 동일한 IP 또는 사용자 ID로 5회 이상 로그인 실패 시 일시적으로 차단되어야 한다...
다음 요구사항을 만족하는 최소 5~7개의 테스트 시나리오를 생성하세요:
1. 위에서 검색된 보안 규칙들을 실제로 적용한 시나리오
2. 코드 분석에서 발견된 API와 함수들을 활용한 시나리오
3. 실무에서 실제로 테스트할 수 있는 구체적인 내용
반드시 다음과 같은 **유효한 JSON 배열 형태로만** 응답해주세요:
[
{
"테스트 케이스 ID": "TC001",
"테스트 시나리오": "비밀번호 정책 검증",
"보안 규칙": "패스워드 입력 검증",
"예상 결과": "8자 미만 비밀번호는 거부됨",
"사전 조건": "비밀번호 입력 폼 존재",
"테스트 단계": ["페이지 진입", "비밀번호 5자리 입력", "에러 메시지 확인"]
}
]
🔁 용도: 사용자가 직접 추가 요구사항 입력 후 재생성할 때 사용
📥 커스텀 입력 포함
다음 정보를 바탕으로 실무에서 사용 가능한 테스트 시나리오를 생성해주세요.
## 템플릿 구조:
- 테스트 케이스 ID: 고유 식별자 (예: TC001)
- 테스트 시나리오: 테스트 내용 (예: 로그인 기능 테스트)
- 보안 규칙: 적용된 보안 규칙 (예: 패스워드 정책)
- 예상 결과: 기대하는 결과 (예: 로그인 성공)
## 코드 분석 결과:
- 보안 키워드: ${codeAnalysis.keywords.join(', ')}
- UI 요소: ${codeAnalysis.uiElements.join(', ')}
- API 엔드포인트: ${codeAnalysis.backendApis.join(', ')}
- 보안 우려사항: ${codeAnalysis.securityConcerns.join(', ')}
- 주요 함수: ${codeAnalysis.functions.join(', ')}
- 컴포넌트: ${codeAnalysis.components.join(', ')}
## 적용할 보안 규칙 (RAG 검색 결과):
${securityRules.map(rule => `### ${rule.title}\n카테고리: ${rule.category}\n내용: ${rule.content.substring(0, 500)}...`).join('\n')}
## 🔥 사용자 추가 요구사항 🔥
${customPrompt}
위의 모든 정보와 특히 사용자의 추가 요구사항을 반드시 반영하여 최소 5-7개의 테스트 시나리오를 생성하세요.
반드시 다음과 같은 유효한 JSON 배열 형태로만 응답해주세요:
[
{
"테스트 케이스 ID": "TC001",
"테스트 시나리오": "구체적인 테스트 시나리오 내용",
"보안 규칙": "적용된 보안 규칙명",
"예상 결과": "예상되는 결과"
}
]
모든 GPT 요청은 다음과 같은 시스템 메시지와 함께 전송
{
"role": "system",
"content": "당신은 소프트웨어 테스트 전문가입니다. 보안 규칙을 반영한 정확하고 실용적인 테스트 시나리오를 생성하는 것이 전문입니다. 항상 유효한 JSON 형태로만 응답하세요."
}
재생성 요청은 다음과 같이 전송
{
role: 'system',
content: '당신은 소프트웨어 테스트 전문가입니다. 보안 규칙을 반영한 정확하고 실용적인 테스트 시나리오를 생성하는 것이 전문입니다. 항상 유효한 JSON 형태로만 응답하세요.'
},
{
role: 'user',
content: enhancedPrompt
}// 하이브리드 검색
app.post('/api/search/hybrid-search', async (req, res) => {
try {
const {
query,
queryVector,
top = 5,
select,
highlight,
highlightPreTag = '<mark>',
highlightPostTag = '</mark>',
searchMode = 'any'
} = req.body;
const indexName = 'security-docs-index';
const url = `${process.env.AZURE_SEARCH_ENDPOINT}/indexes/${indexName}/docs/search?api-version=2024-03-01-preview`;
const searchBody = {
search: query || '*',
top: top,
select: select || 'id,title,filename,category',
searchMode: searchMode,
queryType: 'full',
searchFields: 'title,content,category'
};
// 하이라이트 설정 추가
if (highlight) {
searchBody.highlight = highlight;
searchBody.highlightPreTag = highlightPreTag;
searchBody.highlightPostTag = highlightPostTag;
}
// 벡터 검색 추가 (하이브리드 검색)
if (queryVector && queryVector.length > 0) {
searchBody.vectors = [
{
value: queryVector,
fields: 'contentVector',
k: top
}
];
}
console.log('하이브리드 검색 요청:', url);
console.log('검색 본문:', JSON.stringify(searchBody, null, 2));
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'api-key': process.env.AZURE_SEARCH_API_KEY,
},
body: JSON.stringify(searchBody),
});
if (!response.ok) {
const errorText = await response.text();
console.error('하이브리드 검색 오류:', response.status, errorText);
return res.status(response.status).json({ error: errorText });
}
const data = await response.json();
console.log('Azure Search 응답:', JSON.stringify(data, null, 2));
// 결과 후처리 - content 요약 및 하이라이트 처리
const results = data.value.map((item, index) => {
let processedContent = '';
// 하이라이트된 content가 있으면 그것을 사용
if (item['@search.highlights'] && item['@search.highlights'].content) {
processedContent = item['@search.highlights'].content
.slice(0, 3) // 상위 3개 하이라이트만
.join('... ')
.substring(0, 800) + '...';
} else if (item.content) {
// 하이라이트가 없으면 원본 content 요약
processedContent = summarizeContent(item.content, 800);
}
return {
id: item.id || `result_${index}`,
title: item.title || '제목 없음',
content: processedContent,
filename: item.filename || '',
category: item.category || '일반',
relevance: normalizeRelevanceScore(item['@search.score']),
'@search.score': item['@search.score'],
'@search.highlights': item['@search.highlights']
};
});
console.log(`검색 결과 후처리 완료: ${results.length}개`);
res.json({ results });
} catch (error) {
console.error('하이브리드 검색 프록시 오류:', error);
res.status(500).json({ error: error.message });
}
});// 키워드 검색
app.post('/api/search/keyword-search', async (req, res) => {
try {
const {
query,
top = 5,
select,
highlight,
highlightPreTag = '<mark>',
highlightPostTag = '</mark>',
searchMode = 'any'
} = req.body;
const indexName = 'security-docs-index';
const url = `${process.env.AZURE_SEARCH_ENDPOINT}/indexes/${indexName}/docs/search?api-version=2023-11-01`;
const searchBody = {
search: query || '*',
top,
select: select || 'id,title,filename,category',
searchMode,
queryType: 'full',
searchFields: 'title,content,category'
};
if (highlight) {
searchBody.highlight = highlight;
searchBody.highlightPreTag = highlightPreTag;
searchBody.highlightPostTag = highlightPostTag;
}
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'api-key': process.env.AZURE_SEARCH_API_KEY,
},
body: JSON.stringify(searchBody),
});
const data = await response.json();
const results = (data.value || []).map((item, index) => {
let processedContent = '';
if (item['@search.highlights']?.content) {
processedContent = item['@search.highlights'].content
.slice(0, 3)
.join('... ')
.substring(0, 800) + '...';
} else {
processedContent = item.content || '';
}
return {
id: item.id || `keyword_result_${index}`,
title: item.title || '',
content: processedContent,
filename: item.filename || '',
category: item.category || '',
relevance: item['@search.score'] || 0
};
});
res.json({ results });
} catch (error) {
res.status(500).json({ error: error.message });
}
});| 구분 | 기술 |
|---|---|
| 프론트엔드 | React, TypeScript, Tailwind, Vite |
| 백엔드 | Node.js, Express, Azure App Service |
| AI | Azure OpenAI (GPT-4, text-embedding-ada-002) |
| 검색 | Azure AI Search (Vector + Hybrid Search) |
| 배포 | Docker, Docker Compose, Azure Web App |
- 벡터 검색 적용
- 코드 업로드시 암호화
- 코드 유형 분류 학습
- 회사 코드의 외부 AI 서비스 전송에 대한 정책 수립(Azure private, EntraID 인증 등)
- 사내 서비스로 활용 가능성
- 보안팀에서 rag 인덱스 생성
- 기술혁신단에서 코드 유형 분류 학습
