CIパイプラインでのシェルテスト実行
ShellCheckとBatsをGitHub Actionsに組み込み、すべてのシェル変更をチェック成功時だけ通過させます。
「CIパイプラインでのシェルテスト実行」はCoddyKit上の無料Linux Command Line & Bash Scripting Masteryレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLinux Command Line & Bash Scripting Mastery学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Linux Command Line & Bash Scripting Masteryコースには全4レッスンが含まれています。
シェルスクリプトでCIが重要な理由
シェルスクリプトもコードです。すべてのコードと同じように、自動化された品質ゲートが必要です。CIがなければ、デプロイスクリプトのタイプミスが気付かれないまま本番環境に到達し、午前3時に障害を引き起こす可能性があります。
堅牢なBashプロジェクト向けCIパイプラインでは、すべてのプルリクエストに対して次の2つを必須にします。
- 静的解析(
ShellCheck) — スクリプトが実行される前に、構文エラー、安全でないパターン、POSIX互換性の問題を検出します。 - ユニットテスト/統合テスト(
Bats(Bash Automated Testing System)) — 関数を実行し、正しく動作することをアサートします。
この2つを組み合わせることで、安全網が構築され、安心してリファクタリングでき、オンボーディングも速くなります。このレッスンでは、オープンソースプロジェクトや小規模チームで最も一般的な無料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のランナーにあらかじめインストールされているため、ほとんどの場合、インストール手順は必要ありません。ただし、プレインストールされたバージョンは最新リリースより遅れている可能性があります。再現可能なビルドにするには、特定のバージョンを固定してください。
インストール方法は2つあります。
- プレインストール済みのバイナリを使う — 最も簡単で、ほとんどのプロジェクトでは十分です。
- 公式GitHubリリースのtarballからバージョンを固定してインストールする — ローカルとCIで同じリンターのバージョンを確実に使えます。
以下のステップでは、環境変数として保存した固定のバージョン文字列を使う、バージョン固定方式を示します。アップグレードは1行の変更で行えます。
# .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を実行する
インストール後は、リポジトリ内のすべてのシェルスクリプトを見つけてlintするステップが必要です。findでファイルを探し、shellcheckにパイプで渡します。
知っておくべき重要なフラグ:
-e SC2034— 特定のルールを除外します(使用は控えめにし、コメントを添えてください)。--severity=warning— 警告以上の場合だけ失敗させます(スタイルに関する提案は無視します)。-x—sourceディレクティブをたどり、sourceされたファイルもlintします。
shellcheckが問題を1つでも見つけると、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)は、TAPに準拠したBash用テストフレームワークです。各テストファイルは@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オプションを使ってサブモジュールを復元します。以下のステップに、完全なcheckout設定を示します。
# .github/workflows/ci.yml — checkout with submodules
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive # restores bats-core + helpersCIでBatsテストを実行する
サブモジュールまたはパッケージインストールによってBatsを利用できるようにしたら、テストの実行は1つのコマンドで済みます。ディレクトリを指定すると、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
ここまでの内容を、プロダクションで使える1つのワークフローファイルにまとめます。ここでは次のベストプラクティスを適用しています。
- 2つの独立したジョブ(
lintとtest)を並列実行し、フィードバックを速めています。 testジョブでneeds: lintを宣言し、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のヘルパーやその他のツールをパッケージマネージャー経由でインストールする場合、キャッシュによって2回目以降の実行を大幅に高速化できます。GitHub Actionsでは、このためにactions/cacheアクションを利用できます。
効果的なキャッシュのポイント:
- OS、ツール名、ロックファイルのハッシュを含むキャッシュキーを使います。これにより、依存関係が変わるとキャッシュが自動的に無効になります。
restore-keysのフォールバックを使うと、キャッシュミス時に最初からやり直す代わりに、古いキャッシュを利用できます。- Gitサブモジュールでは、checkoutが高速なため、キャッシュが必要になることはほとんどありません。キャッシュは
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の実行が失敗したときは、別のコミットをプッシュする前にローカルで失敗を再現するのが、最も速く修正する方法です。手法は2つあります。
- 失敗したステップの正確なコマンドを実行する — CIは通常のシェルを実行するため、コマンドをコピー&ペーストして再現できます。
actを使う — GitHub ActionsのワークフローをDocker内でローカル実行するツールで、ホステッドランナーの環境に最も近い状態を再現できます。
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によるShell CI
このレッスンでは、GitHub Actionsを使ってBashプロジェクト向けの完全なCIパイプラインを構築しました。学んだ内容は次のとおりです。
- GitHub Actionsの基本 — ワークフローのYAMLは
.github/workflows/に置き、pushやpull_requestをトリガーとして、ubuntu-latestランナー上でジョブを実行します。 - ShellCheck — Ubuntuランナーにプレインストールされています。
findでスクリプトを見つけ、--severity=warning -xを実用的なlintゲートとして使います。 - サブモジュール経由のBats — bats-coreとヘルパーをGitサブモジュールとして固定し、checkoutアクションで
submodules: recursiveを指定してCIに復元します。 - ジョブの順序 —
needs:を使い、lintが成功した後にだけテストを実行します。これにより、素早いフィードバックを維持し、計算リソースの浪費を防げます。 - ブランチ保護 — GitHubの設定でステータスチェックを必須にし、CIが成功しない限りPRを取り込めないようにします。
- ローカルでの再現 — CIのコマンドをそのままターミナルにコピーするか、
actを使って追加のコミットなしで失敗をデバッグします。
このパイプラインを導入すれば、すべてのシェルの変更がメインブランチに反映される前に自動検証されます。
よくある質問
「CIパイプラインでのシェルテスト実行」レッスンは無料ですか?
はい。「CIパイプラインでのシェルテスト実行」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Linux Command Line & Bash Scripting Masteryコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Linux Command Line & Bash Scripting Masteryコースには全4レッスンが含まれています。
「CIパイプラインでのシェルテスト実行」で何を学びますか?
ShellCheckとBatsをGitHub Actionsに組み込み、すべてのシェル変更をチェック成功時だけ通過させます。 ブラウザで直接実行するハンズオンコードでLinux Command Line & Bash Scripting Masteryを演習し、24時間対応の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パイプラインでのシェルテスト実行