Short, self-contained, and actually useful unit testing library for bash, zsh, ksh, dash, and sh.
There are tons of alternatives, but I was not satisfied with the ones I looked at. They tend to be one of:
- huge multi-file frameworks like bats-core. Maybe useful, but requires installation and not simple to copy around.
- dumb wrappers around the
test
command for natural language assertions. Too minimal to be useful. - long single-file libraries. Better, but usually too long and not feature dense enough for my taste.
So I wrote my own. This is a middle ground for some convenience while remaining small enough to read at a glance and to vendor the whole file as needed.
Only the test code needs to be written in bash or POSIX-compatible shell command language; the tests can test the input and output of any generic program or script.
- Easy usage: just source the library after defining
test_*
functions, and execute your file - Hooks: before_all, after_all, before_each, after_each
- Assertions: no need to reinvent the wheel; just use the
test
or[[ ]]
commands - Output is hidden for passing tests and shown for failing tests
- Verbose and tiny output modes
- Test Anything Protocol (TAP) compliant
- Less than 100 lines total, cleanly organized into a few functions
- Support for tests written in other non-bash POSIX shells, like dash!
- Copy bashaspec.sh to your repository
myscript-spec.sh
#!/bin/bash test_concat() { a='1' b='2' echo 'concatenating...' c="$a$b" [[ "$c" = "12" ]] || return } test_add() { a='1' b='2' echo 'adding...' c=$(( a + b )) [[ "$c" -eq 3 ]] || return } test_an_error() { echo 'returning nonzero is an error' echo 'use return codes to identify failed assertions' [[ 'a' = 'a' ]] || return 1 [[ 'a' = 'b' ]] || return 2 # test failure! [[ 'a' = 'c' ]] || return 3 } test_success() { now="$(date +%s)" future=$(( now + 1 )) (( now < future )) || return } test_external_command() { expected_out="857691210 7" expected_exit=0 cksum_out="$(cksum <<<"foobar")" cksum_exit=$? diff <(printf %s "$expected_out") <(printf %s "$cksum_out") || return 1 [[ "$expected_exit" -eq "$cksum_exit" ]] || return "$cksum_exit" } . ./bashaspec.sh
./myscript-spec.sh
to run just the tests inmyscript-spec.sh
or./bashaspec.sh
to run all test files in the current directory, recursively[user@host ~]$ ./myscript-spec.sh Running 5 tests .x... 4 of 5 tests passed 1 failures: test_an_error returned 2 returning nonzero is an error use return codes to identify failed assertions
set -e
does not affect test functions, so put|| return
after any assertion commands.- This is unavoidable, because the framework needs to prevent
set -e
from exiting the script when a test fails, and the framework needs to capture failed tests, so test functions are put into an OR list (aka||
list).set -e
only affects commands in an OR list after the last||
, and those are supplied by the framework.
- This is unavoidable, because the framework needs to prevent
- There is not a standard for listing defined functions. bashaspec has special cases for bash and zsh, but in other shells, bashaspec will scan the test file for test functions.
When using other shells, if your test script changes directory (cd
) outside of a test function, bashaspec needs to be told the path of the test file by setting the_bashaspec_test_file
variable.
For example:#!/bin/dash cd "$(dirname "$0")" || exit 1 _bashaspec_test_file="$(pwd)/$(basename "$0")" test_foo() { : ... ; } . ../bashaspec.sh
bashaspec.sh
produces TAP output when given the-v
or--verbose
argument. The TAP handling makes the script slightly longer than the original non-tap version, but the main bashaspec.sh version is better anyway, so you should use this.bashaspec-non-posix.sh
only supports tests written in bash, and is kept for reference. You probably want to just use bashaspec.sh.bashaspec-non-tap.sh
is similar, but does not produce TAP output, and has marginally simpler code. Historical; prefer bashaspec.sh for regular usage.bashaspec-ancient.sh
has all bashaspec.sh features, but additionally supports tests written in pre-posix relics like heirloom shell, at the cost of using backticks instead of $(), and at the cost of shelling out to expr to perform arithmetic operations. Do you really need this version? You probably want to just use bashaspec.sh.- This readme is about as long as the actual library.
MIT