🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

자동 테스트 CI 파이프라인 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 11. 28. · 14 Views

자동 테스트 CI 파이프라인 완벽 가이드

GitHub Actions를 활용하여 코드 품질을 자동으로 검증하는 CI 파이프라인 구축 방법을 다룹니다. 린트 검사부터 테스트 실행, 커버리지 리포트, PR 코멘트 자동화까지 실무에서 바로 적용할 수 있는 내용을 담았습니다.


목차

  1. 린트_검사_자동화
  2. 유닛_테스트_실행
  3. 테스트_커버리지_리포트
  4. PR_코멘트로_결과_표시
  5. Status_Check_설정
  6. Required_Checks_보호

1. 린트 검사 자동화

김개발 씨는 새벽까지 열심히 작성한 코드를 자신 있게 PR로 올렸습니다. 그런데 아침에 출근해보니 선배가 남긴 리뷰 코멘트가 수십 개나 달려 있었습니다.

"여기 세미콜론 빠졌어요", "들여쓰기가 탭이랑 스페이스 섞여 있네요"... 기능과는 전혀 상관없는 스타일 지적이 대부분이었습니다.

린트 검사 자동화는 코드가 저장소에 푸시될 때마다 자동으로 코드 스타일과 문법 오류를 검사하는 것입니다. 마치 출판사의 교정 담당자가 책이 인쇄되기 전에 맞춤법과 띄어쓰기를 검토하는 것과 같습니다.

이것을 CI 파이프라인에 통합하면 사람이 일일이 스타일을 확인할 필요가 없어지고, 코드 리뷰에서는 정말 중요한 로직에만 집중할 수 있게 됩니다.

다음 코드를 살펴봅시다.

# .github/workflows/lint.yml
name: Lint Check

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      # 저장소 코드를 체크아웃합니다
      - uses: actions/checkout@v4

      # Node.js 환경을 설정합니다
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      # 의존성을 설치합니다
      - run: npm ci

      # ESLint로 코드 스타일을 검사합니다
      - name: Run ESLint
        run: npm run lint

김개발 씨는 입사 3개월 차 주니어 개발자입니다. 오늘도 야근을 하며 새로운 기능을 완성했습니다.

뿌듯한 마음으로 PR을 올리고 퇴근했는데, 다음 날 확인해보니 코드 리뷰에 빨간 불이 잔뜩 들어와 있었습니다. "세미콜론이 빠졌습니다", "console.log를 지워주세요", "들여쓰기가 2칸이어야 합니다"...

이런 코멘트가 스무 개가 넘었습니다. 정작 중요한 로직에 대한 피드백은 단 두 개뿐이었습니다.

선배 박시니어 씨도 이런 리뷰를 작성하느라 30분을 허비했다며 한숨을 쉬었습니다. 그렇다면 린트 검사 자동화란 정확히 무엇일까요?

쉽게 비유하자면, 린트는 마치 워드 프로세서의 맞춤법 검사기와 같습니다. 글을 쓸 때 빨간 밑줄이 자동으로 그어지면서 오타를 알려주는 것처럼, 린트는 코드를 작성할 때 스타일 규칙에 어긋나는 부분을 찾아냅니다.

이것을 CI 파이프라인에 연결하면 코드가 저장소에 올라갈 때마다 자동으로 검사가 실행됩니다. 린트 자동화가 없던 시절에는 어땠을까요?

개발자들은 코드 리뷰 시간의 상당 부분을 스타일 지적에 사용해야 했습니다. "여기 띄어쓰기 좀 맞춰주세요"라는 말을 하루에도 수십 번 반복했습니다.

더 큰 문제는 사람마다 코딩 스타일이 달라서 같은 프로젝트 안에서도 코드가 뒤죽박죽 섞이는 것이었습니다. 누군가는 탭을, 누군가는 스페이스를 썼고, 프로젝트가 커질수록 이런 혼란은 눈덩이처럼 불어났습니다.

바로 이런 문제를 해결하기 위해 린트 검사 자동화가 등장했습니다. GitHub Actions를 사용하면 코드가 푸시될 때마다 자동으로 ESLint, Prettier 같은 도구가 실행됩니다.

규칙에 어긋나는 코드가 있으면 빌드가 실패하고, 개발자는 PR을 병합하기 전에 문제를 수정해야 합니다. 이렇게 하면 코드 리뷰어는 스타일 문제에 신경 쓸 필요 없이 오직 비즈니스 로직에만 집중할 수 있습니다.

위의 워크플로우 코드를 한 줄씩 살펴보겠습니다. 먼저 on 섹션을 보면 이 워크플로우가 언제 실행되는지 정의합니다.

