Unit testing for shell scripts Unit testing for shell scripts linux linux

Unit testing for shell scripts


UPDATE 2019-03-01: My preference is bats now. I have used it for a few years on small projects. I like the clean, concise syntax. I have not integrated it with CI/CD frameworks, but its exit status does reflect the overall success/failure of the suite, which is better than shunit2 as described below.


PREVIOUS ANSWER:

I'm using shunit2 for shell scripts related to a Java/Ruby web application in a Linux environment. It's been easy to use, and not a big departure from other xUnit frameworks.

I have not tried integrating with CruiseControl or Hudson/Jenkins, but in implementing continuous integration via other means I've encountered these issues:

  • Exit status: When a test suite fails, shunit2 does not use a nonzero exit status to communicate the failure. So you either have to parse the shunit2 output to determine pass/fail of a suite, or change shunit2 to behave as some continuous integration frameworks expect, communicating pass/fail via exit status.
  • XML logs: shunit2 does not produce a JUnit-style XML log of results.


Wondering why nobody mentioned BATS. It's up-to-date and TAP-compliant.

Describe:

#!/usr/bin/env bats@test "addition using bc" {  result="$(echo 2+2 | bc)"  [ "$result" -eq 4 ]}

Run:

$ bats addition.bats ✓ addition using bc1 tests, 0 failures


Roundup by @blake-mizerany sounds great, and I should make use of it in the future, but here is my "poor-man" approach for creating unit tests:

  • Separate everything testable as a function.
  • Move functions into an external file, say functions.sh and source it into the script. You can use source `dirname $0`/functions.sh for this purpose.
  • At the end of functions.sh, embed your test cases in the below if condition:

    if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; thenfi
  • Your tests are literal calls to the functions followed by simple checks for exit codes and variable values. I like to add a simple utility function like the below to make it easy to write:

    function assertEquals(){    msg=$1; shift    expected=$1; shift    actual=$1; shift    if [ "$expected" != "$actual" ]; then        echo "$msg EXPECTED=$expected ACTUAL=$actual"        exit 2    fi}
  • Finally, run functions.sh directly to execute the tests.

Here is a sample to show the approach:

    #!/bin/bash    function adder()    {        return $(($1+$2))    }    (        [[ "${BASH_SOURCE[0]}" == "${0}" ]] || exit 0        function assertEquals()        {            msg=$1; shift            expected=$1; shift            actual=$1; shift            /bin/echo -n "$msg: "            if [ "$expected" != "$actual" ]; then                echo "FAILED: EXPECTED=$expected ACTUAL=$actual"            else                echo PASSED            fi        }        adder 2 3        assertEquals "adding two numbers" 5 $?    )