bash: Unit testing using `shunit2`

A bash script can declare functions with the name test... in order for the shunit2 unit testing tool to automatically discover and run them as tests.

Co-locating tests

To colocate tests with the code they test, define the test... functions inside another function, RunTests, that is conditionally run via a command line flag.

For example, given the following executable script, foo.sh, you could run the script with ./foo.sh and run the tests with ./foo.sh --test:

# Library functions
function lib() { echo "result" ; }
# etc.

# Main script entrypoint
function RunMain() { echo "TODO" ;  }

# Test script entrypoint
function RunTests() {
  # Tests
  function testFoo() { assertEquals "1" "1"; }
  function testBar() { assertEquals "result" "$(lib)"; }
  function testBaz() { assertEquals "1 2 result" "$("$0" 1 2)"; }
  # etc.

  # Run tests
  source shunit2
}

# Choose an entry point
expr "$*" : ".*--test" > /dev/null && RunTests || RunMain

Separating tests

Alternatively, define tests in a separate file that sources the original. Be careful that the main script does not execute when sourced.

Main script, foo.sh:

# Library functions
function lib() { echo "result" ; }
# etc.

# Main script entrypoint
function RunMain() { echo "TODO" ;  }

# Do not execute when sourced
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && RunMain

Test script, foo_test.sh:

# Source main script to make functions available
source foo.sh

# Tests
function testFoo() { assertEquals "1" "1"; }
function testBar() { assertEquals "result" "$(lib)"; }
# etc.

# Run tests
source shunit2
Published on: 27 Sep 2022