main과 develop 브랜치에 푸시하거나, main을 대상으로 PR을 열 때 자동으로 실행됩니다. 다음으로 actions/checkout@v4는 저장소의 코드를 CI 서버로 가져오는 역할을 합니다.

npm ci는 package-lock.json을 기반으로 정확한 버전의 패키지를 설치합니다. 마지막으로 npm run lint가 실제 린트 검사를 수행합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 10명의 개발자가 함께 일하는 스타트업을 생각해봅시다.

하루에 평균 20개의 PR이 올라온다고 가정하면, 린트 자동화 없이는 각 PR마다 10분씩 스타일 리뷰에 시간을 쓰게 됩니다. 하루에 200분, 일주일이면 거의 17시간을 허비하는 셈입니다.

린트 자동화를 도입하면 이 시간을 거의 0으로 줄일 수 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 린트 규칙을 너무 엄격하게 설정하는 것입니다. 모든 규칙을 다 켜버리면 기존 코드에서 수백 개의 에러가 터져 나오고, 팀원들의 불만이 쏟아지게 됩니다.

따라서 처음에는 가장 중요한 규칙 몇 가지만 적용하고, 점진적으로 규칙을 추가해 나가는 것이 현명합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨가 린트 자동화를 도입한 후, 김개발 씨는 PR을 올리기 전에 로컬에서 먼저 린트를 돌려보는 습관이 생겼습니다. CI에서 빨간 불이 들어오면 스스로 부끄러워지니까요.

코드 리뷰 시간은 절반으로 줄었고, 팀 전체의 코드 스타일도 놀라울 정도로 일관성 있게 바뀌었습니다.

실전 팁

💡 - 로컬에서도 린트를 실행할 수 있도록 huskylint-staged를 설정하면 커밋 전에 미리 문제를 잡을 수 있습니다

  • ESLint와 Prettier를 함께 사용할 때는 충돌을 방지하기 위해 eslint-config-prettier를 설치하세요

2. 유닛 테스트 실행

김개발 씨가 새로 만든 할인 계산 함수가 운영 서버에 배포된 날, 고객센터에 전화가 빗발쳤습니다. "결제 금액이 마이너스로 나와요!" 확인해보니 특정 조건에서 음수가 반환되는 버그였습니다.

불과 10줄짜리 함수에서 벌어진 일이었습니다.

유닛 테스트 실행 자동화는 코드 변경이 발생할 때마다 자동으로 테스트를 돌려서 기존 기능이 깨지지 않았는지 확인하는 것입니다. 마치 공장에서 제품을 출하하기 전에 품질 검사를 하는 것과 같습니다.

CI 파이프라인에 테스트를 통합하면 버그가 있는 코드가 절대로 메인 브랜치에 병합되지 않도록 막을 수 있습니다.

다음 코드를 살펴봅시다.

# .github/workflows/test.yml
name: Unit Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      # Jest로 유닛 테스트를 실행합니다
      - name: Run Tests
        run: npm test -- --ci --reporters=default

      # 테스트 결과를 아티팩트로 저장합니다
      - name: Upload Test Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: coverage/

김개발 씨는 그날 밤 회사에서 긴급 패치를 진행해야 했습니다. 단 한 줄의 조건문 실수가 회사에 수백만 원의 손해를 입힌 것입니다.

고객의 신뢰도 함께 떨어졌습니다. 박시니어 씨가 다가와 물었습니다.

"테스트 코드는 작성했었나요?" 김개발 씨는 고개를 저었습니다. "시간이 없어서요..." 박시니어 씨는 한숨을 쉬며 말했습니다.

"테스트 코드를 작성하는 데 30분이 걸리고, 버그를 수정하는 데 6시간이 걸렸네요." 그렇다면 유닛 테스트 자동화란 정확히 무엇일까요? 쉽게 비유하자면, 유닛 테스트는 마치 자동차 공장의 품질 검사 라인과 같습니다.

차가 조립될 때마다 브레이크는 제대로 작동하는지, 에어백은 터지는지 하나하나 검사합니다. 문제가 발견되면 즉시 라인을 멈추고 수정합니다.

출고된 후에 고객이 사고를 당하는 것보다 공장에서 미리 잡는 것이 훨씬 낫기 때문입니다. 테스트 자동화가 없던 시절에는 어땠을까요?

개발자들은 코드를 수정한 후 직접 브라우저를 열어 이것저것 클릭해가며 테스트했습니다. 하지만 사람의 기억력에는 한계가 있습니다.

100개의 기능 중 99개를 테스트하고 딱 1개를 놓치면, 그 1개가 버그가 되어 고객에게 도달합니다. 게다가 수동 테스트는 시간도 오래 걸려서 "이번엔 안 터지겠지"라며 건너뛰는 일이 잦았습니다.

