-
Notifications
You must be signed in to change notification settings - Fork 212
Cookbook
Let's say a function was called, and the output should be included to help in debugging the test. This is the recommended way to do so.
#!/bin/bash
# test_good_bad.sh
good() { echo "Wohoo!!"; }
bad() { echo "Booo :-("; return 123; }
test_functions() {
# Naively hope that all functions return true.
while read desc fn; do
output=$(${fn} 2>&1); rtrn=$?
assertTrue "${desc}: unexpected error (${rtrn}); ${output}" ${rtrn}
done <<EOF
good() good
bad() bad
EOF
}
. ~/lib/sh/shunit2
Testing the code show that the bad
output is added to the assertTrue
output.
$ ./test_good_bad.sh
test_functions
ASSERT:bad(): unexpected error (123); Booo :-(
Ran 1 test.
FAILED (failures=1)
The most straightforward method is to pipe STDOUT
and STDERR
to files, and use the diff
or grep
command against those files. To simplify this, shUnit2 provides a temporary directory for writing such files that will be automatically cleaned up at the end of the test run.
The minimal solution for this looks like this. For a slightly more helpful example, see examples/output_test.sh.
#! /bin/sh
generateOutput() {
echo 'this output went to STDOUT'
echo 'this output went to STDERR' >&2
return 1
}
testGenerateOutput() {
( generateOutput >"${stdoutF}" 2>"${stderrF}" )
assertTrue "the command exited with an error" $?
# This test will pass because the grepped output matches.
grep -q 'STDOUT' "${stdoutF}"
assertTrue 'STDOUT message missing' $?
# This test will fail because the grepped output doesn't match.
grep -q 'ST[andar]DERR[or]' "${stderrF}"
assertTrue 'STDERR message missing' $?
return 0
}
oneTimeSetUp() {
# Define global variables for command output.
stdoutF="${SHUNIT_TMPDIR}/stdout"
stderrF="${SHUNIT_TMPDIR}/stderr"
}
setUp() {
# Truncate the output files.
cp /dev/null "${stdoutF}"
cp /dev/null "${stderrF}"
}
# Load and run shUnit2.
. ./shunit2
The following code is pulled from the unit tests of log4sh, and demonstrates the testing of various conversion patterns. The custom assert was written to reduce code duplication (think the DRY principle) for many tests in that codebase.
assertPattern() {
msg=''
if [ $# -eq 3 ]; then
msg=$1
shift
fi
pattern=$1
expected=$2
appender_setPattern ${APP_NAME} "${pattern}"
appender_activateOptions ${APP_NAME}
actual=`logger_info 'dummy'`
msg=`eval "echo \"${msg}\""`
assertEquals "${msg}" "${expected}" "${actual}"
}
testCategoryPattern() {
pattern='%c'
expected='shell'
msg="category '%c' pattern failed: '\${expected}' != '\${actual}'"
assertPattern "${msg}" "${expected}" "${pattern}"
}
Passing command-line arguments to a test script is not a problem, as long as one small requirement is met. Before calling shunit2, use shift
to shift all the command-line arguments off the stack.
#!/bin/bash
# test_date_cmd.sh
# Echo any provided arguments.
[ $# -gt 0 ] && echo "ARGC: $# 1:$1 2:$2 3:$3"
test_date_cmd() {
( date '+%a' >/dev/null 2>&1 )
local rtrn=$?
assertTrue "unexpected date error; ${rtrn}" ${rtrn}
}
# Eat all command-line arguments before calling shunit2.
shift $#
. ~/lib/sh/shunit2
Calling the command without arguments.
$ ./test_date_cmd.sh
test_date_cmd
Ran 1 test.
OK
Calling the command with arguments.
$ ./test_date_cmd.sh a b c
ARGC: #:3 1:a 2:b 3:c
test_date_cmd
Ran 1 test.
OK
Writing unit tests for a pre-existing binary is not much different than writing unit tests for functions. The only real difference is that you do not have the degree of flexibility of being able to rewrite the command to make testing of unique conditions easier.
If you would like to see an example of testing the mkdir command, look examples/mkdir_test.sh.
Mocking files is not difficult, but it usually requires slightly refactoring the code under test so that the code isn't only using constants to specify the files to be read. To alternatives are:
- The filename to be read should be passed into a function that reads the file, allowing a unit test to pass a different file in.
- A function should be provided that returns the filename to be read, and that function returns a filename according to whether the code is functioning normally, or under test.
Example code for both of these can be seen in examples/mock_file.sh and examples/mock_file_test.sh
Install shunit2.
$ brew install shunit2
Create a basic homebrew_test.sh
test.
testHomebrew() {
assertTrue 0
}
. /usr/local/bin/shunit2
Run the test.
$ sh homebrew_test.sh
testHomebrew
Ran 1 test.
OK