ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

AAS 实战:基于 bats-testing-patterns 技能构建生产级 Shell 脚本测试体系

AAS 实战:基于 bats-testing-patterns 技能构建生产级 Shell 脚本测试体系 AAS 实战基于 bats-testing-patterns 技能构建生产级 Shell 脚本测试体系【免费下载链接】agentic-awesome-skillsAAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,445 agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.项目地址: https://gitcode.com/gh_mirrors/an/agentic-awesome-skillsBatsBash Automated Testing System是面向 Shell 脚本的 TAP 兼容测试框架。本指南以 AASagentic-awesome-skills仓库中 bats-testing-patterns 技能实现手册 为骨架系统讲解安装、测试结构、断言、setup/teardown、Mock、Fixture、CI/CD 集成等完整测试模式。读完本文你将掌握一套可直接复制的生产级 Shell 测试方案并能基于本仓库的技能定义文件SKILL.md在自己的项目中落地 TDD 工作流。一、技能定位什么时候该用 Bats在 AAS 仓库中bats-testing-patterns技能以 Claude 技能SKILL.md与根目录镜像skills/bats-testing-patterns/SKILL.md两份形式存在front matter 记录了元数据risk: critical、source: community、date_added: 2026-02-27。技能描述明确了适用面——为 Shell 脚本编写单元测试、实现脚本 TDD、在 CI/CD 中搭建自动化测试、覆盖边界与错误条件、跨 Shell 环境验证行为。技能的使用边界划分得很清楚适用场景为 Shell 脚本写单元测试实现脚本驱动的 TDD在 CI/CD 管道中搭建自动化测试测试边界与错误条件跨 Shell 环境验证行为。不适用场景项目不使用 Shell 脚本需要超出 Shell 行为范围的集成测试目标仅限 lint 或格式化。技能执行指令给出五步工作流确认 Shell 方言与支持环境 → 搭建带 helpers 和 fixtures 的测试结构 → 针对退出码、输出和副作用写测试 → 添加 setup/teardown 并在 CI 中运行 → 需要详细示例时打开resources/implementation-playbook.md。本文主体正是对该实现手册的完整展开。二、Bats 基础TAP 框架与安装方式2.1 什么是 BatsBatsBash Automated Testing System是符合 TAPTest Anything Protocol测试任何事物协议的 Shell 脚本测试框架核心能力包括简单、自然的测试语法test块与 CI 系统兼容的 TAP 输出格式Fixtures 与 setup/teardown 支持断言辅助函数并行测试执行TAP 协议的价值在于标准化只要测试输出遵循 TAP 格式Jenkins、GitHub Actions、GitLab CI 等系统都能直接解析通过/失败状态无需为每种框架定制适配器。2.2 安装方式Bats 支持三种主流安装途径按平台选择# macOS with Homebrew brew install bats-core # Ubuntu/Debian源码编译安装 git clone https://github.com/bats-core/bats-core.git cd bats-core ./install.sh /usr/local # From npm (Node.js) npm install --global bats # Verify installation bats --versionHomebrew 途径适合 macOS 本地开发install.sh接受安装前缀参数如/usr/local适合 Linux 服务器或 Docker 镜像构建阶段npm 全局安装bats包最为轻量常用于 CI 环境——安装后务必用bats --version验证。2.3 推荐目录结构实现手册给出标准分层结构将被测代码与测试代码严格隔离project/ ├── bin/ │ ├── script.sh │ └── helper.sh ├── tests/ │ ├── test_script.bats │ ├── test_helper.sh │ ├── fixtures/ │ │ ├── input.txt │ │ └── expected_output.txt │ └── helpers/ │ └── mocks.bash └── README.mdbin/被测的 Shell 脚本本体tests/*.bats测试用例文件tests/test_helper.sh共享的辅助函数断言、环境准备tests/fixtures/静态输入/期望输出数据tests/helpers/mocks.bashMock 与 Stub 实现。这种结构与 AAS 仓库自身的脚本组织方式一致——仓库根目录的 scripts/ 下集中放置真实 Shell 脚本如 validate-links.sh、activate-skills.sh、validate-glossary.sh便于为它们统一编写测试。三、基本测试结构test 块与生命周期钩子一个最简但完整的.bats文件如下#!/usr/bin/env bats # Load test helper if present load test_helper # Setup runs before each test setup() { export TMPDIR$(mktemp -d) } # Teardown runs after each test teardown() { rm -rf $TMPDIR } # Test: simple assertion test Function returns 0 on success { run my_function input [ $status -eq 0 ] } # Test: output verification test Function outputs correct result { run my_function test [ $output expected output ] } # Test: error handling test Function returns 1 on missing argument { run my_function [ $status -eq 1 ] }关键语法要素test 描述 { ... }每个用例块描述字符串会直接出现在测试报告中setup()/teardown()在每个用例前后各执行一次用于隔离环境mktemp -d创建临时目录teardown 中必须清理load test_helper加载同目录下的辅助脚本等价于 sourcerun command捕获命令的执行结果将退出码存入$status、标准输出存入$output断言本质就是[ ... ]/[[ ... ]]条件表达式失败即用例失败。值得注意run捕获的$output是合并后的标准输出多行输出会用换行拼接而$lines数组则按行切分两者在不同场景下各有用途。四、断言模式退出码、输出与文件4.1 退出码断言退出码是 Shell 程序最核心的契约Bats 通过run$status直接验证#!/usr/bin/env bats test Command succeeds { run true [ $status -eq 0 ] } test Command fails as expected { run false [ $status -ne 0 ] } test Command returns specific exit code { run my_function --invalid [ $status -eq 127 ] } test Can capture command result { run echo hello [ $status -eq 0 ] [ $output hello ] }-eq 0验证成功路径-ne 0验证失败路径精确断言特定退出码如 127 表示命令未找到可以捕捉失败但失败方式不对的回归同时断言$status与$output让成功路径的返回值也受控。4.2 输出断言输出断言覆盖精确匹配、子串匹配、正则匹配与多行匹配四种形态#!/usr/bin/env bats test Output matches string { result$(echo hello world) [ $result hello world ] } test Output contains substring { result$(echo hello world) [[ $result *world* ]] } test Output matches pattern { result$(date %Y) [[ $result ~ ^[0-9]{4}$ ]] } test Multi-line output { run printf line1\nline2\nline3 [ $output line1 line2 line3 ] } test Lines variable contains output { run printf line1\nline2\nline3 [ ${lines[0]} line1 ] [ ${lines[1]} line2 ] [ ${lines[2]} line3 ] }[ $a $b ]POSIX 精确字符串比较必须引号包裹防止分词[[ $a *sub* ]]bash 内置的 glob 子串匹配[[ $a ~ regex ]]正则匹配适合验证格式如年份^[0-9]{4}$多行输出用$output整体比较或按行用$lines数组逐行断言——后者对输出顺序敏感的解析类函数尤其有用。4.3 文件断言Shell 脚本常以文件系统副作用作为结果因此文件断言是重点#!/usr/bin/env bats test File is created { [ ! -f $TMPDIR/output.txt ] my_function $TMPDIR/output.txt [ -f $TMPDIR/output.txt ] } test File contents match expected { my_function $TMPDIR/output.txt [ $(cat $TMPDIR/output.txt) expected content ] } test File is readable { touch $TMPDIR/test.txt [ -r $TMPDIR/test.txt ] } test File has correct permissions { touch $TMPDIR/test.txt chmod 644 $TMPDIR/test.txt [ $(stat -f %OLp $TMPDIR/test.txt) 644 ] } test File size is correct { echo -n 12345 $TMPDIR/test.txt [ $(wc -c $TMPDIR/test.txt) -eq 5 ] }用-f、-r、-d等测试操作符验证存在性、可读性、目录性首个用例在调用前先断言文件尚不存在能验证函数确实创建而非复用旧文件权限断言stat -f %OLp是macOS 语法在 Linux 上需改为stat -c %a输出644。若测试需跨平台可用uname分支或优先选择[ -x ]、[ -r ]这类可移植测试操作符文件大小用wc -c file计数注意echo -n避免换行干扰计数。五、setup/teardown 生命周期模式5.1 基本模式每用例独立隔离#!/usr/bin/env bats setup() { # Create test directory TEST_DIR$(mktemp -d) export TEST_DIR # Source script under test source ${BATS_TEST_DIRNAME}/../bin/script.sh } teardown() { # Clean up temporary directory rm -rf $TEST_DIR } test Test using TEST_DIR { touch $TEST_DIR/file.txt [ -f $TEST_DIR/file.txt ] }$BATS_TEST_DIRNAME是 Bats 内置变量指向当前.bats文件所在目录用它可以稳定定位被测脚本../bin/script.sh不依赖工作目录source把被测函数直接注入当前 Shell 环境测试里可直接调用函数而非子进程每个用例都获得全新临时目录互不污染。5.2 带资源初始化构建输入/输出环境#!/usr/bin/env bats setup() { # Create directory structure mkdir -p $TMPDIR/data/input mkdir -p $TMPDIR/data/output # Create test fixtures echo line1 $TMPDIR/data/input/file1.txt echo line2 $TMPDIR/data/input/file2.txt # Initialize environment export DATA_DIR$TMPDIR/data export INPUT_DIR$DATA_DIR/input export OUTPUT_DIR$DATA_DIR/output } teardown() { rm -rf $TMPDIR/data } test Processes input files { run my_process_script $INPUT_DIR $OUTPUT_DIR [ $status -eq 0 ] [ -f $OUTPUT_DIR/file1.txt ] }对于读目录、写目录的批处理类脚本setup 中预置输入文件、导出输入/输出目录变量用例内直接引用避免在每个用例里重复铺陈环境。5.3 全局模式setup_file / teardown_file当多个用例共享昂贵资源大文件、数据库、网络连接时使用文件级钩子#!/usr/bin/env bats # Load shared setup from test_helper.sh load test_helper # setup_file runs once before all tests setup_file() { export SHARED_RESOURCE$(mktemp -d) echo Expensive setup $SHARED_RESOURCE/data.txt } # teardown_file runs once after all tests teardown_file() { rm -rf $SHARED_RESOURCE } test First test uses shared resource { [ -f $SHARED_RESOURCE/data.txt ] } test Second test uses shared resource { [ -d $SHARED_RESOURCE ] }setup_file()在整个文件所有用例前执行一次teardown_file()在全部用例结束后执行一次代价是用例之间共享状态、存在隐性依赖——只把只读型或重建成本高的资源放这里可写资源仍应走每用例的 setup/teardown。六、Mock 与 Stub 模式6.1 函数级 Mock针对被测脚本内部调用的外部命令/函数在测试 Shell 中重新定义同名函数并导出#!/usr/bin/env bats # Mock external command my_external_tool() { echo mocked output return 0 } test Function uses mocked tool { export -f my_external_tool run my_function [[ $output *mocked output* ]] }export -f将函数连同定义导出到子进程环境保证被测脚本即使 fork 子 Shell 也能命中 MockMock 内部可自定义输出与退出码从而驱动被测代码走不同分支。6.2 命令级 StubPATH 注入当被测脚本直接调用外部可执行文件如curl、jq时用假命令抢占 PATH#!/usr/bin/env bats setup() { # Create stub directory STUBS_DIR$TMPDIR/stubs mkdir -p $STUBS_DIR # Add to PATH export PATH$STUBS_DIR:$PATH } create_stub() { local cmd$1 local output$2 local code${3:-0} cat $STUBS_DIR/$cmd EOF #!/bin/bash echo $output exit $code EOF chmod x $STUBS_DIR/$cmd } test Function works with stubbed curl { create_stub curl { \status\: \ok\ } 0 run my_api_function [ $status -eq 0 ] }create_stub是复用的桩工厂接收命令名、输出内容、退出码三个参数退出码默认 0将 stub 目录置于PATH最前Shell 命令查找会优先命中桩这是模拟 HTTP 客户端、数据库客户端等外部依赖的最可靠手段——无需安装真实服务。6.3 变量级 Stub环境变量覆盖很多 Shell 程序的行为由环境变量驱动直接构造环境即可测两条路径#!/usr/bin/env bats test Function handles environment override { export MY_SETTINGoverride_value run my_function [ $status -eq 0 ] [[ $output *override_value* ]] } test Function uses default when var unset { unset MY_SETTING run my_function [ $status -eq 0 ] [[ $output *default* ]] }同一函数在变量被覆盖与变量未设置两种环境下分别断言验证配置优先级逻辑覆盖值 默认值。七、Fixture 管理7.1 静态 Fixture 文件复杂输入数据JSON、CSV、长文本不适合内联在用例里应放入tests/fixtures/#!/usr/bin/env bats # Fixture directory: tests/fixtures/ setup() { FIXTURES_DIR${BATS_TEST_DIRNAME}/fixtures WORK_DIR$(mktemp -d) export WORK_DIR } teardown() { rm -rf $WORK_DIR } test Process fixture file { # Copy fixture to work directory cp $FIXTURES_DIR/input.txt $WORK_DIR/input.txt # Run function run my_process_function $WORK_DIR/input.txt # Compare output diff $WORK_DIR/output.txt $FIXTURES_DIR/expected_output.txt }通过$BATS_TEST_DIRNAME/fixtures定位 fixture避免硬编码绝对路径先拷贝到临时工作目录再处理防止污染源 fixture最终用diff与期望输出逐字节比对——比字符串相等更严格能暴露多余空白/换行差异。7.2 动态 Fixture 生成当需要大批量或参数化数据时用函数现场生成#!/usr/bin/env bats generate_fixture() { local lines$1 local file$2 for i in $(seq 1 $lines); do echo Line $i content $file done } test Handle large input file { generate_fixture 1000 $TMPDIR/large.txt run my_function $TMPDIR/large.txt [ $status -eq 0 ] [ $(wc -l $TMPDIR/large.txt) -eq 1000 ] }动态生成适合性能/容量类验证如 1000 行大文件同时用例末尾复核生成结果保证测试前提自身可靠。八、进阶模式8.1 错误条件测试生产级测试必须覆盖失败路径而不只是 happy path#!/usr/bin/env bats test Function fails with missing file { run my_function /nonexistent/file.txt [ $status -ne 0 ] [[ $output *not found* ]] } test Function fails with invalid input { run my_function [ $status -ne 0 ] } test Function fails with permission denied { touch $TMPDIR/readonly.txt chmod 000 $TMPDIR/readonly.txt run my_function $TMPDIR/readonly.txt [ $status -ne 0 ] chmod 644 $TMPDIR/readonly.txt # Cleanup } test Function provides helpful error message { run my_function --invalid-option [ $status -ne 0 ] [[ $output *Usage:* ]] }错误用例同时断言退出码非零与错误信息内容not found、Usage:后者能防止报错但信息误导的劣质实现权限错误用例记得在断言后恢复权限chmod 644避免污染后续用例。8.2 依赖工具检测与 skip当被测脚本依赖jq等可选工具时用skip优雅降级而非直接失败#!/usr/bin/env bats setup() { # Check for required tools if ! command -v jq /dev/null; then skip jq is not installed fi export SCRIPT${BATS_TEST_DIRNAME}/../bin/script.sh } test JSON parsing works { skip_if ! command -v jq /dev/null run my_json_parser {key: value} [ $status -eq 0 ] }skip 原因使用例标记为跳过TAP 输出中为ok # SKIPCI 视为通过而非失败对需要 root、特定架构、特定 Shell 方言的用例同样适用让测试套件在不同环境都能跑起来。8.3 跨 Shell 方言兼容性测试Shell 脚本常需兼容 bash、POSIX sh、dash 等多种解释器#!/usr/bin/env bats test Script works in bash { bash ${BATS_TEST_DIRNAME}/../bin/script.sh arg1 } test Script works in sh (POSIX) { sh ${BATS_TEST_DIRNAME}/../bin/script.sh arg1 } test Script works in dash { if command -v dash /dev/null; then dash ${BATS_TEST_DIRNAME}/../bin/script.sh arg1 else skip dash not installed fi }分别用bash、sh、dash显式执行被测脚本捕获bash 特有语法在 POSIX 环境下崩溃的移植性问题dash不可用如 macOS 默认无 dash时跳过保持跨平台可运行。8.4 并行执行对相互独立、互不共享状态的用例可在 Bats 文件内并行化#!/usr/bin/env bats test Multiple independent operations { run bash -c for i in {1..10}; do my_operation $i done wait [ $status -eq 0 ] } test Concurrent file operations { for i in {1..5}; do my_function $TMPDIR/file$i done wait [ -f $TMPDIR/file1 ] [ -f $TMPDIR/file5 ] }Shell 层面用后台 wait聚合验证并发场景的正确性如并发写不同文件前提是各操作必须无共享可写状态——这要求测试数据隔离每个任务使用独立文件否则会产生竞态噪声。九、Test Helper 复用模式当多个.bats文件需要相同断言时抽到test_helper.sh统一维护#!/usr/bin/env bash # Source script under test export SCRIPT_DIR${BATS_TEST_DIRNAME%/*}/bin # Common test utilities assert_file_exists() { if [ ! -f $1 ]; then echo Expected file to exist: $1 return 1 fi } assert_file_equals() { local file$1 local expected$2 if [ ! -f $file ]; then echo File does not exist: $file return 1 fi local actual$(cat $file) if [ $actual ! $expected ]; then echo File contents do not match echo Expected: $expected echo Actual: $actual return 1 fi } # Create temporary test directory setup_test_dir() { export TEST_DIR$(mktemp -d) } cleanup_test_dir() { rm -rf $TEST_DIR }assert_file_exists/assert_file_equals返回非零即失败并打印诊断信息期望值 vs 实际值失败可读性远高于裸[ ]SCRIPT_DIR${BATS_TEST_DIRNAME%/*}/bin的推导逻辑与 AAS 仓库真实脚本的做法一致——例如 scripts/validate-links.sh 使用SCRIPT_DIR$(cd $(dirname ${BASH_SOURCE[0]}) pwd)再结合PROJECT_ROOT定位项目根两者都是基于脚本自身位置定位资源的稳健模式在测试文件顶部load test_helper即可复用全套工具保持用例简洁。十、CI/CD 集成10.1 GitHub Actions 工作流name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Install Bats run: | npm install --global bats - name: Run Tests run: | bats tests/*.bats - name: Run Tests with Tap Reporter run: | bats tests/*.bats --tap | tee test_output.tapnpm install --global bats一行完成 CI 安装无需克隆源码bats tests/*.bats通配符批量执行全部测试文件非零退出码自动使 CI 失败--tap输出 TAP 格式并通过tee落盘存档供后续解析或归档。10.2 Makefile 集成.PHONY: test test-verbose test-tap test: bats tests/*.bats test-verbose: bats tests/*.bats --verbose test-tap: bats tests/*.bats --tap test-parallel: bats tests/*.bats --parallel 4 coverage: test # Optional: Generate coverage reports--verbose打印每个用例的详细输出含run捕获的 stdout/stderr排查失败更直观--parallel 4Bats 内置并行执行按文件粒度并行4 路并发前提是各测试文件间无共享状态coverage目标预留为覆盖率报告的扩展点。十一、最佳实践十条实现手册沉淀了十条可操作的工程准则逐条落地即可让测试套件达到生产级每个测试只测一件事——单一职责原则失败定位一目了然使用描述性测试名——test的描述要清楚说明被测行为测试后清理——所有临时文件必须在 teardown 中移除避免跨用例污染与磁盘泄漏同时测成功与失败路径——不要只写 happy path错误处理是 Shell 脚本最易回归的部分Mock 外部依赖——用函数 Mock、PATH 桩、环境变量隔离被测单元保证测试确定性复杂数据用 Fixtures——长 JSON/CSV 放入tests/fixtures/提升可读性在 CI/CD 中运行测试——尽早捕获回归跨 Shell 方言测试——bash/sh/dash 分别验证确保可移植性保持测试快速——能并行就并行慢测试会拖垮开发节奏文档化复杂测试配置——对非常规模式如桩、共享资源加注释说明。十二、在 AAS 仓库中的落点与延伸本技能在仓库中有两个实体技能定义与使用说明 负责何时用、怎么用的决策层实现手册 负责怎么写的细节层两者配合构成完整技能根目录另有镜像副本 skills/bats-testing-patterns/SKILL.md。作为实践参照AAS 仓库自身的 scripts/ 目录就是天然的 Bats 测试对象以 validate-links.sh 为例它具备典型的生产 Shell 脚本特征——set -euo pipefail严格模式、基于BASH_SOURCE推导脚本目录、路径感知的确定性输出这些都可以用本文的退出码断言、输出断言与文件断言体系逐一覆盖activate-skills.sh、validate-glossary.sh 同理。你可以在自己的项目中按第二节的目录结构搭建tests/树配合test_helper.sh与fixtures/将本文全部模式直接落地。【免费下载链接】agentic-awesome-skillsAAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,445 agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.项目地址: https://gitcode.com/gh_mirrors/an/agentic-awesome-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表