0Pricing
DevOps Bootcamp · 강의

CI 파이프라인에서 셸 테스트 실행

ShellCheck와 Bats를 GitHub Actions에 연결하여 모든 셸 변경 사항이 통과한 검사만 반영되도록 합니다.

CI 파이프라인에서 셸 테스트 실행은(는) CoddyKit의 무료 DevOps Bootcamp 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 DevOps Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

셸 스크립트에서 CI가 중요한 이유

셸 스크립트도 코드이며 모든 코드와 마찬가지로 자동화된 품질 검사를 적용해야 합니다. CI가 없으면 배포 스크립트의 오타가 아무도 모르게 운영 환경에 도달하여 새벽 3시에 장애를 일으킬 수 있습니다.

견고한 Bash 프로젝트용 CI 파이프라인은 모든 풀 리퀘스트에서 다음 두 가지를 시행합니다.

  • 정적 분석 — ShellCheck를 사용하여 스크립트가 실행되기 전에 구문 오류, 안전하지 않은 패턴, POSIX 이식성 문제를 찾아냅니다.
  • 단위/통합 테스트 — Bats(Bash Automated Testing System)를 사용하여 함수를 실행하고 올바르게 동작하는지 검증합니다.

두 도구를 함께 사용하면 안전망이 형성되어 안심하고 리팩터링할 수 있고 새 팀원도 더 빠르게 적응할 수 있습니다. 이번 레슨에서는 오픈 소스 및 소규모 팀 프로젝트에서 가장 널리 사용되는 무료 CI 플랫폼인 GitHub Actions에 두 도구를 연결합니다.

셸 프로젝트를 위한 GitHub Actions 입문

GitHub Actions는 GitHub에 내장된 이벤트 기반 CI/CD입니다. 워크플로는 .github/workflows/ 아래에 저장되는 YAML 파일입니다. 워크플로는 이벤트(push, pull_request 등)에 의해 트리거되고 호스팅 실행기에서 작업을 실행합니다.

다음 핵심 개념을 알아 두십시오.

  • on: — 트리거입니다(예: push, pull_request).
  • jobs: — 각각 새로운 VM에서 실행되는 병렬 작업 단위입니다.
  • steps: — 작업 안에서 순서대로 실행되는 셸 명령 또는 재사용 가능한 액션입니다.
  • runs-on: — 실행기 이미지입니다(여기서는 ubuntu-latest를 사용합니다).

워크플로 파일은 저장소에 커밋해야 합니다. GitHub가 파일을 자동으로 감지하므로 별도의 외부 설정은 필요하지 않습니다.

# Minimal skeleton — .github/workflows/ci.yml
name: CI

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

jobs:
  shell-checks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: echo "Steps go here"

워크플로에 ShellCheck 설치하기

ShellCheck는 ubuntu-latest 실행기에 사전 설치되어 있으므로 대부분의 경우 설치 단계가 전혀 필요하지 않습니다. 하지만 사전 설치된 버전이 최신 릴리스보다 오래되었을 수 있습니다. 재현 가능한 빌드를 위해서는 특정 버전을 고정하십시오.

두 가지 설치 전략이 있습니다.

  • 사전 설치된 바이너리 사용 — 가장 간단하며 대부분의 프로젝트에 충분합니다.
  • 공식 GitHub 릴리스 타르볼을 사용하여 버전을 고정해 설치 — 로컬과 CI에서 동일한 린터 버전을 사용하도록 보장합니다.

아래 단계에서는 환경 변수로 저장한 고정 버전 문자열을 사용하는 방식을 보여 줍니다. 따라서 업그레이드는 한 줄만 변경하면 됩니다.

# .github/workflows/ci.yml  — ShellCheck install step
- name: Install ShellCheck
  env:
    SC_VERSION: v0.10.0
  run: |
    curl -sSfL \
      "https://github.com/koalaman/shellcheck/releases/download/${SC_VERSION}/shellcheck-${SC_VERSION}.linux.x86_64.tar.xz" \
      | tar -xJf - --strip-components=1 -C /usr/local/bin shellcheck-${SC_VERSION}/shellcheck
    shellcheck --version

모든 스크립트에서 ShellCheck 실행하기

