테스트 자동화가 파이프라인 안에서 실패하는 이유는 대개 게이트 설계 탓이다
CI/CD 파이프라인에 테스트 계층·품질 게이트·보안 스캔을 내재화하는 방법과 GitHub Actions 구현 예시를 정리한다
2026-08-12 · 최초 발행 2025-12-18
CI(지속적 통합)는 코드 변경을 빈번히 통합하고 자동 빌드·정적 분석·단위 테스트를 도는 프로세스고, CD(지속적 제공/배포)는 검증된 아티팩트를 자동 배포하고 환경 간 승격을 관리하는 프로세스다. 이 둘을 테스트 자동화와 묶는 이유는 단순하다 — 단위, 통합, 계약(Contract), E2E, 성능·보안 테스트를 파이프라인에 내재화하지 않으면 품질 게이트가 그냥 형식으로 남기 때문이다. 코드 스멜·취약점·테스트 커버리지·플래키율 같은 정량 지표를 통과 기준으로 걸고, 실패하면 즉시 중단·알림으로 문제 확산을 막는 게 게이트의 역할이다.
단계마다 게이트를 어디에 거는가
파이프라인은 코드 푸시 → 빌드·정적 분석 → 단위 테스트 → 아티팩트 생성 → 통합·E2E 테스트 → 스테이징 → 승인 → 프로덕션 배포 순으로 흐른다. 각 단계에는 커버리지 80% 이상, SAST/CVE 임계치, 라이선스 정책 같은 게이트를 걸고, 실패하면 파이프라인을 중단하고 알리고 아티팩트를 폐기한다.
테스트 계층화는 테스트 피라미드를 그대로 따른다. 단위 테스트는 대량·고속으로, 통합·계약 테스트는 선별적으로, E2E는 최소한만 돌린다. 테스트 데이터 관리(TDM)는 고정 픽스처와 시드 데이터, 트랜잭션 롤백, 임시 데이터베이스·컨테이너로 케이스 간 독립성을 확보하고, 비관/낙관 락 케이스와 트랜잭션 경계 테스트로 동시성까지 검증한다.
성능·안정성은 병렬 실행과 의존성·빌드 아티팩트 캐시로 파이프라인 시간을 줄이는 데서 시작한다. 모노레포라면 변경 영향도 기반 선택적 테스트(경로 필터링)와 테스트 셰이딩(sharding)을 적용하고, 플래키 테스트는 탐지·격리·재시도 한도 정책을 두면서 장기적으로는 근본 원인을 없애는 작업을 병행한다.
보안·규정 준수는 SAST, SCA, 컨테이너 이미지 스캔, SBOM 생성, 서명·검증(Sigstore) 자동화로 내재화한다. 여기에 비밀 관리(Secrets Manager)와 서플라이체인 보호(SLSA 수준 준수)를 얹고, 변경 이력·배포 승인 기록을 보존해 컴플라이언스 대응까지 갖춘다.
마이크로서비스, 모노레포, 모바일에서 파이프라인이 갈라지는 지점
마이크로서비스 배포는 서비스 단위로 컨테이너 이미지를 빌드·레지스트리 푸시·계약 테스트·카나리 배포까지 거치고, 임시 네임스페이스에 통합 테스트 환경을 만들었다가 자동 폐기해 격리성을 확보한다. 모노레포 제품군은 영향받은 모듈만 부분 빌드·테스트·배포하고, 공통 라이브러리가 바뀌면 역의존 계약 테스트를 돌린다. 워크스페이스·캐시를 최적화하면 파이프라인 시간이 선형 이하로 유지된다. 모바일 앱은 CI에서 정적 분석·유닛 테스트를 돌리고 디바이스 팜에서 UI 테스트를 자동화한 뒤, 스토어 릴리스 트랙(내부/베타/프로덕션)으로 점진적 배포하며 크래시 모니터링을 연계한다.
게이트가 실패하면 중단·알림, 아티팩트 폐기, 이슈 자동 생성으로 이어진다. 통합 테스트에서는 트랜잭션 경계를 명시하고 롤백 기반 격리, 동시성 시나리오 검증을 함께 넣어야 한다.
테스트 유형별로 운영 지표를 나란히 놓으면 왜 피라미드 구조를 지켜야 하는지 드러난다.
| 테스트 유형 | 성능(속도) | 확장성(병렬화) | 일관성(플래키율) | 안정성(환경 의존도) | 운영 편의(유지보수) |
|---|---|---|---|---|---|
| 단위(Unit) | 매우 빠름(초) | 높음 | 매우 낮음 | 낮음 | 매우 높음 |
| 통합(Integration) | 보통(분) | 중간 | 낮음 | 중간 | 보통 |
| 계약(Contract) | 빠름(초~분) | 높음 | 낮음 | 낮음 | 높음 |
| E2E(UI/API) | 느림(분~십여분) | 낮음 | 중간~높음 | 높음 | 낮음 |
| 성능/부하 | 느림(분~시간) | 중간 | 낮음 | 높음 | 낮음 |
GitHub Actions로 실제로 짜보면
전제조건은 Node.js 18·npm·Jest 테스트 스위트, 컨테이너 빌드 가능 환경과 GHCR 또는 레지스트리 권한, Kubernetes 접근 권한과 배포 매니페스트다.
name: ci-cd
on:
push:
branches: [ "main" ]
pull_request:
env:
IMAGE: ghcr.io/your-org/your-app:${{ github.sha }}
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: npm
- name: Install
run: npm ci
- name: Lint & SAST (ESLint + npm audit)
run: |
npm run lint
npm audit --audit-level=high || true
- name: Unit Test with Coverage
run: npm test -- --ci --coverage --maxWorkers=50%
- name: Enforce Coverage Gate
run: |
threshold=80
actual=$(node -e "console.log(require('./coverage/coverage-summary.json').total.statements.pct)")
echo "coverage=${actual}%"
awk -v a="$actual" -v t="$threshold" 'BEGIN{exit(a<t)}'
- name: Build app
run: npm run build
- name: Docker build
run: docker build -t $IMAGE .
- name: Registry login
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push image
run: docker push $IMAGE
- name: Integration Test (docker compose)
run: |
docker compose -f compose.test.yml up --abort-on-container-exit --exit-code-from sut
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: image-digest
path: <(docker inspect --format='{{index .RepoDigests 0}}' $IMAGE)
cd:
needs: ci
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: production
steps:
- name: Kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > ~/.kube/config
- name: Canary deploy
run: |
kubectl set image deployment/your-app app=$IMAGE
kubectl rollout status deployment/your-app --timeout=5m
- name: Smoke test
run: curl -fsS https://your-app.example.com/healthz
- name: Verify SLO (error rate, latency)
run: |
# 예시: 외부 모니터링 API 질의 후 기준 미달이면 실패
echo "check SLO here"
- name: Promote or Rollback
run: |
echo "promotion logic or rollback on failure"
운영 포인트는 세 가지다. 테스트 데이터는 통합 테스트에서 마이그레이션 후 시드하고 종료 시 드롭하거나 트랜잭션을 롤백한다. 플래키는 재시도 최대 1회로 제한하고 실패 케이스는 격리 태깅하며 근본 원인 개선을 위한 이슈를 자동 생성한다. 파이프라인 시간은 10~15분 내 종료를 목표로 하고 30분을 넘기면 병목 단계를 분리하거나 병렬화한다.
게이트를 세게 걸수록 무엇을 잃는가
모범사례에는 항상 트레이드오프가 붙는다. 테스트 피라미드를 지키면 속도·안정성은 최적화되지만 복잡한 시나리오가 E2E에 남아 커버리지 체감이 떨어질 수 있다. 카나리·블루-그린은 위험을 분산하고 무중단 전환이 가능하지만 인프라 비용과 라우팅 복잡도가 늘어난다. 계약 테스트(Consumer-Driven)는 팀 간 독립 배포를 촉진하지만 계약 관리와 버전 호환성 유지 비용이 든다. 플래키 테스트 재시도는 일시적 실패를 완충하지만 실제 결함을 은폐할 위험이 있어 재시도 한도와 격리 정책이 반드시 필요하다. 보안 스캔을 강화하면 초기 취약점은 잘 막히지만 파이프라인이 지연되고 허용 정책·허위양성 관리 부담이 늘어난다.
CI/CD와 테스트 자동화의 통합은 품질 게이트 중심의 표준화된 파이프라인을 만들어 리드타임·신뢰성·안정성을 동시에 끌어올리는 전략이다. 리드타임은 4070% 단축, 변경 실패율은 2040% 감소, 배포 빈도는 510배 향상, MTTR은 3050% 단축된다고 보고되며(팀 성숙도에 따라 변동), 개발자 생산성도 수작업 테스트·핫픽스가 줄면서 함께 오른다. 초기에는 단위·계약 테스트와 커버리지 게이트부터 적용하고, 이후 카나리·성능 테스트로 단계적으로 확장하는 순서가 실패 확률을 낮춘다.