Skip to content

$ cat ./posts/bash/testing-shell-scripts-with-bats.bats

-rw-r--r-- 1.5K #bash #git

Testing shell scripts with bats

by chris · 2 min read · /bash


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.

← cd .. fris@linux:~/blog$ man bash