0Pricing
Linux Command Line & Bash Scripting Mastery · 课时

在 CI 管道中运行 Shell 测试

将 ShellCheck 和 Bats 接入 GitHub Actions,要求每次 Shell 变更都通过检查。

在 CI 管道中运行 Shell 测试 是 CoddyKit 上的免费 Linux Command Line & Bash Scripting Mastery 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Linux Command Line & Bash Scripting Mastery 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。

Shell 脚本为何需要持续集成

Shell 脚本也是代码——和所有代码一样,它们需要自动化的质量门禁。没有持续集成时,部署脚本中的一个拼写错误可能会悄无声息地进入生产环境,并在凌晨 3 点造成服务中断。

可靠的 Bash 项目持续集成流程会对每个拉取请求强制执行两项检查:

  • 静态分析:通过 ShellCheck 在脚本运行前发现语法错误、不安全模式以及 POSIX 可移植性问题。
  • 单元测试/集成测试:通过 Bats(Bash 自动化测试系统)执行您的函数并断言其行为正确。

两者结合后会形成一道安全网,让重构更有底气,也能加快新成员上手。本课将通过GitHub Actions把这两个工具接入持续集成;GitHub Actions 是开源项目和小型团队项目最常用的免费持续集成平台。

Shell 项目的 GitHub Actions 入门

GitHub Actions 是内置于 GitHub 的事件驱动型持续集成/持续交付系统。工作流是存放在 .github/workflows/ 下的 YAML 文件。它会在事件(推送、拉取请求等)发生时触发,并在托管运行器上运行作业。

您需要了解的关键概念:

  • on:——触发器(例如 push、pull_request)
  • jobs:——并行执行的工作单元,每个作业都在全新的 VM 上运行
  • steps:——作业内按顺序执行的 Shell 命令或可复用的操作
  • 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 发布版压缩包安装,确保本地和持续集成环境使用相同版本的代码检查器。

下面的步骤展示了固定版本的方法:将固定的版本字符串存储为环境变量,从而只需修改一行即可升级。

# .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

安装完成后,您需要添加一个步骤来发现代码库中的所有 Shell 脚本并对其进行检查。使用 find 定位文件,然后将它们通过管道传给 shellcheck。

需要了解的重要选项:

  • -e SC2034——排除特定规则(请谨慎使用,并添加注释)。
  • --severity=warning——只在出现警告及更严重问题时失败(忽略样式建议)。
  • -x——跟踪 source 指令,同时检查被引入的文件。

如果 shellcheck 发现任何问题,它会以非零状态退出,从而自动使持续集成步骤失败,无需额外逻辑。

# .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 自动化测试系统)是一个符合 TAP 标准的 Bash 测试框架。每个测试文件都是一个 .bats 文件,其中包含 @test 代码块。

测试主体以 0 状态退出时测试通过,以非零状态退出时测试失败。Bats 提供了辅助变量和函数:

  • $status——上一个 run 命令的退出代码。
  • $output——上一个 run 命令的标准输出与标准错误的合并结果。
  • $lines——输出行数组。
  • run <cmd>——执行命令,即使命令以非零状态退出,也不会使测试失败。

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

在持续集成环境中,使用 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

在持续集成中运行 Bats 测试

Bats 可用后(通过子模块或软件包安装),运行测试只需一条命令。将其指向某个目录,并使用 --recursive 选项,Bats 就会递归发现其中所有的 .bats 文件。

--formatter tap 选项会输出 TAP(测试通用协议)格式,许多持续集成系统都能解析这种格式来生成测试报告。默认的美观格式化器更适合人类阅读原始日志。

使用 --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

分支保护:强制检查通过

不阻止合并的持续集成工作流充其量只能起到建议作用。GitHub 的分支保护规则可以将检查转变为硬性门禁。

配置方法:进入 Settings → Branches → Add rule,为 main 添加规则,然后启用:

  • Require status checks to pass before merging——按名称选择 ShellCheck 和 Bats Tests。
  • Require branches to be up to date before merging——防止基于过时分支通过检查的拉取请求将有问题的代码合入。
  • Do not allow bypassing the above settings——即使对代码库管理员也应用这些规则。

启用这些规则后,合并的唯一途径就是提交一个所有持续集成作业都通过的拉取请求,这正是您所需要的安全网。

在本地调试失败的持续集成步骤

持续集成运行失败时,最快的修复流程是在推送另一条提交前先在本地重现故障。可以采用两种技巧:

  • 运行完全相同的命令——在终端中运行失败步骤里的命令;持续集成使用普通 Shell,因此这些命令可以复制粘贴并重现。
  • 使用 act——这是一个在 Docker 中本地运行 GitHub Actions 工作流的工具,能够尽可能接近托管运行器环境。

仅在持续集成中失败的常见原因,是您的 Mac 与持续集成环境之间的工具版本不一致(例如 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/

知识检查:持续集成流程概念

测试您将 ShellCheck 和 Bats 接入 GitHub Actions 的理解。

回顾:使用 ShellCheck 和 Bats 的 Shell 持续集成

本课中,您使用 GitHub Actions 为 Bash 项目构建了完整的持续集成流程。您学习了以下内容:

  • GitHub Actions 基础——工作流 YAML 存放在 .github/workflows/ 中,会在 push 和 pull_request 事件发生时触发,并在 ubuntu-latest 运行器上运行作业。
  • ShellCheck——Ubuntu 运行器已预装;使用 find 发现脚本,并使用 --severity=warning -x 实现实用的代码检查门禁。
  • 通过子模块使用 Bats——将 bats-core 和辅助工具固定为 Git 子模块,并在检出操作中使用 submodules: recursive 在持续集成环境中恢复它们。
  • 作业排序——使用 needs: 确保只有代码检查通过后才运行测试,从而快速获得反馈并避免浪费计算资源。
  • 分支保护——在 GitHub 设置中强制执行状态检查,确保没有拉取请求能够在持续集成未全部通过的情况下合入。
  • 本地重现——将持续集成命令直接复制到终端,或使用 act 调试故障,无需额外提交。

有了这套流程,每次 Shell 代码变更都会在影响主分支之前自动完成验证。

常见问题解答

「在 CI 管道中运行 Shell 测试」课时是免费的吗?

是的 — 「在 CI 管道中运行 Shell 测试」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Linux Command Line & Bash Scripting Mastery 课程的其余内容,请升级到 CoddyKit PRO。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。

「在 CI 管道中运行 Shell 测试」这节课中我会学到什么?

将 ShellCheck 和 Bats 接入 GitHub Actions,要求每次 Shell 变更都通过检查。 你通过在浏览器中直接运行的动手代码来练习 Linux Command Line & Bash Scripting Mastery,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Linux Command Line & Bash Scripting Mastery 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Linux Command Line & Bash Scripting Mastery 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「在 CI 管道中运行 Shell 测试」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Linux Command Line & Bash Scripting Mastery 课中编写并运行代码吗?

能。每节 Linux Command Line & Bash Scripting Mastery 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 Bats-core 对函数进行单元测试
  2. 模拟命令与替代外部工具
  3. 测试固件、临时环境与覆盖率
  4. 在 CI 管道中运行 Shell 测试
← 返回 Linux Command Line & Bash Scripting Mastery