Spotless/JaCoCo/ESLint/커버리지 게이트와 로컬 교정법, main 보호 규칙과 학생 PR 실습 흐름을 CI.md에 정리. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.9 KiB
CI — Gitea Actions (push 시 자동 테스트)
push/PR마다 백엔드 테스트 + 프론트 빌드를 자동으로 돌려, 회귀를 배포 전에 잡는다. 워크플로 정의:
.gitea/workflows/ci.yml
구성 요소
| 요소 | 위치 | 역할 |
|---|---|---|
| Actions 활성화 | docker-compose.prod.yml gitea env GITEA__actions__ENABLED=true |
Gitea에 Actions 기능 켜기 |
| 러너(runner) | compose 서비스 runner(gitea/act_runner) |
일감을 받아 job 컨테이너를 띄우는 일꾼 |
| 러너 설정·등록 | 호스트 /opt/mirim-runner/ (config.yaml, .runner) |
최초 등록 정보·job 컨테이너 네트워크 설정 |
| 워크플로 | .gitea/workflows/ci.yml |
무엇을 검사할지(백엔드 test, 프론트 build) |
동작 흐름
- 누가 push하면 Gitea가 워크플로를 큐에 넣는다.
- 러너가 job을 받아
catthehacker/ubuntu:act-22.04이미지로 job 컨테이너를 띄운다. - job 컨테이너는
deploy_default네트워크 +--add-host edu.awesomedevapp.com:host-gateway로 Gitea에서 코드를 clone한 뒤, 백엔드mvn test/ 프론트npm ci && npm run build를 실행한다. - 하나라도 실패하면 커밋에 빨간 X, 다 통과하면 초록 체크가 붙는다. (Gitea 웹 → Actions 탭)
러너를 처음부터 다시 세워야 한다면
# 1) Actions 활성화 (compose env, 이미 되어 있음) 후 gitea 재기동
# 2) 등록 토큰 발급
sudo docker exec -u git mirim-gitea gitea --config /data/gitea/conf/app.ini actions generate-runner-token
# 3) 설정 파일 (/opt/mirim-runner/config.yaml)
# labels: ubuntu-latest → catthehacker/ubuntu:act-22.04
# container.network: deploy_default
# container.options: --add-host=edu.awesomedevapp.com:host-gateway ← job이 Gitea에 clone하려면 필수
# 4) 최초 1회만 토큰으로 등록 (등록되면 .runner 생성, 이후 compose가 관리)
sudo docker run --rm --network deploy_default \
-v /var/run/docker.sock:/var/run/docker.sock -v /opt/mirim-runner:/data -w /data \
-e GITEA_INSTANCE_URL=http://mirim-gitea:3000 \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<토큰> -e CONFIG_FILE=/data/config.yaml \
gitea/act_runner:0.2.11
# 5) compose로 상시 실행
sudo docker compose -f deploy/docker-compose.prod.yml up -d runner
함정 메모 (겪은 것)
- Maven 다운로드: dlcdn 미러는 옛 버전을 지운다(3.9.9가 404로 사라져 CI가 깨졌었다).
→ 모든 버전을 영구 보관하는
archive.apache.org를 쓴다. - clone 실패: job 컨테이너가 Gitea(
edu.awesomedevapp.com:3000)에 못 닿으면 checkout이 실패한다. →--add-host edu.awesomedevapp.com:host-gateway로 컨테이너에서 호스트(→ 게시된 3000)로 잇는다. - actions/checkout은 node 필요: job 이미지에 node가 있어야 한다(catthehacker 이미지에 포함). 그래서 백엔드 job도 이 이미지를 쓰고 Maven만 따로 내려받는다.
배포 스모크 테스트 (deploy/smoke-test.sh)
CI는 "코드"를 배포 전에 검사한다. 하지만 실제로 났던 사고(보안헤더로 학습 문서 iframe이 안 뜬 것)는 코드가 아니라 Caddy 설정 문제라 CI 테스트로는 안 잡혔다. 스모크 테스트는 배포된 살아있는 사이트를 실제 사용자처럼 두드려, 설정·헤더·인프라까지 포함해 "정말 열리는가"를 확인한다. → CI(코드 그물) + 스모크(설정·운영 그물).
# 배포 직후 반드시 실행 — 하나라도 실패하면 exit 1
bash deploy/smoke-test.sh
검사 항목(11): 홈 200 · HTTP→HTTPS 리다이렉트 · 보안헤더 3종 · 학습 문서 200 + X-Frame-Options=SAMEORIGIN + no-cache + 한국어 본문(그 회귀를 직접 감지) · 백엔드 API 401(Caddy→백엔드→DB 경로) · SPA 라우트 폴백.
권장 배포 흐름: 빌드 → 업로드 →
docker compose up→bash deploy/smoke-test.sh. 스모크가 빨간불이면 배포에 문제가 있는 것이니 바로 롤백/수정한다.
품질 게이트 (push/PR마다 자동 검사)
CI가 통과해야 하는 관문이 아래처럼 늘었다. 로컬에서 먼저 돌려 보고 밀면 빨간불을 피한다.
| 게이트 | 무엇을 막나 | 로컬에서 고치는 법 |
|---|---|---|
| Spotless(백엔드) | 안 쓰는 import·줄끝 공백·파일끝 개행 | cd backend && mvn spotless:apply |
| JaCoCo(백엔드) | 라인 커버리지 30% 미만으로 후퇴 | 테스트 추가 (mvn test 후 target/site/jacoco/index.html) |
| ESLint(프론트) | 안 쓰는 변수·없는 전역·React 훅 오용 | cd frontend && npm run lint |
| 커버리지(프론트) | 채점 로직 runSql.js 40% 미만 |
npm run coverage 로 확인 후 테스트 보강 |
커버리지 바닥(30%/40%)은 "지금보다 나빠지지 마라"는 **래칫(ratchet)**이다. 테스트를 늘리면 이 숫자도 함께 올려 바닥을 끌어올리는 게 건강한 흐름.
main 브랜치 보호 (PR 워크플로)
main은 보호 규칙이 걸려 있다(Gitea → 저장소 설정 → 브랜치):
- CI 두 개(backend-test·frontend-build)가 초록불이어야 merge 가능
- PR에 승인 1개 필요 (멘토가 학생 PR을 리뷰·승인)
- 학생은
main에 직접 push 불가 → 브랜치 만들어 PR 여는 흐름을 익힌다 - 운영·배포용으로 관리자 계정만 직접 push 허용(화이트리스트)
학생 실습 흐름: git switch -c feat/내작업 → 커밋 → push → Gitea에서 PR 생성 →
CI 초록불 + 멘토 승인 → merge. (실무 표준 흐름을 그대로 경험)
다음 개선 (학생 티켓)
- Maven·npm 캐시로 CI 실행 시간 단축
- 커버리지 바닥을 단계적으로 상향(30%→40%→…)
- 스모크 테스트를 배포 스크립트에 자동 편입(deploy 후 자동 실행·실패 시 알림)
- E2E 테스트(Playwright: 로그인→강좌→퀴즈)