在 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/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
在持续集成环境中,使用 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 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 Bats-core 对函数进行单元测试
- 模拟命令与替代外部工具
- 测试固件、临时环境与覆盖率
- 在 CI 管道中运行 Shell 测试