바로 이런 문제를 해결하기 위해 테스트 자동화가 등장했습니다. GitHub Actions에서 테스트를 실행하면 PR이 올라올 때마다 자동으로 모든 테스트 케이스가 돌아갑니다.

하나라도 실패하면 빌드가 실패하고, 해당 PR은 병합할 수 없게 됩니다. 실수로 버그가 있는 코드를 올려도 CI가 문지기처럼 막아주는 것입니다.

위의 워크플로우 코드를 한 줄씩 살펴보겠습니다. npm test -- --ci 명령어에서 --ci 플래그는 Jest를 CI 환경에 최적화된 모드로 실행합니다.

대화형 감시 모드가 비활성화되고, 에러 발생 시 자세한 정보를 출력합니다. if: always() 조건은 테스트가 실패하더라도 결과를 업로드하도록 보장합니다.

이렇게 하면 실패한 테스트의 상세 내역을 나중에 확인할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 결제 시스템을 개발한다고 가정해봅시다. 카드 결제, 계좌이체, 포인트 결제 등 다양한 결제 수단이 있고, 각각 수십 가지 예외 상황이 존재합니다.

사람이 매번 이걸 다 테스트하는 것은 불가능합니다. 하지만 테스트 코드로 모든 케이스를 자동화해두면 매 배포 전에 수백 개의 시나리오를 단 몇 분 만에 검증할 수 있습니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 테스트를 너무 세세하게 작성하는 것입니다.

구현 세부사항을 테스트하면 코드를 조금만 리팩토링해도 테스트가 와르르 깨집니다. 따라서 외부에서 보이는 동작, 즉 입력과 출력을 기준으로 테스트하는 것이 바람직합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 그날 이후 김개발 씨는 기능을 구현하기 전에 테스트 코드부터 작성하는 습관을 들였습니다.

처음에는 번거로웠지만, 한 달이 지나자 버그 발생률이 눈에 띄게 줄어들었습니다. 무엇보다 코드를 수정할 때 두려움이 사라졌습니다.

테스트가 통과하면 자신 있게 배포할 수 있으니까요.

실전 팁

💡 - 테스트 실행 시간이 길어지면 --maxWorkers 옵션으로 병렬 실행 워커 수를 조절하세요

  • 데이터베이스가 필요한 테스트는 testcontainers를 활용하면 CI에서도 안정적으로 실행할 수 있습니다

3. 테스트 커버리지 리포트

김개발 씨가 자랑스럽게 "테스트 100개 작성했습니다!"라고 보고했습니다. 그런데 박시니어 씨가 커버리지 리포트를 보더니 고개를 갸웃거렸습니다.

"그런데 결제 모듈은 하나도 테스트 안 됐네요?"

테스트 커버리지 리포트는 전체 코드 중에서 얼마나 많은 부분이 테스트로 검증되었는지를 수치로 보여주는 것입니다. 마치 건강검진에서 여러 항목을 검사하고 종합 점수를 매기는 것과 같습니다.

커버리지 리포트를 CI에 통합하면 테스트가 부족한 영역을 한눈에 파악하고, 팀 전체가 코드 품질을 객관적으로 관리할 수 있게 됩니다.

다음 코드를 살펴봅시다.

# .github/workflows/coverage.yml
name: Test Coverage

on:
  pull_request:
    branches: [main]

jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      # 커버리지와 함께 테스트를 실행합니다
      - name: Run Tests with Coverage
        run: npm test -- --coverage --coverageReporters=json-summary

      # 커버리지 임계값을 확인합니다
      - name: Check Coverage Threshold
        run: |
          COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
          echo "Line coverage: $COVERAGE%"
          if (( $(echo "$COVERAGE < 80" | bc -l) )); then
            echo "Coverage is below 80%!"
            exit 1
          fi

김개발 씨는 테스트 코드를 열심히 작성했다고 생각했습니다. 100개나 되는 테스트 케이스가 모두 통과했으니까요.

그런데 박시니어 씨가 보여준 커버리지 리포트에는 충격적인 숫자가 적혀 있었습니다. 전체 코드의 35%만 테스트로 검증되고 있었습니다.

"100개를 작성했는데 왜 이렇게 낮죠?" 김개발 씨가 물었습니다. 박시니어 씨가 대답했습니다.

"테스트 개수보다 중요한 건 어떤 코드를 테스트하느냐예요. 유틸 함수만 100번 테스트해봤자, 핵심 비즈니스 로직이 검증 안 되면 의미가 없어요." 그렇다면 테스트 커버리지란 정확히 무엇일까요?

