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.
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
andsource
it into the script. You can usesource `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 $? )