같이 하고 싶은 프로젝트에 합류하게 되었고 마침 공부중인 테스트를 적용하고 싶었다
그러나 결합도가 높아 테스트하기 용이한 코드가 아니었고 이를 함수 단위로 분리하는 리팩토링 과정을 거쳤다
예상했지만 역시 regression (이전에 제대로 작동하던 SW 기능에 문제가 생기는 버그) 이 발생했다
프로젝트는 점점 성장하면서 복잡해진다. 지금 같이 프로젝트 초기에 테스트를 구축하여 지속적인 코드 통합을 가져가는 것이 트러블 슈팅하기 쉽지 않을까?
개발자가 매번 빌드 및 테스트 스크립트를 실행하여 저장소에 push 하는 프로세스는 번거롭기도 하고 깜빡할 수 있기 때문에 CI 프로세스를 자동화를 하고 싶다
현재 프로젝트의 CD 는 vercel 이 담당 하고 있으니 나는 Github actions 를 사용하여 CI 자동화를 하기로 했다
“지속적 통합” 이라는 뜻으로 여러 개발자가 개발을 진행하면서 빌드와 테스트 과정을 자동화하여 동일한 코드를 품질로 유지 및 관리하는 개념
vscode extention 을 설치한다.
.github/workflow 의 yaml 파일의 대략적인 구조를 설명한 이미지
name: CI # 액션의 이름
run-name: ${{ github.actor }} (${{ github.actor_id }}) # 액선을 실행할 때 구분할 명칭
on: # 액션 트리거 이벤트
pull_request: # main 브랜치를 제외한 모든 브랜치에 대해 PR 가 발생한 경우 트리거
branches-ignore:
- main
push: # main, develop 브랜치를 제외한 모든 브랜치에 push 가 발생한 경우 트리거
branches-ignore: [main, develop]
jobs:
build: ...
test: ...
push
와 pull_request
이벤트다.push
특정 브랜치에 코드가 push 될 때마다 workflow 가 실행된다.
이 단계에서 CI 과정을 수행하면 코드 변경 사항이 저장소에 반영되기 전에 문제가 없는지 확인할 수 있다
pull_request
새로운 PR 가 열리거나 PR 가 업데이트 될 때마다 workflow 가 실행된다.
이 단계에서 CI 과정을 수행하면 코드 변경 사항이 main 브랜치에 merge 되기 전에 문제가 없는지 확인할 수 있다
추가적으로 고려할 수 있는 이벤트
schedule
특정 시간 간격으로 workflow 를 실행하여 정기적으로 빌드 및 테스트를 수행할 수 있다workflow_dispatch
수동으로 workflow 를 실행하여 필요할 때마다 빌드 및 테스트를 수행할 수 있다pull_request
가 close
상태일 때 CI 를 실행하지 않는 이유PR merge 여부를 확인하는 것이 더 중요하다
PR 을 닫는 시점에서는 PR 이 이미 병합되었는지, 아니면 닫히고 있는지를 확인하는 것이 더 중요하다
PR 가 병합된 경우에는 이미 빌드와 테스트가 수행되었을 것이므로, 다시 실행할 필요가 없다
코드 변경 사항 없음
PR 를 닫을 때는 코드 변경 사항이 없다
빌드와 테스트는 주로 코드 변경 사항에 대한 검증을 위해 수행되므로, PR 를 닫는 시점에서는 이러한 작업이 불필요하다
리소스 낭비 방지
빌드와 테스트는 일반적으로 시간과 리소스가 소모되는 작업이다
PR 를 닫는 시점에서 불필요한 빌드와 테스트를 수행하면 리소스를 낭비할 수 있다
PR Merge 시 검증 수행
대신에 PR 를 merge 할 때 빌드와 테스트를 수행하는 것이 더 적절하다
PR 가 main 브랜치에 merge 되기 전에 코드 변경 사항에 대한 검증을 수행하는 것이 중요하다
따라서 GitHub Actions Workflow에서는 일반적으로 pull_request
이벤트의 types
에 closed
를 포함시키지 않는다
대신에 opened
, reopened
, synchronize
등의 types
을 사용하여 PR이 열리거나 코드 변경 사항이 있을 때 빌드와 테스트를 수행한다
pull_request
의 close
시점에 특정 작업 (PR 관련 통계 수집, 알림 전송, 리소스 정리) 을 수행하야 하는 경우 closed
이벤트 타입을 추가하여 필요한 작업을 정의할 수 있다
# FE-CI-dev.yaml
# 개발 환경 액션
# 트리거 이벤트 및 개발 환경 변수 설정
name: CI - Development
run-name: ${{ github.actor }} (${{ github.actor_id }})
on:
pull_request:
branches-ignore:
- main
push:
branches-ignore: [main, develop]
# FE-CI-prod.yaml
# 배포 환경 액션
# 트리거 이벤트 및 배포 환경 변수 설정
name: CI - Production
run-name: ${{ github.actor }} (${{ github.actor_id }})
on:
pull_request:
branches: main
빌드시 노드 모듈과 빌드 결과를 캐싱하여 Test 단계에서 재사용하여 workflow 실행시간을 개선한다
jobs: # workflow 작업 목록
build: # build 작업 정의
runs-on: ubuntu-latest # runner 서버 지정
strategy: # 작업 내부에서 사용할 전략 정의
matrix: # 작업 내부에서 사용할 내부 변수 목록
node-version: ['18.x'] # 사용할 Node.js의 버전을 변수화
steps: # 작업을 구성 단계
- name: Checkout # 코드 체크아웃 (가장 최신 버전의 HEAD commit 코드 사용)
uses: actions/checkout@v4 # github에서 공식적으로 제공하는 체크아웃 액션
- name: Install Node.js ${{ matrix.node-version }} # Node 설치
uses: actions/setup-node@v4
with: # 액션에 전달할 인자
node-version: ${{ matrix.node-version }} # matrix 에서 정의한 Node.js 버전 변수
- name: Cache Node modules # node_modules 캐싱
id: cache # 이 단계의 식별자
uses: actions/cache@v3
with:
path: '**/node_modules' # 캐시할 경로
# 캐시 키 지정
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies # 의존성 설치
if: steps.cache.outputs.cache-hit != 'true' # 캐시 히트가 아닐 경우에만 실행
run: npm ci # package-lock.json 에 정의된 버전으로 설치
- name: Run build # 빌드 실행
env: # 이 단계에서 사용할 환경 변수
VITE_SERVER_URL: ${{ secrets.VITE_SERVER_URL }} # github secret 환경 변수 사용
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
VITE_KAKAO_JS_SDK_KEY: ${{ secrets.VITE_KAKAO_JS_SDK_KEY }}
VITE_GOOGLE_ID: ${{ secrets.VITE_GOOGLE_ID }}
run: npm run build # 프로젝트 빌드 미리 정의한 환경변수 사용시 -- development 등을 사용
- uses: actions/upload-artifact@v3 # 빌드 결과물을 재사용하기 위해 업로드
with:
name: build-output # 업로드할 파일의 이름
path: dist # 업로드할 파일의 경로 빌드시 지정한 폴더명을 사용해야 한다.
test: ... # 테스트 작업 정의 (생략)
"build"와 "test" 작업이 동일한 값을 사용하여 캐시 키를 생성하기 때문에 캐시를 공유할 수 있다
test 작업의 Install Dependencies 단계에서 build 작업에서 캐시된 node_modules 를 캐시 키로 접근할 수 있기 때문에 test 작업에서 Cache Node modules 단계가 필요 없다고 생각했다.
하지만 "Cache Node modules" 단계가 "test" 작업에 포함하는 이유가 있다
jobs:
build: ...
test:
needs: build # build 작업을 완료하고 test 작업을 수행
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['18.x']
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Cache Node modules # 캐시 접근
id: cache
uses: actions/cache@v3
with:
path: '**/node_modules'
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
- name: Install Dependencies # 캐시 사용
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
- name: Download artifact # build 단계에서 업로드한 빌드 결과물 다운로드
uses: actions/download-artifact@v3
with:
name: build-output
path: dist
- name: Run test # 테스트 수행
env:
VITE_SERVER_URL: ${{ secrets.VITE_SERVER_URL }}
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
VITE_KAKAO_JS_SDK_KEY: ${{ secrets.VITE_KAKAO_JS_SDK_KEY }}
VITE_GOOGLE_ID: ${{ secrets.VITE_GOOGLE_ID }}
run: npm run test
vitest-coverage-report-action 를 사용해 PR 결과에 test coverage 가 추가되면 좋을 것 같다
https://github.com/lirantal/is-website-vulnerable 를 사용해 특정 사이트 취약점 검사