쉽게 비유하자면, 커버리지는 마치 시험 범위와 같습니다. 수학 시험을 앞두고 공부를 했는데, 1장부터 5장까지가 범위인데 1장만 열심히 공부했다면 어떨까요?

아무리 1장을 완벽하게 이해해도 시험 성적은 20점이 나올 수밖에 없습니다. 테스트 커버리지도 마찬가지입니다.

전체 코드 중에서 테스트가 실제로 실행한 부분이 얼마나 되는지를 백분율로 알려줍니다. 커버리지 리포트가 없던 시절에는 어땠을까요?

개발자들은 "충분히 테스트했다"라는 감에 의존했습니다. 하지만 감은 틀릴 때가 많습니다.

본인이 작성한 코드는 무의식적으로 피해가기도 하고, 복잡한 분기문은 한두 가지 경우만 테스트하고 넘어가기 쉽습니다. 결과적으로 테스트가 있어도 사각지대가 생기고, 거기서 버그가 터지곤 했습니다.

바로 이런 문제를 해결하기 위해 커버리지 리포트가 등장했습니다. Jest 같은 테스트 프레임워크에서 --coverage 옵션을 주면 테스트 실행과 동시에 어떤 라인이 실행되었는지 추적합니다.

그 결과를 HTML이나 JSON으로 출력하면, 코드의 어느 부분이 테스트되었고 어느 부분이 빠졌는지 한눈에 볼 수 있습니다. CI에서 이 값을 확인해서 80% 미만이면 빌드를 실패시키면, 팀 전체가 일정 수준 이상의 테스트 커버리지를 유지하도록 강제할 수 있습니다.

위의 워크플로우 코드를 한 줄씩 살펴보겠습니다. --coverageReporters=json-summary 옵션은 커버리지 결과를 JSON 형태로 출력합니다.

이 파일을 파싱해서 전체 라인 커버리지를 추출합니다. jq 명령어는 JSON을 다루는 유틸리티로, 복잡한 JSON 구조에서 원하는 값만 쏙 빼올 수 있습니다.

마지막으로 bc -l을 사용해 소수점 비교를 수행하고, 80% 미만이면 exit 1로 빌드를 실패시킵니다. 실제 현업에서는 어떻게 활용할까요?

대규모 프로젝트에서는 보통 80% 이상의 커버리지를 목표로 합니다. 하지만 무조건 100%를 추구할 필요는 없습니다.

단순한 getter/setter나 설정 파일까지 테스트하는 것은 시간 낭비일 수 있습니다. 중요한 것은 핵심 비즈니스 로직, 특히 돈이나 보안과 관련된 코드가 철저히 테스트되는 것입니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 커버리지 숫자에만 집착하는 것입니다.

커버리지가 높아도 의미 없는 테스트일 수 있습니다. 예를 들어 함수를 호출만 하고 결과를 검증하지 않으면 커버리지는 올라가지만 버그를 잡지 못합니다.

따라서 커버리지는 참고 지표로만 활용하고, 실제로 중요한 시나리오가 검증되는지가 더 중요합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

커버리지 리포트를 분석한 김개발 씨는 결제 모듈과 회원 가입 로직에 테스트가 전혀 없다는 것을 발견했습니다. 일주일에 걸쳐 이 부분에 집중적으로 테스트를 추가했고, 커버리지는 35%에서 78%로 올라갔습니다.

그리고 그 과정에서 숨어 있던 버그 3개를 발견해서 미리 수정할 수 있었습니다.

실전 팁

💡 - CodecovCoveralls 같은 서비스를 연동하면 PR마다 커버리지 변화를 예쁘게 시각화해줍니다

  • 커버리지가 떨어지는 PR은 자동으로 경고하도록 설정하면 품질 하락을 미리 방지할 수 있습니다

4. PR 코멘트로 결과 표시

김개발 씨가 PR을 올리고 CI가 끝나기를 기다렸습니다. 10분 후 빌드가 실패했는데, 무엇이 문제인지 알려면 GitHub Actions 페이지로 들어가서 로그를 뒤져야 했습니다.

"왜 이렇게 불편하지?" 하고 생각하던 찰나, 박시니어 씨가 마법 같은 기능을 보여주었습니다.

PR 코멘트로 결과 표시는 CI 파이프라인의 실행 결과를 PR 페이지에 자동으로 코멘트로 남기는 것입니다. 마치 시험을 보고 나면 선생님이 채점 결과를 직접 책상에 올려주는 것과 같습니다.

개발자는 다른 페이지로 이동하지 않고도 테스트 결과, 커버리지 변화, 린트 오류 등을 한눈에 확인할 수 있습니다.

