Testing Shell Scripts

Getting Started

The best way I have found to unit test shell scripts is rather simple and comes from JSON.sh[1] (I've changed this a little bit for my own use):

#!/bin/sh

# Set current directory to the current file
cd "${0%/*}" || exit 1

fail=0
tests=0
passed=0

for test in tests/*.sh ; do
    tests=$((tests+1))
    echo "TEST: $test"
    if "./$test"; then
        echo "OK: ---- $test"
        passed=$((passed+1))
    else
        echo "FAIL: $test"
        fail=$((fail+ret))
    fi
done

if [ "$fail" -eq 0 ]; then
    echo 'SUCCESS'
    exitcode=0
else
    echo 'FAILURE'
    exitcode=1
fi

echo "$passed / $tests"
exit $exitcode

Essentially what this does is it:

I haven't written super complex shell scripts yet, but I feel like this gives enough flexibility to work with simple unit testing in basic applications.

Another advantage is that meta files, like set up and tear down, can be kept in a file preceded by ., as they will be skipped by the above glob.

Examples

An example of this could be testing a function called capitalize. First, we write the above test runner to the root folder and then make the test file in our tests folder:

tests/capitalize.sh

cd ${0%/*}

source "../capitalize.sh"

fails=0

words=(bob john Jane ron-jon)
expected_words=(Bob John Jane Ron-jon)

for word in "${words[@]}"; do
    i=$((i + 1))
    result="$(capitalize "$word")"
    if [ "$result" != "${expected_words[i]}" ]; then
        fails=$((fails + 1))
    fi
done

exit $fails

And then we run the test runner and see that it failed before writing the actual function.

capitalize.sh

capitalize() {
    # imagine this capitalizes things and returns the capitalized word
}

If you want to test using external files, you can change the for loop above to a glob, like

for file in ./mock_data/*; do

References

  1. https://github.com/dominictarr/JSON.sh
  2. https://thomaslevine.com/computing/shell-testing/
  3. https://poisel.info/posts/2022-05-10-shell-unit-tests/

Last modified: 202401040446