설치가 끝나면 저장소의 모든 셸 스크립트를 찾아 검사하는 단계가 필요합니다. find로 파일을 찾은 다음 파이프로 shellcheck에 전달하십시오.

알아 두어야 할 주요 옵션:

  • -e SC2034 — 특정 규칙을 제외합니다(신중하게 사용하고 주석을 함께 작성하십시오).
  • --severity=warning — 스타일 제안은 무시하고 경고 이상의 문제만 발견해도 실패 처리합니다.
  • -x — source 지시문을 따라가 소스된 파일도 검사합니다.

shellcheck가 문제를 하나라도 발견하면 0이 아닌 종료 상태를 반환하므로 CI 단계가 자동으로 실패합니다. 추가 로직은 필요하지 않습니다.

# .github/workflows/ci.yml  — ShellCheck lint step
- name: Lint shell scripts
  run: |
    # Find all .sh files and files with a bash/sh shebang
    mapfile -t scripts < <(
      find . -type f -name '*.sh' -not -path './.git/*'
    )
    if [[ ${#scripts[@]} -eq 0 ]]; then
      echo 'No shell scripts found — skipping.'
      exit 0
    fi
    echo "Linting ${#scripts[@]} file(s)..."
    shellcheck --severity=warning -x "${scripts[@]}"

Bats란 무엇이며 어떻게 작동하나요

Bats(Bash Automated Testing System)는 Bash를 위한 TAP 호환 테스트 프레임워크입니다. 각 테스트 파일은 @test 블록을 포함하는 .bats 파일입니다.

테스트 본문이 0으로 종료되면 테스트가 통과하고, 0이 아닌 상태로 종료되면 실패합니다. Bats는 다음과 같은 도우미 변수와 함수를 제공합니다.

  • $status — 마지막 run 명령의 종료 코드입니다.
  • $output — 마지막 run 명령의 표준 출력과 표준 오류를 합친 결과입니다.
  • $lines — 출력 줄의 배열입니다.
  • run <cmd> — 0이 아닌 종료 상태가 발생해도 테스트를 실패시키지 않고 명령을 실행합니다.

run 도우미는 필수적입니다. 이 도우미가 없으면 실패한 명령이 테스트를 중단하므로 $status를 확인할 수 없습니다.

#!/usr/bin/env bats
# tests/greet.bats

setup() {
  # Runs before every @test block
  source "${BATS_TEST_DIRNAME}/../lib/greet.sh"
}

@test "greet outputs hello with the given name" {
  run greet "Alice"
  [ "$status" -eq 0 ]
  [ "$output" = "Hello, Alice!" ]
}

@test "greet fails when no argument is provided" {
  run greet
  [ "$status" -eq 1 ]
  [[ "$output" == *"Usage"* ]]
}

Git 하위 모듈로 Bats-Core 설치하기

프로젝트에 Bats를 추가하는 표준적인 방법은 Git 하위 모듈로 추가하는 것입니다. 이렇게 하면 특정 커밋을 고정하고 실행기 버전과 로컬 개발 환경의 버전을 동일하게 유지하며 패키지 관리자에 의존하지 않을 수 있습니다.

다음 명령을 로컬에서 한 번 실행한 후 결과를 커밋하십시오.

  • git submodule add https://github.com/bats-core/bats-core test/bats
  • git submodule add https://github.com/bats-core/bats-support test/test_helper/bats-support
  • git submodule add https://github.com/bats-core/bats-assert test/test_helper/bats-assert

CI에서는 actions/checkout@v4와 submodules: recursive 옵션을 사용하여 하위 모듈을 복원합니다. 아래 단계에서 전체 체크아웃 구성을 보여 줍니다.

# .github/workflows/ci.yml  — checkout with submodules
- name: Checkout repository
  uses: actions/checkout@v4
  with:
    submodules: recursive   # restores bats-core + helpers

CI에서 Bats 테스트 실행하기

하위 모듈 또는 패키지 설치를 통해 Bats를 사용할 수 있게 되면 테스트 실행은 하나의 명령으로 끝납니다. 디렉터리를 지정하면 Bats가 --recursive 옵션을 사용하여 모든 .bats 파일을 재귀적으로 찾습니다.

--formatter tap 옵션은 TAP(Test Anything Protocol) 형식으로 출력합니다. 많은 CI 시스템이 테스트 보고서를 위해 이 형식을 분석합니다. 기본값인 pretty 형식은 원시 로그를 사람이 읽기에 더 적합합니다.

--timing을 사용하여 느린 테스트를 조기에 발견하십시오. 5초를 넘겨 실행되는 테스트는 대개 원치 않는 네트워크 호출이나 누락된 모의 객체를 의미합니다.

# .github/workflows/ci.yml  — Bats test step
- name: Run Bats tests
  run: |
    # If installed as a submodule:
    ./test/bats/bin/bats \
      --recursive \
      --timing \
      tests/

    # If installed via apt or brew (alternative):
    # bats --recursive --timing tests/

완전한 워크플로: ShellCheck + Bats

이제 모든 요소를 하나의 운영 환경용 워크플로 파일로 결합해 보겠습니다. 다음 모범 사례를 적용했습니다.

  • 두 개의 분리된 작업(lint와 test)이 병렬로 실행되어 더 빠르게 피드백을 제공합니다.
  • test 작업에 needs: lint를 선언하여 린트가 통과한 후에만 테스트가 실행되도록 합니다. 이렇게 하면 명백히 문제가 있는 코드에 실행기 시간을 낭비하지 않습니다.
  • 고정된 액션 버전(@v4)을 사용하여 업스트림 업데이트로 인한 예기치 않은 문제를 방지합니다.
  • permissions: 블록으로 워크플로 토큰의 권한을 필요한 최소 수준으로 제한합니다.
# .github/workflows/ci.yml
name: Shell CI

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

jobs:
  lint:
    name: ShellCheck
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run ShellCheck
        run: |
          mapfile -t scripts < <(find . -name '*.sh' -not -path './.git/*')
          [[ ${#scripts[@]} -gt 0 ]] && shellcheck --severity=warning -x "${scripts[@]}"

  test:
    name: Bats Tests
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - name: Run tests
        run: ./test/bats/bin/bats --recursive --timing tests/

더 빠른 실행을 위한 의존성 캐싱

워크플로 내부에서 패키지 관리자를 통해 Bats 도우미나 다른 도구를 설치하는 경우 캐싱을 사용하면 이후 실행 속도가 크게 빨라집니다. GitHub Actions는 이를 위해 actions/cache 액션을 제공합니다.

효과적인 캐싱을 위한 핵심 사항:

  • 운영 체제, 도구 이름, 잠금 파일 해시를 포함하는 캐시 키를 사용하십시오. 그러면 의존성이 변경될 때 캐시가 자동으로 무효화됩니다.
  • restore-keys 대체 설정을 사용하면 캐시를 찾지 못했을 때 처음부터 다시 시작하는 대신 오래된 캐시를 사용할 수 있습니다.
  • Git 하위 모듈은 체크아웃이 빠르므로 캐싱이 거의 필요하지 않습니다. 캐시는 npm, pip 또는 컴파일된 도구를 설치할 때 가장 유용합니다.
# .github/workflows/ci.yml  — cache step example
- name: Cache Bats npm helpers
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-bats-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-bats-

- name: Install helpers
  run: npm ci  # uses cache when available

브랜치 보호: 성공한 검사 강제하기

병합을 차단하지 않는 CI 워크플로는 기껏해야 권고 사항에 불과합니다. GitHub 브랜치 보호 규칙을 사용하면 검사를 강제 조건으로 만들 수 있습니다.

구성하려면 main에 대해 Settings → Branches → Add rule로 이동한 다음 다음 항목을 활성화하십시오.

  • Require status checks to pass before merging — 이름으로 ShellCheck과 Bats Tests를 선택합니다.
  • Require branches to be up to date before merging — 오래된 기준 브랜치에서 검사를 통과한 PR이 문제가 있는 코드를 병합하지 못하게 합니다.
  • Do not allow bypassing the above settings — 저장소 관리자에게도 규칙을 적용합니다.

이 규칙을 적용하면 병합할 수 있는 유일한 방법은 모든 CI 작업이 성공한 PR을 사용하는 것입니다. 바로 원하는 안전망입니다.

실패한 CI 단계를 로컬에서 디버깅하기

CI 실행이 실패했을 때 가장 빠르게 문제를 해결하는 방법은 다른 커밋을 푸시하기 전에 로컬에서 실패를 재현하는 것입니다. 두 가지 기법을 사용할 수 있습니다.

  • 정확히 같은 명령 실행 — 실패한 단계의 명령을 터미널에서 실행하십시오. CI는 일반 셸을 실행하므로 명령을 복사해 붙여 넣어 재현할 수 있습니다.
  • act 사용 — Docker 내부에서 GitHub Actions 워크플로를 로컬로 실행하는 도구입니다. 호스팅 실행기 환경과 최대한 비슷한 환경을 제공합니다.

CI에서만 실패하는 흔한 원인은 Mac의 도구 버전과 CI의 도구 버전이 다른 경우입니다(예: macOS의 BSD find와 Ubuntu의 GNU find). 항상 --posix 옵션으로 테스트하거나 act를 사용하여 Ubuntu 이미지를 로컬에서 실행하십시오.

#!/usr/bin/env bash
# run_ci_locally.sh  — mimic the CI lint step on your machine
set -euo pipefail

echo '=== ShellCheck ==='
mapfile -t scripts < <(find . -name '*.sh' -not -path './.git/*')
if [[ ${#scripts[@]} -eq 0 ]]; then
  echo 'No .sh files found.'
else
  shellcheck --severity=warning -x "${scripts[@]}"
  echo "Linted ${#scripts[@]} file(s) — OK"
fi

echo '=== Bats ==='
./test/bats/bin/bats --recursive --timing tests/

지식 확인: CI 파이프라인 개념

ShellCheck와 Bats를 GitHub Actions에 연결하는 방법에 대한 이해도를 확인해 보십시오.

복습: ShellCheck와 Bats를 사용한 셸 CI

이번 레슨에서는 GitHub Actions를 사용하여 Bash 프로젝트를 위한 완전한 CI 파이프라인을 구축했습니다. 다음 내용을 다뤘습니다.

  • GitHub Actions 기초 — 워크플로 YAML은 .github/workflows/에 있으며, push와 pull_request에서 트리거되고 ubuntu-latest 실행기에서 작업을 실행합니다.
  • ShellCheck — Ubuntu 실행기에 사전 설치되어 있습니다. find로 스크립트를 찾고 --severity=warning -x를 사용하여 실용적인 린트 검사를 구성합니다.
  • 하위 모듈을 통한 Bats — bats-core와 도우미를 Git 하위 모듈로 고정하고, 체크아웃 액션에서 submodules: recursive를 사용하여 CI에서 복원합니다.
  • 작업 순서 — needs:를 사용하여 린트가 통과한 후에만 테스트를 실행함으로써 빠르게 피드백을 받고 컴퓨팅 자원 낭비를 방지합니다.
  • 브랜치 보호 — GitHub 설정에서 상태 검사를 강제하여 성공한 CI 없이는 어떤 PR도 병합되지 않도록 합니다.
  • 로컬 재현 — CI 명령을 터미널에 직접 복사하거나 act를 사용하여 추가 커밋 없이 실패를 디버깅합니다.

이 파이프라인을 구성하면 셸 변경 사항이 주 브랜치에 반영되기 전에 모두 자동으로 검증됩니다.

자주 묻는 질문

“CI 파이프라인에서 셸 테스트 실행” 강의는 무료인가요?

네 — “CI 파이프라인에서 셸 테스트 실행” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 DevOps Bootcamp 강의 전체를 잠금 해제할 수 있습니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

“CI 파이프라인에서 셸 테스트 실행”에서 뭘 배우나요?

ShellCheck와 Bats를 GitHub Actions에 연결하여 모든 셸 변경 사항이 통과한 검사만 반영되도록 합니다. 브라우저에서 직접 실행하는 실습 코드로 DevOps Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

DevOps Bootcamp을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 DevOps Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“CI 파이프라인에서 셸 테스트 실행” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 DevOps Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 DevOps Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Bats-core로 함수 단위 테스트
  2. 명령 모킹 및 외부 도구 스텁 처리
  3. 테스트 픽스처, 임시 환경 및 커버리지
  4. CI 파이프라인에서 셸 테스트 실행
← DevOps Bootcamp(으)로 돌아가기