다음 코드를 살펴봅시다.

# .github/workflows/pr-comment.yml
name: PR Comment

on:
  pull_request:
    branches: [main]

jobs:
  comment:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm test -- --coverage --coverageReporters=json-summary

      # PR에 커버리지 결과를 코멘트로 남깁니다
      - name: Post Coverage Comment
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const coverage = JSON.parse(fs.readFileSync('coverage/coverage-summary.json'));
            const lines = coverage.total.lines.pct;
            const body = `## Test Coverage Report\n\n| Metric | Coverage |\n|--------|----------|\n| Lines | ${lines}% |`;
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: body
            });

김개발 씨는 PR을 올릴 때마다 같은 루틴을 반복했습니다. CI가 끝나기를 기다리고, Actions 탭을 클릭하고, 워크플로우를 선택하고, 로그를 스크롤해서 결과를 확인합니다.

성공했으면 다행이고, 실패했으면 다시 로그를 뒤져서 원인을 찾아야 합니다. 하루에 PR을 다섯 번 올리면 이 과정을 다섯 번 반복해야 했습니다.

어느 날 박시니어 씨의 PR을 보니 신기한 게 있었습니다. PR 페이지에 예쁜 표가 달린 코멘트가 자동으로 달려 있었습니다.

테스트 통과 여부, 커버리지 숫자, 심지어 이전 대비 변화량까지 한눈에 보였습니다. "이거 어떻게 한 거예요?" 그렇다면 PR 코멘트 자동화란 정확히 무엇일까요?

쉽게 비유하자면, 이것은 마치 비서가 보고서를 정리해서 책상 위에 올려놓는 것과 같습니다. 회의에 들어가기 전에 필요한 정보를 한 페이지로 요약해주는 것이죠.

CI 결과도 마찬가지입니다. 개발자가 굳이 찾아다니지 않아도 PR 페이지에 중요한 정보가 자동으로 정리되어 나타납니다.

PR 코멘트 자동화가 없던 시절에는 어땠을까요? 개발자들은 CI 결과를 확인하기 위해 여러 페이지를 오갔습니다.

Actions 탭, 로그 페이지, 때로는 외부 서비스까지. 특히 여러 프로젝트를 담당하는 개발자는 어느 PR에서 무엇이 실패했는지 추적하기가 어려웠습니다.

코드 리뷰어도 마찬가지였습니다. PR을 보면서 "이거 테스트 통과했나?"를 확인하려면 또 다른 페이지를 열어야 했습니다.

바로 이런 문제를 해결하기 위해 PR 코멘트 자동화가 등장했습니다. actions/github-script를 사용하면 워크플로우 안에서 GitHub API를 직접 호출할 수 있습니다.

테스트 결과를 파싱해서 마크다운 형식으로 예쁘게 정리한 다음, PR에 코멘트로 남기면 됩니다. 이 모든 과정이 자동으로 이루어지므로 개발자는 PR 페이지만 보면 모든 정보를 얻을 수 있습니다.

위의 워크플로우 코드를 한 줄씩 살펴보겠습니다. 먼저 permissions 섹션을 보면 pull-requests: write 권한을 요청합니다.

이 권한이 있어야 PR에 코멘트를 달 수 있습니다. actions/github-script@v7은 JavaScript로 GitHub API를 호출할 수 있게 해주는 액션입니다.

context.issue.number는 현재 PR의 번호를 가져오고, github.rest.issues.createComment로 코멘트를 생성합니다. 실제 현업에서는 어떻게 활용할까요?

대규모 팀에서는 코멘트에 더 많은 정보를 담습니다. 예를 들어 이전 커밋 대비 커버리지 변화량, 새로 추가된 테스트 수, 린트 경고 목록 등을 표로 정리합니다.

심지어 커버리지가 떨어지면 경고 이모지를, 올라가면 축하 이모지를 자동으로 붙이기도 합니다. 이런 시각적 피드백은 개발자들이 품질에 더 신경 쓰도록 유도합니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 코멘트를 너무 많이 남기는 것입니다.

커밋할 때마다 코멘트가 달리면 PR 페이지가 난잡해집니다. 따라서 기존 코멘트를 업데이트하거나, 최종 결과만 남기도록 설정하는 것이 좋습니다.

find-commentupdate-comment 액션을 조합하면 같은 코멘트를 계속 업데이트할 수 있습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨가 설정해준 PR 코멘트 자동화 덕분에 김개발 씨는 이제 Actions 탭을 거의 열지 않습니다. PR 페이지 하나에서 코드 변경사항도 보고, 리뷰 코멘트도 보고, CI 결과도 확인합니다.

