The scripts nobody tests are the ones that run as root on every machine in the fleet. bats is a test runner written in bash, so there is no new language to learn and no runtime to install beyond the shell already there.
1. A test is a function with a name
#!/usr/bin/env bats
setup() {
load '../lib/deploy.sh'
TMP="$(mktemp -d)"
}
teardown() {
rm -rf "$TMP"
}
@test "version_from_tag strips the v prefix" {
run version_from_tag "v1.4.2"
[ "$status" -eq 0 ]
[ "$output" = "1.4.2" ]
}
@test "version_from_tag rejects a bare number" {
run version_from_tag "1.4.2"
[ "$status" -ne 0 ]
}
run captures status and output instead of letting a non-zero exit abort the test. That single helper is most of what bats provides.
2. Structure the script so it can be tested
A script that does its work at the top level cannot be sourced. Put the logic in functions and guard the entry point:
main() { ... }
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Now the test file can source it and call individual functions without triggering a deployment.
3. Test the dangerous paths first
Not the happy path — the argument parsing, the path construction, and anything that builds a string later passed to rm. Those are where the outages live. A test that asserts build_target_dir "" fails loudly is worth more than twenty tests of a formatting helper.