Skip to content
AgileLogics
5 min read

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 command runs 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.