특히 커버리지가 떨어지면 빨간 경고가 떠서 바로 알 수 있어 편리합니다.

실전 팁

💡 - 코멘트가 중복되지 않도록 marocchino/sticky-pull-request-comment 액션을 사용하면 같은 코멘트를 업데이트합니다

  • 마크다운 표, 접기/펼치기, 배지 등을 활용하면 정보를 더 깔끔하게 전달할 수 있습니다

5. Status Check 설정

김개발 씨가 급하게 버그를 수정하고 PR을 올렸습니다. CI가 돌아가는 동안 "빨리 배포해야 하는데..."라며 초조하게 기다렸습니다.

그런데 실수로 CI가 끝나기도 전에 Merge 버튼을 눌러버렸습니다. 다행히 아무 일도 일어나지 않았습니다.

"왜 병합이 안 되지?"

Status Check는 CI 파이프라인의 실행 결과를 PR에 연결하여 성공/실패 상태를 표시하는 기능입니다. 마치 비행기가 이륙하기 전에 관제탑의 승인을 받아야 하는 것과 같습니다.

모든 Status Check가 통과해야만 초록색 체크마크가 표시되고, 설정에 따라 병합 버튼이 활성화됩니다.

다음 코드를 살펴봅시다.

# .github/workflows/ci.yml
name: CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

# 이 워크플로우의 이름이 Status Check로 표시됩니다
jobs:
  lint:
    name: Lint Check  # 이 이름이 PR에 표시됩니다
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run lint

  test:
    name: Unit Tests  # 이 이름이 PR에 표시됩니다
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test

  build:
    name: Build Check  # 이 이름이 PR에 표시됩니다
    runs-on: ubuntu-latest
    needs: [lint, test]  # lint와 test가 성공해야 실행됩니다
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run build

김개발 씨는 급한 마음에 Merge 버튼을 눌렀지만 아무 반응이 없었습니다. 화면을 다시 보니 Merge 버튼이 회색으로 비활성화되어 있었습니다.

그 아래에는 노란색 원과 함께 "Some checks haven't completed yet"이라는 메시지가 떠 있었습니다. "아, CI가 아직 안 끝났구나." 잠시 후 CI가 완료되었습니다.

테스트가 모두 통과하자 노란색 원이 초록색 체크마크로 바뀌었고, Merge 버튼도 활성화되었습니다. 박시니어 씨가 다가와 말했습니다.

"Status Check 덕분에 실수로 깨진 코드를 병합하는 일이 없어졌죠." 그렇다면 Status Check란 정확히 무엇일까요? 쉽게 비유하자면, Status Check는 마치 출국 심사와 같습니다.

비행기를 타려면 여권 심사, 보안 검색, 탑승권 확인 등 여러 단계를 거쳐야 합니다. 하나라도 통과하지 못하면 비행기에 탈 수 없습니다.

Status Check도 마찬가지로, 린트 검사, 테스트, 빌드 등 여러 단계가 모두 통과해야 코드를 병합할 수 있습니다. Status Check가 없던 시절에는 어땠을까요?

개발자들은 CI 결과를 직접 확인하고 판단해야 했습니다. 급할 때는 "아, CI 아직 돌아가는 중인데 테스트 정도는 괜찮겠지"라며 병합하는 일도 있었습니다.

그 결과 메인 브랜치에 빌드가 안 되는 코드가 올라가기도 했습니다. 다른 팀원들이 풀을 받았다가 "어?

왜 안 돌아가지?"라며 혼란에 빠지곤 했습니다. 바로 이런 문제를 해결하기 위해 Status Check가 등장했습니다.

GitHub Actions 워크플로우가 실행되면 자동으로 해당 PR에 Status Check가 등록됩니다. 각 Job의 name 필드에 지정한 이름이 PR 페이지 하단에 표시되고, 실행 중이면 노란색, 성공하면 초록색, 실패하면 빨간색으로 나타납니다.

모든 Status Check가 초록색이 되어야 "All checks have passed"라는 메시지가 표시됩니다. 위의 워크플로우 코드를 한 줄씩 살펴보겠습니다.

각 Job에 name 필드를 지정하면 PR에서 그 이름으로 표시됩니다. 예를 들어 name: Lint Check라고 하면 PR 페이지에 "Lint Check"라는 이름으로 Status Check가 나타납니다.

**needs: [lint, test]**는 의존성을 정의합니다. build Job은 lint와 test가 모두 성공해야만 실행됩니다.

하나라도 실패하면 build는 아예 시작되지 않습니다. 실제 현업에서는 어떻게 활용할까요?

