안전한 코드를 배포하라: 실제로 중요한 GitHub Actions 패턴

발행: (2026년 3월 16일 오전 07:11 GMT+9)
7 분 소요
원문: Dev.to

Source: Dev.to

Ship Safer Code: 실제로 중요한 GitHub Actions 패턴을 위한 표지 이미지

prabhu ponnambalam

대부분의 CI 가이드가 가진 문제점

“GitHub Actions unit tests”를 검색하고 가이드를 찾아 YAML을 복사하면 동작합니다 — 하지만 다음과 같은 상황이 발생합니다:

  • 문서 전용 PR이 20분짜리 빌드를 트리거합니다.
  • CI에서는 테스트가 통과하지만 프로덕션에서는 충돌합니다.
  • 한 번씩만 실패하는 플레이키 테스트가 무작위로 실패해 팀 전체를 차단합니다.

이것은 실제 문제입니다. 해결 방법은 다음과 같습니다.

1. 경로 필터링 — 변경 사항이 없을 때 빌드 건너뛰기

코드가 변경되지 않았을 때 테스트 건너뛰기

- name: Detect changes
  id: changes
  uses: dorny/paths-filter@v3
  with:
    filters: |
      code:
        - '**/*.cpp'
        - '**/*.h'
        - '**/*.hpp'
        - '**/CMakeLists.txt'

- name: Run Tests
  if: steps.changes.outputs.code == 'true'
  run: make run-tests

실제 영향: README.md의 오타 수정만으로도 이전에는 러너 시간을 20분이나 소모했습니다. 이제는 5초 이내에 완료됩니다.

2. Docker 안에서 빌드 및 테스트 실행

GitHub Actions 러너 박스

러너 환경 드리프트는 재현성을 해치는 조용한 살인자입니다. Ubuntu 버전이 바뀌고, 시스템 라이브러리가 업데이트되면 이유를 설명할 수 없는 빨간 CI가 갑자기 나타납니다.

- name: Build Docker image with tests enabled
  run: |
    docker build . -f Dockerfile -t myapp-ci \
      --build-arg WITH_TESTS=true

- name: Run tests inside container
  run: |
    mkdir -p ./test-output
    docker run --rm \
      -v ./test-output:/app/build/Testing/Temporary \
      myapp-ci \
      ctest --test-dir /app/build --output-on-failure

./test-output을 마운트하나요? 테스트가 실패하면 로그가 필요합니다. 마운트하지 않으면 컨테이너가 종료될 때 로그가 사라집니다.

3. ASAN 및 TSAN 실행 — 코드가 생각보다 안전하지 않음

sanitizer

프로덕션에 도달하는 대부분의 C++ 버그는 다음 단계에서 차단됩니다:

  • AddressSanitizer (ASAN) – 버퍼 오버플로, use‑after‑free, 메모리 누수
  • ThreadSanitizer (TSAN) – 데이터 레이스, 데드락
jobs:
  release:
    uses: ./.github/workflows/test.yml
    with:
      preset: release

  asan:
    uses: ./.github/workflows/test.yml
    with:
      preset: asan

  tsan:
    needs: asan          # 순차 실행 — 두 작업 모두 CPU 집약적
    uses: ./.github/workflows/test.yml
    with:
      preset: tsan

⚠️ ASAN과 TSAN을 동시에 실행하지 마세요. 두 도구가 같은 러너 자원을 경쟁하면 실제 버그가 아닌 자원 부족으로 인한 거짓 실패가 발생합니다. needs:를 사용해 순서를 지정하세요.

4. 전체 스위트가 아닌 실패한 테스트만 재시도

실패한 테스트 재시도

플레이키 테스트는 존재합니다. 이를 부정하면 팀에 매주 몇 시간씩 손해가 됩니다. 잘못된 해결책은 테스트에 DISABLED_를 붙이거나 무시하는 것이고, 올바른 해결책은 다음과 같습니다:

- name: Run Tests
  run: ctest --test-dir build --output-on-failure --parallel $(nproc)

- name: Retry Failed Tests
  if: failure()
  run: |
    ctest --test-dir build \
      --rerun-failed \
      --output-on-failure \
      --repeat until-pass:3

--rerun-failed는 CTest에 내장된 옵션으로, 마지막 실행 결과를 읽어 실패한 테스트만 다시 실행합니다. 20분짜리 전체 스위트 재시도가 90초짜리 타깃 재시도로 바뀝니다.

5. 디버그용 상세 출력 — 일반 실행을 오염시키지 않기

verbose toggle

-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftonlb35bmo190yxk0q4w.png)

- name: Run Tests (normal)
  run: ctest --test-dir build --output-on-failure

- name: Run Tests (verbose)
  if: github.event.inputs.verbose == 'true'
  run: ctest --test-dir build --output-on-failure --verbose

깊은 진단이 필요할 때는 verbose 입력을 토글하고, 그렇지 않을 경우 일상적인 실행을 위해 출력이 깔끔하게 유지되도록 하세요.

자세한 테스트 출력

매 실행마다 자세한 테스트 출력은 수천 줄의 잡음과 같습니다. 하지만 새벽 2시에 디버깅을 할 때는 모든 바이트가 필요합니다.

- name: Run Tests
  if: runner.debug != '1'
  run: ctest --test-dir build --output-on-failure

- name: Run Tests (verbose)
  if: runner.debug == '1'
  run: ctest --test-dir build --verbose

디버그 모드를 활성화하려면: GitHub Actions → Re‑run jobs → Enable debug logging. YAML을 변경할 필요도 없고, 새로운 커밋도 필요 없습니다. 스위치를 전환하기만 하면 됩니다.

6. 하나의 설정 블록으로 중복 실행 취소

동시성 취소

수정을 푸시하고, 뭔가를 놓쳤다는 걸 깨달아 다시 푸시합니다. 이제 같은 브랜치에 두 개의 CI 실행이 생깁니다. 첫 번째 실행은 낭비입니다.

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

모든 워크플로우의 상단에 두 줄만 추가하면 됩니다. 새로운 푸시가 이전 실행을 즉시 종료시킵니다. 비용도 없고, 고민도 없으며, 즉각적인 승리입니다.

요약

CI는 코드다. 같은 원칙을 적용하라: 낭비 없이, 유용한 실패, 유지보수가 쉬운.

0 조회
Back to Blog

관련 글

더 보기 »

Python에서 PDF 텍스트 추출 방법 (2026)

PDF에서 텍스트 추출 PDF에서 텍스트를 추출하는 것은 여전히 데이터 엔지니어링, AI 파이프라인 및 자동화 워크플로우에서 가장 일반적인 작업 중 하나입니다. Whether...

2025년에 Python 자동화로 돈 버는 방법

개발자로서 자동화(automation)와 그것이 프로세스(processes)를 간소화하고 효율성(efficiency)을 높이며 비용(costs)을 절감할 잠재력에 익숙할 것입니다. 하지만 …