Testing Scripts with Bats
Prerequisites: Functions, Debugging scripts
Scripts that other people — or production systems — rely on deserve
tests. Bats (Bash Automated Testing System) runs tests written in a
Bash-like syntax. Install it with your package manager (apt install bats, brew install bats-core).
Making a script testable
Put logic in functions, and only run main when the file is executed
(not when it's loaded by a test):
# illustrative
# slugify.sh
slugify() {
local text=${1,,}
text=${text//[^a-z0-9]/-}
while [[ $text == *--* ]]; do text=${text//--/-}; done
text=${text#-}
echo "${text%-}"
}
main() {
slugify "$*"
}
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
main "$@"
fi
The last three lines are a common idiom: BASH_SOURCE[0] equals $0 only
when the file is run directly, so a test can source it to get the
function without running main.
Writing tests
# illustrative
# test/slugify.bats
setup() {
source "$BATS_TEST_DIRNAME/../slugify.sh"
}
@test "lowercases and replaces spaces" {
run slugify "Hello World"
[ "$status" -eq 0 ]
[ "$output" = "hello-world" ]
}
@test "collapses punctuation and trims dashes" {
run slugify " Bash: Tips & Tricks! "
[ "$output" = "bash-tips-tricks" ]
}
$ bats test/
test/slugify.bats
✓ lowercases and replaces spaces
✓ collapses punctuation and trims dashes
2 tests, 0 failures
@test "description" { ... }defines a test.run commandruns it and captures$status(exit code) and$output.- Each assertion is an ordinary test: if any command fails, the test fails.
The bats-assert helper library adds readable assertions such as
assert_output "hello-world" and assert_failure.
What to test
Test the logic that transforms data and makes decisions, plus error handling: missing arguments, missing files, and bad input should all fail with clear messages and non-zero exit codes. Run the tests — and ShellCheck — automatically in CI.