대규모 프로젝트에서는 보통 5개에서 10개 정도의 Status Check를 설정합니다. 린트, 유닛 테스트, 통합 테스트, 빌드, 보안 스캔, 의존성 취약점 검사 등 다양한 검사를 병렬로 실행합니다.

각 검사의 결과가 개별적으로 표시되므로 무엇이 문제인지 한눈에 파악할 수 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 Status Check를 너무 많이 만드는 것입니다. 20개의 체크가 달리면 어느 것이 중요한지 파악하기 어렵습니다.

또한 일부 검사가 가끔 실패하는 "flaky test"가 있으면 개발자들이 Status Check를 무시하는 나쁜 습관이 생깁니다. 따라서 안정적이고 의미 있는 검사만 Status Check로 등록하는 것이 중요합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 이제 김개발 씨는 PR을 올리면 자동으로 세 개의 Status Check가 뜨는 것을 알고 있습니다.

Lint Check, Unit Tests, Build Check. 세 개가 모두 초록색이 될 때까지 기다렸다가 병합합니다.

가끔 급할 때 조바심이 나기도 하지만, 이 덕분에 메인 브랜치는 항상 건강한 상태를 유지합니다.

실전 팁

💡 - Job 이름은 간결하고 명확하게 지정하세요. "CI"보다는 "Lint Check", "Unit Tests"처럼 구체적인 이름이 좋습니다

  • needs를 활용해 의존성을 설정하면 불필요한 Job 실행을 줄이고 피드백 속도를 높일 수 있습니다

6. Required Checks 보호

어느 날 신입 개발자가 실수로 테스트가 실패한 PR을 병합해버렸습니다. 그날 저녁 운영 서버에서 에러가 터졌습니다.

박시니어 씨는 긴급 회의를 소집했습니다. "앞으로 이런 일이 다시는 일어나지 않도록 조치해야 합니다."

Required Checks는 특정 Status Check가 반드시 통과해야만 PR을 병합할 수 있도록 강제하는 브랜치 보호 규칙입니다. 마치 금고에 두 개의 열쇠가 있어야만 열리는 것처럼, 아무리 급해도 필수 검사를 건너뛸 수 없습니다.

이것은 실수로 인한 장애를 시스템적으로 방지하는 마지막 방어선입니다.

다음 코드를 살펴봅시다.

# 브랜치 보호 규칙 설정 (GitHub 저장소 Settings에서 설정)
# Settings > Branches > Add branch protection rule

# 또는 GitHub API로 설정하는 방법:
# gh api repos/{owner}/{repo}/branches/main/protection \
#   --method PUT \
#   --field required_status_checks='{"strict":true,"contexts":["Lint Check","Unit Tests","Build Check"]}'

# .github/workflows/ci.yml (필수 검사로 등록될 워크플로우)
name: CI Pipeline

on:
  pull_request:
    branches: [main]

jobs:
  lint:
    name: Lint Check  # Required Check로 등록됩니다
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run lint

  test:
    name: Unit Tests  # Required Check로 등록됩니다
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test

  build:
    name: Build Check  # Required Check로 등록됩니다
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build

그날 저녁, 긴급 회의에 모인 팀원들의 표정은 어두웠습니다. 단순한 실수 하나가 수만 명의 사용자에게 영향을 미쳤습니다.

신입 개발자는 고개를 숙이고 있었고, 박시니어 씨는 화이트보드에 무언가를 그리기 시작했습니다. "이건 여러분 개인의 잘못이 아닙니다." 박시니어 씨가 말했습니다.

"시스템이 이런 실수를 허용했다는 게 문제예요. 사람은 실수를 합니다.

그래서 시스템으로 막아야 해요." 그렇다면 Required Checks란 정확히 무엇일까요? 쉽게 비유하자면, Required Checks는 마치 은행 금고의 이중 잠금 장치와 같습니다.

아무리 은행장이라 해도 혼자서는 금고를 열 수 없습니다. 두 명의 담당자가 각자의 열쇠를 동시에 사용해야만 문이 열립니다.

Required Checks도 마찬가지로, 지정된 모든 검사가 통과해야만 병합이 가능합니다. 관리자조차 이 규칙을 우회할 수 없습니다.

Required Checks가 없던 시절에는 어땠을까요? Status Check가 실패해도 Merge 버튼은 누를 수 있었습니다.

물론 경고 메시지가 뜨긴 했지만, 급한 상황에서는 "이번 한 번만..."이라며 무시하기 일쑤였습니다. 그 결과 테스트가 깨진 코드가 메인 브랜치에 들어가고, 그걸 기반으로 다른 개발자들이 작업하면서 문제가 눈덩이처럼 커졌습니다.

