변경 영향 분석 기반 테스트 선택, 위험 기반 우선순위, 플레이키 테스트 자동 격리, 병렬 실행 최적화, 결함 예측, GitHub Actions/GitLab CI 통합을 다루는 AI QA 파이프라인 구축 가이드입니다.
전통적인 CI 파이프라인은 모든 PR에 대해 전체 테스트 스위트를 동일한 방식으로 실행합니다. AI QA 파이프라인은 이를 지능적으로 최적화합니다.
| 영역 | 기존 파이프라인 | AI QA 파이프라인 |
|---|---|---|
| 테스트 선택 | 전체 실행 | 변경 영향 분석 기반 선택 |
| 실행 순서 | 알파벳 또는 정의 순 | 위험도/실패 확률 순 |
| 플레이키 대응 | 고정 횟수 재시도 | 패턴 기반 자동 격리 |
| 실행 시간 | 30-60분 | 10-20분 |
| 실패 분석 | 로그 수동 확인 | AI 자동 분류 및 근본 원인 제시 |
모든 코드 변경이 모든 테스트에 영향을 주는 것은 아닙니다. Change Impact Analysis(변경 영향 분석)는 변경된 코드가 어떤 테스트에 영향을 미치는지를 정적/동적으로 분석합니다.
interface ChangeImpact {
changedFiles: string[];
directlyAffectedTests: string[];
indirectlyAffectedTests: string[];
riskScore: number;
recommendation: "full" | "selective" | "smoke";
}
async function analyzeImpact(prDiff: string): Promise<ChangeImpact> {
const changedFiles = parseDiffFiles(prDiff);
// 1단계: 정적 의존성 분석
const importGraph = await buildImportGraph("src/");
const directDeps = changedFiles.flatMap((f) =>
importGraph.getReverseDependencies(f)
);
// 2단계: 테스트-코드 매핑
const testMap = await loadTestCoverageMap();
const directlyAffectedTests = directDeps
.flatMap((dep) => testMap.getTestsForFile(dep))
.filter(unique);
// 3단계: AI 기반 간접 영향 추론
const indirectlyAffectedTests = await predictIndirectImpact(
changedFiles,
directlyAffectedTests
);
// 4단계: 위험도 계산
const riskScore = calculateRiskScore(changedFiles, {
defectHistory: await getDefectHistory(changedFiles),
changeFrequency: await getChangeFrequency(changedFiles),
complexity: await getComplexity(changedFiles),
});
return {
changedFiles,
directlyAffectedTests,
indirectlyAffectedTests,
riskScore,
recommendation:
riskScore > 0.8 ? "full" : riskScore > 0.4 ? "selective" : "smoke",
};
}변경 영향 분석의 정확도는 테스트-코드 매핑의 품질에 크게 의존합니다. 초기에는 커버리지 데이터를 수집하여 매핑을 구축하고, 시간이 지남에 따라 실제 실패 패턴을 학습하여 정확도를 높여갑니다.
선택된 테스트를 실행할 때, 위험도가 높은 테스트를 먼저 실행하면 결함을 더 빨리 발견할 수 있습니다.
| 요소 | 가중치 | 설명 |
|---|---|---|
| 최근 실패 이력 | 높음 | 최근에 실패한 테스트는 다시 실패할 확률이 높음 |
| 코드 변경 밀접도 | 높음 | 변경된 코드와 직접 관련된 테스트 |
| 결함 발견 빈도 | 중간 | 과거에 실제 결함을 발견한 테스트 |
| 실행 시간 | 중간 | 빠른 테스트를 먼저 실행하여 빠른 피드백 |
| 비즈니스 중요도 | 높음 | 결제, 인증 등 핵심 기능 관련 테스트 |
interface TestPriority {
testId: string;
testFile: string;
priority: number;
reasons: string[];
}
function prioritizeTests(
tests: string[],
context: PrioritizationContext
): TestPriority[] {
return tests
.map((testId) => {
let priority = 0;
const reasons: string[] = [];
// 최근 실패 이력 (+30점)
if (context.recentFailures.has(testId)) {
priority += 30;
reasons.push("최근 실패 이력 있음");
}
// 변경 코드 직접 관련 (+25점)
if (context.directlyAffected.has(testId)) {
priority += 25;
reasons.push("변경 코드와 직접 관련");
}
// 결함 발견 빈도 (+20점)
const defectCount = context.defectDiscoveryCount.get(testId) ?? 0;
priority += Math.min(defectCount * 5, 20);
if (defectCount > 0) {
reasons.push(`과거 ${defectCount}건 결함 발견`);
}
// 빠른 테스트 우선 (+15점)
const duration = context.avgDuration.get(testId) ?? 10000;
if (duration < 1000) {
priority += 15;
reasons.push("빠른 실행 (1초 미만)");
} else if (duration < 5000) {
priority += 10;
}
// 비즈니스 중요도 (+10점)
if (context.criticalPaths.has(testId)) {
priority += 10;
reasons.push("핵심 비즈니스 경로");
}
return { testId, testFile: testId, priority, reasons };
})
.sort((a, b) => b.priority - a.priority);
}Flaky Test(플레이키 테스트)는 CI 파이프라인의 가장 큰 적입니다. AI 기반 자동 격리는 플레이키 테스트를 감지하고, 일반 테스트와 분리하여 별도로 관리합니다.
name: AI QA Pipeline
on:
pull_request:
branches: [main]
jobs:
classify-tests:
runs-on: ubuntu-latest
outputs:
stable-tests: ${{ steps.classify.outputs.stable }}
flaky-tests: ${{ steps.classify.outputs.flaky }}
steps:
- uses: actions/checkout@v4
- name: Classify tests
id: classify
run: |
node scripts/classify-tests.js \
--history=test-history.json \
--threshold=0.2
run-stable:
needs: classify-tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run stable tests
run: |
npx jest --testPathPattern="${{ needs.classify-tests.outputs.stable-tests }}"
run-flaky:
needs: classify-tests
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Run flaky tests with retries
run: |
npx jest \
--testPathPattern="${{ needs.classify-tests.outputs.flaky-tests }}" \
--retries=3
- name: Report flaky results
if: always()
run: node scripts/report-flaky.js플레이키 테스트를 격리하는 것은 "무시하는 것"과 다릅니다. 격리된 테스트는 별도의 대시보드에서 추적하고, 주기적으로 원인을 분석하여 수정해야 합니다. 격리만 하고 방치하면 테스트 로트의 또 다른 형태가 됩니다.
테스트 실행 시간을 줄이는 가장 직접적인 방법은 병렬 실행입니다. AI는 테스트 간 의존성과 리소스 사용 패턴을 분석하여 최적의 병렬화 전략을 수립합니다.
interface ShardPlan {
shards: Array<{
id: number;
tests: string[];
estimatedDuration: number;
}>;
totalEstimatedDuration: number;
parallelSpeedup: number;
}
function optimizeShards(
tests: TestMetadata[],
maxShards: number
): ShardPlan {
// 실행 시간 기준으로 테스트를 내림차순 정렬
const sorted = [...tests].sort(
(a, b) => b.avgDuration - a.avgDuration
);
// 최소 힙(min-heap) 기반 로드 밸런싱
const shards: Array<{ id: number; tests: string[]; totalDuration: number }> =
Array.from({ length: maxShards }, (_, i) => ({
id: i,
tests: [],
totalDuration: 0,
}));
for (const test of sorted) {
// 가장 여유로운 샤드에 테스트 배정
const lightest = shards.reduce((min, shard) =>
shard.totalDuration < min.totalDuration ? shard : min
);
lightest.tests.push(test.id);
lightest.totalDuration += test.avgDuration;
}
const maxDuration = Math.max(...shards.map((s) => s.totalDuration));
const sequentialDuration = tests.reduce((sum, t) => sum + t.avgDuration, 0);
return {
shards: shards.map((s) => ({
id: s.id,
tests: s.tests,
estimatedDuration: s.totalDuration,
})),
totalEstimatedDuration: maxDuration,
parallelSpeedup: sequentialDuration / maxDuration,
};
}AI는 과거 데이터를 분석하여 결함이 발생할 가능성이 높은 코드 영역을 예측할 수 있습니다.
| 특성 | 설명 |
|---|---|
| 코드 복잡도 | 순환 복잡도, 인지 복잡도 |
| 변경 빈도 | 최근 N일간 변경 횟수 |
| 변경 크기 | 추가/삭제된 라인 수 |
| 개발자 경험 | 해당 코드에 대한 개발자의 친숙도 |
| 과거 결함 | 해당 파일/모듈의 결함 이력 |
| 코드 연령 | 마지막 대규모 리팩터링 이후 경과 시간 |
=== PR #1234 결함 예측 ===
[위험도 0.87] src/services/payment-processor.ts
- 순환 복잡도: 28 (권장: 15 이하)
- 최근 30일 변경: 12회
- 과거 결함: 4건
- 이번 변경: +45 / -12 라인
권장: 집중 코드 리뷰 + 추가 테스트
[위험도 0.62] src/api/routes/orders.ts
- 순환 복잡도: 15
- 최근 30일 변경: 8회
- 과거 결함: 2건
- 이번 변경: +23 / -5 라인
권장: 일반 코드 리뷰
[위험도 0.23] src/utils/format.ts
- 순환 복잡도: 5
- 최근 30일 변경: 1회
- 과거 결함: 0건
- 이번 변경: +8 / -2 라인
권장: 표준 프로세스결함 예측 모델은 확률적 도구이지, 결정론적 판단이 아닙니다. "이 코드에 반드시 결함이 있다"가 아니라, "이 코드에 주의를 기울여야 한다"는 신호로 활용해야 합니다. 위험도가 높은 영역에 더 많은 리뷰 시간과 테스트를 할당하는 전략입니다.
지금까지 다룬 기능을 통합한 전체 AI QA 파이프라인입니다.
name: AI QA Pipeline
on:
pull_request:
branches: [main]
push:
branches: [main]
env:
NODE_VERSION: "22"
jobs:
# 1단계: 변경 분석 및 테스트 계획
analyze:
runs-on: ubuntu-latest
outputs:
test-plan: ${{ steps.plan.outputs.plan }}
risk-level: ${{ steps.plan.outputs.risk }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Analyze changes and create test plan
id: plan
run: |
node scripts/ai-test-planner.js \
--base=${{ github.event.pull_request.base.sha }} \
--head=${{ github.event.pull_request.head.sha }}
# 2단계: 단위 + 통합 테스트 (병렬)
unit-integration:
needs: analyze
runs-on: ubuntu-latest
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- name: Run tests (shard ${{ matrix.shard }})
run: |
pnpm test -- \
--shard=${{ matrix.shard }}/4 \
--forceExit \
--ci
# 3단계: E2E 테스트 (위험도 높을 때만)
e2e:
needs: [analyze, unit-integration]
if: needs.analyze.outputs.risk-level == 'high'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: npx playwright install --with-deps
- name: Run E2E tests
run: pnpm test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: e2e-results
path: test-results/
# 4단계: 품질 게이트
quality-gate:
needs: [unit-integration, e2e]
if: always()
runs-on: ubuntu-latest
steps:
- name: Evaluate quality gate
run: |
echo "Checking quality gate..."
node scripts/quality-gate.js \
--coverage-threshold=80 \
--mutation-threshold=70이 장에서는 AI QA 파이프라인의 핵심 구성 요소와 구축 방법을 살펴보았습니다.
핵심 내용을 정리하면 다음과 같습니다.
9장에서는 Agentic QA -- 자율 테스트 에이전트를 다룹니다. 유저 스토리에서 Gherkin 시나리오를 거쳐 실행 가능한 테스트 코드까지 자동 변환하는 에이전트 아키텍처와, Human-on-the-loop 감독 체계를 깊이 살펴봅니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
유저 스토리에서 Gherkin 시나리오를 거쳐 실행 가능한 테스트로 자동 변환하는 Agentic QA의 아키텍처, 자율 탐색 테스트, Human-on-the-loop 감독 체계, 그리고 품질 게이트 통합을 다룹니다.
테스트 로트(Test Rot) 문제의 근본 원인과 AI 기반 셀프 힐링, 셀렉터 자동 재바인딩, 테스트 코드 리팩터링, 중복 테스트 감지, 커버리지 갭 분석 등 유지보수 비용 절감 전략을 다룹니다.
단위, 통합, E2E, 시각적, 변이 테스트를 하나의 AI 테스트 자동화 파이프라인으로 통합합니다. Codium, Playwright, Applitools를 결합한 CI/CD 파이프라인과 대시보드, 도입 로드맵, ROI 측정을 다룹니다.