CI 파이프라인에서 셸 테스트 실행
ShellCheck와 Bats를 GitHub Actions에 연결하여 모든 셸 변경 사항이 통과한 검사만 반영되도록 합니다.
CI 파이프라인에서 셸 테스트 실행은(는) CoddyKit의 무료 Linux Command Line & Bash Scripting Mastery 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Linux Command Line & Bash Scripting Mastery 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Linux Command Line & Bash Scripting Mastery 강의에는 총 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/batsgit submodule add https://github.com/bats-core/bats-support test/test_helper/bats-supportgit 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 + helpersCI에서 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로 업그레이드하면 Linux Command Line & Bash Scripting Mastery 강의 전체를 잠금 해제할 수 있습니다. Linux Command Line & Bash Scripting Mastery 강의에는 총 4개의 강의가 포함되어 있습니다.
“CI 파이프라인에서 셸 테스트 실행”에서 뭘 배우나요?
ShellCheck와 Bats를 GitHub Actions에 연결하여 모든 셸 변경 사항이 통과한 검사만 반영되도록 합니다. 브라우저에서 직접 실행하는 실습 코드로 Linux Command Line & Bash Scripting Mastery을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Linux Command Line & Bash Scripting Mastery을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Linux Command Line & Bash Scripting Mastery은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“CI 파이프라인에서 셸 테스트 실행” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Linux Command Line & Bash Scripting Mastery 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Linux Command Line & Bash Scripting Mastery 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- Bats-core로 함수 단위 테스트
- 명령 모킹 및 외부 도구 스텁 처리
- 테스트 픽스처, 임시 환경 및 커버리지
- CI 파이프라인에서 셸 테스트 실행