바로 이런 문제를 해결하기 위해 Required Checks가 등장했습니다. GitHub의 Branch Protection Rules에서 특정 브랜치(보통 main이나 master)에 보호 규칙을 설정할 수 있습니다.

"Require status checks to pass before merging" 옵션을 켜고, 필수 검사 목록에 "Lint Check", "Unit Tests", "Build Check" 등을 추가합니다. 이렇게 설정하면 이 검사들이 모두 초록색이 아닌 이상 병합 버튼 자체가 비활성화됩니다.

위의 설정 과정을 살펴보겠습니다. GitHub 저장소의 Settings 탭으로 들어갑니다.

왼쪽 메뉴에서 Branches를 선택하고, Add branch protection rule 버튼을 클릭합니다. Branch name pattern에 "main"을 입력합니다.

그리고 "Require status checks to pass before merging"을 체크한 후, 드롭다운에서 필수로 지정할 검사들을 선택합니다. "Require branches to be up to date before merging"도 함께 켜면 최신 main 기준으로 테스트가 통과해야 병합할 수 있습니다.

실제 현업에서는 어떻게 활용할까요? 대부분의 기업에서 main 브랜치에는 Required Checks가 필수로 설정되어 있습니다.

여기에 추가로 "Require pull request reviews before merging"을 켜서 최소 1명 이상의 리뷰 승인을 받아야 병합할 수 있게 합니다. 일부 기업에서는 "Require linear history"를 켜서 merge commit 없이 깔끔한 히스토리를 유지하기도 합니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 겪는 문제 중 하나는 Required Check로 등록된 워크플로우의 이름을 바꿨다가 병합이 안 되는 상황입니다.

예를 들어 "Unit Tests"를 "Test Suite"로 바꾸면, Branch Protection에 등록된 "Unit Tests"는 영원히 pending 상태가 됩니다. 따라서 워크플로우 이름을 변경할 때는 Branch Protection 설정도 함께 업데이트해야 합니다.

다시 그날 회의로 돌아가 봅시다. 박시니어 씨는 화이트보드에 그린 아키텍처를 설명한 후, 바로 노트북을 열어 Required Checks를 설정했습니다.

다음 날부터 테스트가 실패한 PR은 아예 병합 버튼이 회색으로 표시되었습니다. 처음에는 "너무 불편하다"는 불만도 있었지만, 한 달이 지나자 운영 장애가 눈에 띄게 줄어들었고, 모두가 이 규칙의 가치를 인정하게 되었습니다.

실전 팁

💡 - "Require branches to be up to date" 옵션을 켜면 오래된 브랜치가 그대로 병합되는 것을 방지할 수 있습니다

  • 긴급 상황을 위해 특정 사용자나 팀에게 bypass 권한을 부여할 수 있지만, 이 권한은 정말 신중하게 관리해야 합니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#GitHub Actions#CI/CD#자동화테스트#린트검사#테스트커버리지#GitHub Actions,CI/CD,DevOps

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

마이크로서비스 배포 완벽 가이드

Kubernetes를 활용한 마이크로서비스 배포의 핵심 개념부터 실전 운영까지, 초급 개발자도 쉽게 따라할 수 있는 완벽 가이드입니다. 실무에서 바로 적용 가능한 배포 전략과 노하우를 담았습니다.

CodeBuild로 빌드 자동화 완벽 가이드

AWS CodeBuild를 활용한 빌드 자동화의 모든 것. buildspec.yml 작성부터 Docker 빌드, ECR 푸시까지 실무에 바로 적용 가능한 자동화 파이프라인을 구축합니다.

Application Load Balancer 완벽 가이드

AWS의 Application Load Balancer를 처음 배우는 개발자를 위한 실전 가이드입니다. ALB 생성부터 ECS 연동, 헬스 체크, HTTPS 설정까지 실무에 필요한 모든 내용을 다룹니다. 초급 개발자도 쉽게 따라할 수 있도록 단계별로 설명합니다.

고객 상담 AI 시스템 완벽 구축 가이드

AWS Bedrock Agent와 Knowledge Base를 활용하여 실시간 고객 상담 AI 시스템을 구축하는 방법을 단계별로 학습합니다. RAG 기반 지식 검색부터 Guardrails 안전 장치, 프론트엔드 연동까지 실무에 바로 적용 가능한 완전한 시스템을 만들어봅니다.

에러 처리와 폴백 완벽 가이드

AWS API 호출 시 발생하는 에러를 처리하고 폴백 전략을 구현하는 방법을 다룹니다. ThrottlingException부터 서킷 브레이커 패턴까지, 실전에서 바로 활용할 수 있는 안정적인 에러 처리 기법을 배웁니다.