test-shell
Skill1 starsChanged 9 days agoMITView on GitHub
BATS shell script testing. Use when writing or running shell script tests. Covers setup/teardown, assertions, mocking, helper patterns, and coverage with kcov.
In this repository41 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- analyze-codeSkill
- analyze-projectSkill
- analyze-testsSkill
- audit-mergesSkill
- babysit-prSkill
- backlogSkill
- branchSkill
- coderabbitSkill
- commitSkill
- greptileSkill
- implement-issuesSkill
- issueSkill
- jiraSkill
- lintro-addSkill
- lintro-reviewSkill
- lintro-verifySkill
- lintSkill
- properizeSkill
- pr-raycastSkill
- prSkill
- raycastSkill
- rebaseSkill
- reconcileSkill
- scorecardSkill
- stand-ciSkill
- stand-generalSkill
- stand-odinSkill
- stand-pySkill
- stand-rustSkill
- stand-tsSkill
- sweep-prsSkill
- test-apiSkill
- testSkill
- test-ui-qsfSkill
- test-uiSkill
- turbo-addSkill
- turbo-testSkill
- turbo-verifySkill
- which-prSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 5 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 233
One assertion focus per test: - Test one behavior, use descriptive names
- Distinctive: line 234
Use setup/teardown: - Never leave temp files behind
- Distinctive: line 236
Mock external commands: - Don't depend on network/external state
- Distinctive: line 237
Group related tests: - Use comment headers to organize
- Distinctive: line 239
Keep tests fast: - Avoid sleep, minimize I/O
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Shell Testing with BATS
BATS (Bash Automated Testing System) for testing shell scripts and libraries.
Run Commands
# Run all tests recursively
bats --recursive tests/bats/
# Run specific test file
bats tests/bats/unit/lib/test_log.bats
# Run with TAP output (for CI)
bats --tap tests/bats/
# Run with timing info
bats --timing tests/bats/
# Filter tests by name pattern
bats --filter "validate_semver" tests/bats/
# Run in parallel
bats --jobs 4 tests/bats/Directory Structure
tests/
├── bats/
│ ├── unit/
│ │ └── lib/
│ │ ├── test_log.bats
│ │ ├── test_fs.bats
│ │ ├── github/
│ │ │ └── test_output.bats
│ │ └── release/
│ │ └── test_version.bats
│ └── integration/
│ └── test_actions.bats
├── helpers/
│ ├── common.bash # Shared utilities
│ ├── mocks.bash # Command mocking
│ └── github_env.bash # GitHub Actions simulation
└── fixtures/
└── json/ # Test data filesTest File Template
#!/usr/bin/env bats
# SPDX-License-Identifier: MIT
# Purpose: Tests for scripts/ci/lib/example.sh
load "../helpers/common"
load "../helpers/mocks"
setup() {
setup_temp_dir
export LIB_DIR
}
teardown() {
teardown_temp_dir
}
# =============================================================================
# Function group description
# =============================================================================
@test "function_name: does expected thing" {
run bash -c 'source "$LIB_DIR/example.sh" && function_name "arg"'
assert_success
assert_output "expected output"
}
@test "function_name: handles edge case" {
run bash -c 'source "$LIB_DIR/example.sh" && function_name ""'
assert_failure
assert_output --partial "error message"
}Assertions (bats-assert)
# Exit code assertions
assert_success # Exit code 0
assert_failure # Exit code != 0
# Output assertions
assert_output "exact match"
assert_output --partial "substring"
assert_output --regexp "pattern.*here"
refute_output # Output is empty
refute_output --partial "not present"
# Line assertions (for multiline output)
assert_line "exact line"
assert_line --partial "substring in any line"
assert_line --index 0 "first line"
# Equality
assert_equal "$expected" "$actual"File Assertions (bats-file)
assert_file_exists "/path/to/file"
assert_file_not_exists "/path/to/file"
assert_dir_exists "/path/to/dir"
assert_file_contains "/path/to/file" "expected content"Setup and Teardown
# Per-test setup/teardown
setup() {
setup_temp_dir # Create $BATS_TEST_TMPDIR
export LIB_DIR # Make available to subshells
}
teardown() {
teardown_temp_dir # Clean up temp files
}
# Per-file setup/teardown (run once)
setup_file() {
# Heavy one-time setup (e.g., start services)
}
teardown_file() {
# One-time cleanup
}Mocking Commands
# In mocks.bash helper:
mock_command() {
local cmd="$1"
local output="$2"
local exit_code="${3:-0}"
local mock_dir="${BATS_TEST_TMPDIR}/mocks"
mkdir -p "$mock_dir"
cat > "${mock_dir}/${cmd}" <<MOCK
#!/usr/bin/env bash
echo "$output"
exit $exit_code
MOCK
chmod +x "${mock_dir}/${cmd}"
export PATH="${mock_dir}:$PATH"
}
# Usage in test:
@test "handles curl failure" {
mock_command "curl" "Connection refused" 1
run my_download_function "http://example.com"
assert_failure
}Testing with Bash 4+ Features
macOS ships with Bash 3.2. For tests requiring Bash 4+ features:
# In common.bash:
bash4_available() {
[[ "${BASH4_AVAILABLE:-0}" -eq 1 ]]
}
# In tests:
@test "feature requiring bash 4+" {
if ! bash4_available; then
skip "requires bash 4+"
fi
# ... test code using ${var,,} or associative arrays
}GitHub Actions Environment Simulation
# In github_env.bash helper:
setup_github_env() {
export GITHUB_OUTPUT="${BATS_TEST_TMPDIR}/github_output"
export GITHUB_ENV="${BATS_TEST_TMPDIR}/github_env"
export GITHUB_STEP_SUMMARY="${BATS_TEST_TMPDIR}/step_summary"
touch "$GITHUB_OUTPUT" "$GITHUB_ENV" "$GITHUB_STEP_SUMMARY"
}
# Assert GitHub output was set:
get_github_output() {
local key="$1"
grep "^${key}=" "$GITHUB_OUTPUT" | cut -d= -f2-
}
@test "sets github output" {
setup_github_env
run bash -c 'source "$LIB_DIR/github/output.sh" && set_github_output "key" "value"'
assert_success
assert_equal "value" "$(get_github_output "key")"
}Coverage with kcov
# Run with coverage
kcov --include-path=scripts/ci/lib coverage-report bats tests/bats/
# CI workflow configuration
coverage: true
coverage-threshold: 80
upload-coverage: trueBest Practices
- One assertion focus per test - Test one behavior, use descriptive names
- Use setup/teardown - Never leave temp files behind
- Isolate tests - Each test should work independently
- Mock external commands - Don't depend on network/external state
- Group related tests - Use comment headers to organize
- Test edge cases - Empty strings, missing files, invalid input
- Keep tests fast - Avoid sleep, minimize I/O
Common Patterns
Testing Exit Codes
@test "die: exits with code 1" {
run bash -c 'source "$LIB_DIR/log.sh" && die "error"'
assert_failure
assert_equal "1" "$status"
}Testing stderr vs stdout
@test "logs to stderr" {
run bash -c 'source "$LIB_DIR/log.sh" && log_error "msg" 2>&1'
assert_output --partial "ERROR"
}Testing Function Exports
@test "function is exported" {
source "$LIB_DIR/example.sh"
run bash -c "declare -F my_function"
assert_success
}Skipping Tests
@test "requires specific tool" {
command -v special_tool >/dev/null || skip "special_tool not installed"
# ... test code
}Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

