배포 QA 고도화 TDD
Background
Codex Git Hook을 통합해 로컬 코드 품질 게이트를 완성했다. 이제 서버 환경에서의 품질 보증(CI/CD)과 UI 레벨 검증(E2E)이 필요하다.
Overview
GitHub Actions 기반 CI/CD 파이프라인을 구축하고, Playwright E2E 테스트로 UI-API 전체 플로우를 자동 검증한다. 인프라는 Docker Compose로 표준화하고, main 머지 시 SSH를 통해 즉시 배포된다.
Terminology
| 용어 | 설명 |
|---|---|
| TBD | Trunk-Based Development — main을 항상 배포 가능 상태로 유지 |
| CI | Continuous Integration — PR 시 자동 빌드·테스트 |
| CD | Continuous Deployment — main 머지 시 자동 배포 |
| E2E | End-to-End — UI에서 API까지 전체 플로우 검증 |
| Branch Protection | GitHub 브랜치 보호 규칙 — CI 통과 없이 머지 불가 |
| ghcr.io | GitHub Container Registry — Docker 이미지 무료 저장소 |
| Smoke Test | 핵심 기능 동작 여부만 빠르게 검증하는 최소 E2E |
Define Problem
AS-IS
- 배포 자동화 없음 — 수동 로컬 빌드 후 배포
- CI 없음 — PR 시 코드 품질 서버 검증 불가
- E2E 없음 — UI API 장애 사전 감지 불가
- git hooks 존재하나 로컬에서만 동작 (GitHub Actions 미적용)
- Docker 이미지 없음 — 일관된 배포 단위 부재
TO-BE
- GitHub Actions CI: PR to main 시 전체 테스트 + E2E + 빌드 자동 실행
- GitHub Actions CD: main 머지 시 Docker 이미지 빌드 → ghcr.io 푸시 → SSH 배포
- Playwright E2E: 핵심 UI 플로우 6개, API 실패 자동 감지
- Dockerfile: 백엔드(Spring Boot) + 프론트엔드(Next.js) 이미지화
- Branch Protection: CI 필수 통과 후 머지 가능
Possible Solutions
CI/CD 플랫폼
| 방안 | 설명 | 왜 채택 | 미채택 대안 |
|---|---|---|---|
| GitHub Actions (채택) | GitHub 내장, 별도 서버 불필요, 레포 연동 즉시 | 코드가 GitHub에 있어 추가 설정 없음. Public 무제한 무료 | Jenkins(별도 서버), GitLab CI(플랫폼 이전), CircleCI(유료) |
E2E 도구
| 방안 | 설명 | 왜 채택 | 미채택 대안 |
|---|---|---|---|
| Playwright (채택) | Microsoft 제공, API 인터셉트 내장, CI 최적화 | Next.js 공식 권장. page.on('response')로 API 실패 자동 감지 용이 | Cypress(메모리 높음·CI 느림), Selenium(레거시·설정 복잡) |
배포 전략
| 방안 | 설명 | 왜 채택 | 미채택 대안 |
|---|---|---|---|
| Docker Compose + SSH (채택) | 기존 docker-compose.yml 확장, SSH로 서버 접근 후 pull & up | 이미 docker-compose.yml 존재. 인프라 비용 없음 | K8s(오버스펙), Railway/Render(vendor lock-in), Heroku(유료) |
E2E 실행 환경
| 방안 | 설명 | 왜 채택 | 미채택 대안 |
|---|---|---|---|
| CI docker service + bootRun (채택) | MySQL을 GitHub Actions service 컨테이너로, 백엔드를 bootRun으로 기동 | Docker 이미지 없어도 실행 가능. CI 단계별 독립성 유지 | Docker Compose 전체 빌드(느림·이미지 선행 필요), 외부 스테이징 서버(비용) |
Detail Design
CI 파이프라인 (.github/workflows/ci.yml)
PR to main 트리거
├── Job: test-backend
│ GitHub Actions service: MySQL 8.0
│ ./gradlew test --no-daemon
│
├── Job: test-frontend
│ npm ci && npm test
│
├── Job: e2e (needs: test-backend, test-frontend)
│ GitHub Actions service: MySQL 8.0
│ ./gradlew bootRun & (백그라운드 기동, health 대기)
│ npm run dev & (프론트 기동, health 대기)
│ npx playwright test e2e/smoke/
│
└── Job: build (needs: e2e)
./gradlew build -x test
docker build backend/
docker build frontend/
CD 파이프라인 (.github/workflows/cd.yml)
main push 트리거
├── Job: build-and-push
│ docker build backend/ → ghcr.io/biuea3866/stock-backend:latest
│ docker build frontend/ → ghcr.io/biuea3866/stock-frontend:latest
│
└── Job: deploy (needs: build-and-push)
SSH → 프로덕션 서버
docker-compose -f docker-compose.prod.yml pull
docker-compose -f docker-compose.prod.yml up -d
Docker 구성
backend/Dockerfile
eclipse-temurin:21-jre-alpine
COPY build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
frontend/Dockerfile (Multi-stage, standalone output)
Stage 1 (builder): node:20-alpine → npm ci → next build
Stage 2 (runner): node:20-alpine → standalone 결과물 복사
EXPOSE 3000
CMD ["node", "server.js"]
docker-compose.prod.yml (3개 서비스)
mysql: ghcr.io MySQL 8.0, healthcheck
backend: ghcr.io/biuea3866/stock-backend:latest
frontend: ghcr.io/biuea3866/stock-frontend:latestE2E 테스트 구조
frontend/e2e/
helpers/
api-monitor.ts ← 모든 API 호출을 수집, 4xx/5xx 시 에러 throw
smoke/
homepage.spec.ts ← 메인 화면 로딩 + API 정상 여부
stocks.spec.ts ← 종목 목록 조회
watchlist.spec.ts ← 관심종목 CRUD
order.spec.ts ← 주문 화면 접근
alert.spec.ts ← 알림 설정 화면
chat.spec.ts ← AI 챗봇 접근
api-monitor.ts 동작 방식
page.on('response', response => {
if (response.url().includes('/api/') && response.status() >= 400) {
errors.push(`${response.status()} ${response.url()}`)
}
})
// 테스트 종료 시 errors.length > 0 이면 failComponent Diagram
flowchart LR subgraph GitHub PR[Pull Request] Main[main merge] end subgraph CI["CI (ci.yml)"] BTest[test-backend] FTest[test-frontend] E2E[e2e] Build[build] end subgraph CD["CD (cd.yml)"] Push[build-and-push] Deploy[deploy SSH] end subgraph Server["프로덕션 서버"] BE[Spring Boot :8080] FE[Next.js :3000] DB[MySQL :3306] end PR --> CI CI -->|pass| Main Main --> CD Push --> GHCR[ghcr.io] Deploy --> Server GHCR -.->|pull| Server
Sequence Diagram (PR QA 흐름)
sequenceDiagram participant Dev as 개발자 participant GH as GitHub participant CI as GitHub Actions CI participant Prod as 프로덕션 서버 Dev->>GH: PR 생성 GH->>CI: ci.yml 트리거 CI->>CI: test-backend (5분) CI->>CI: test-frontend (1분) CI->>CI: e2e smoke (3분) CI->>CI: build check (2분) CI-->>GH: CI pass Dev->>GH: PR 머지 (CI 통과 후 가능) GH->>CI: cd.yml 트리거 CI->>CI: Docker 빌드 + ghcr.io 푸시 CI->>Prod: SSH → docker-compose up -d Prod-->>GH: 배포 완료
ERD
변경 없음 (인프라 레이어만 추가)
Testing Plan
- BE 단위 테스트 (Kotest + MockK): 도메인·앱 레이어 비즈니스 로직 검증 —
test-backend잡 - BE 통합 테스트 (Kotest + TestContainers): 인프라·프레젠테이션 레이어, 실제 MySQL 연동 검증 —
test-backend잡 - FE 단위 테스트 (vitest): 컴포넌트·훅 동작 검증 —
test-frontend잡 - E2E 스모크 테스트 (Playwright): 핵심 UI 플로우 6개(메인·종목·관심종목·주문·알림·챗봇) 검증 —
e2e잡 - API 장애 감지 (Playwright response listener): 모든
/api/*호출에서 4xx/5xx 응답 자동 감지 → 테스트 실패 처리 —e2e잡
Observability
| 항목 | 방법 |
|---|---|
| CI 결과 | GitHub PR Status Check (required) |
| CD 결과 | GitHub Actions 성공/실패 알림 (이메일) |
| E2E 실패 상세 | Playwright HTML Report (CI artifact, 7일 보관) |
| 배포 이력 | GitHub Actions 실행 로그 |
Release Scenario
- 개발자 PR 생성 → CI 자동 실행 (~11분)
- CI 통과 → 코드 리뷰(기존 code-reviewer hook) → main 머지
- main 머지 → CD 자동 트리거 → Docker 빌드 + 배포 (~5분)
- 배포 완료 → GitHub Actions 성공 알림
롤백: 이전 이미지 태그로 재배포
# 서버에서 실행
IMAGE_TAG=<이전 SHA> docker-compose -f docker-compose.prod.yml up -dProject Information
- 담당자: biuea
- 스택: GitHub Actions, Playwright, Docker, docker-compose
- 티켓: STK9-01 ~ STK9-06
Document History
| 날짜 | 변경 내용 | 작성자 |
|---|---|---|
| 2026-06-20 | 최초 작성 | biuea |