Testing Package¶
Testing package provides features similar to xUnit frameworks available in other languages (testing package in Go, pyunit for Python, JUnit for Java, etc.).
Writing Tests¶
It is great having tests for each function. We keep test files in the
same package as the corresponding source files. Test files have to
have suffix _test.sh
. Although not required, it is expected that
for every file, e.g., matrix.sh
, there would be a corresponding
test file, e.g., matrix_test.sh
.
Each test is a function that starts with function test_
. These
functions have to be at the beginning of lines.
function test_strings_cap() { # Valid test signature.
function test_strings_cap() { # NOT a valid test signature (not at the beginning of a line).
function strings_cap_test() { # NOT a valid test signature (does not start with test).
test_strings_cap() { # NOT a valid test signature (no function keyword).
function test_strings_cap() { # NOT a valid test signature (spaces after ()).
function test_strings_cap () { # NOT a valid test signature (spaces after the identifier).
function test_strings_cap()
{ # NOT a valid test signature ({ not on the same line).
Test function names in the gobash repository are formed with the following convention:
function test_
at the beginning (required)name of the module being tested (e.g.,
strings
)name of the function being tested OR meaningful description if more than a single function is targeted
Below are some examples from this repo.
#strings_test.sh - test for strings.sh module.
function test_strings_len() { ...
# complex_test.sh - test for complex.sh module.
function test_complex_plus() { ...
Test Outcome¶
The outcome of a test corresponds to the return code from the test function.
Here is an example of a trivial passing test.
function test_passing() {
return 0
}
Here is an example of a trivial failing test.
function test_failing() {
return 1 # or any other non-zero value
}
Accessing Test Metadata¶
The first (and only) argument passed to each test function (test for
short) is an object (an instance of the TestT
struct), which
carries metadata about the test itself and can be used by the
developers to set test status (more on this in later sections).
function test_first_arg() {
local -r t="${1}"
# You can access info about the test via t.
}
Skipping Tests¶
Using a test metadata object, one can skip a test by invoking the
skip
method. An optional message can be given as well, which will
be shown during test result reporting.
In the example below, we show a test that is skipped in case
dependencies for the library being tested are not
available. Specifically, when testing the whiptail
package, we
check that dependencies for that package are available (by invoking
whiptail_enabled
). If dependencies are not available, we skip the
test.
function test_whiptail_msg_box() {
local -r t="${1}"
! whiptail_enabled && $t skip "No deps."
local box
# ...
}
Note
If a test fails and the skip flag is set to true, the test will be counted as failing (and not as skipped).
Asserting Results¶
gobash includes a number of assertion functions (assert.sh)
that can be conveniently used in tests. If an assertion fails, a stack
trace is printed, and exit 1
is executed. (Note that a failing
assertion stops only the current test, and not the entire test run,
because each test is run in a subshell.) In the library itself, we do
not use assert
functions to ensure compatibility with set -e
option in bash.
#!/bin/bash
. gobash/gobash
function test_with_assertion() {
assert_eq 3 5 "3 and 5 are not equal"
}
# Output
# ERROR: <3> not equal to <5> (3 and 5 are not equal)
# 67 assert_eq $HOME/projects/gobash/src/lang/assert.sh
# 6 test_with_assertion ./demo_test.sh
# ./demo_test.sh 708[ms]
# Tests run: 1, failed: 1, skipped: 0.
# Total time: 834[ms]
Mocking Functions¶
Mocking in gobash is done on a function level. There is nothing specific to gobash, as we simply rely on bash dynamic nature and ability to replace any function (in a specific scope).
Below is an example of mocking that we use during testing of the
whiptail
API. To avoid opening any window during testing (and
invoking whiptail
), we implement a mock function that will be
invoked from show
. The mock function simply returns a result that
we desire.
function test_whiptail_input_box() {
local -r t="${1}"
! whiptail_enabled && $t skip "No deps."
local box
box=$(WTInputBox "Text")
assert_ze $?
# Mocking whiptail command.
function whiptail() {
echo "Result" >&3
return 0
}
local -r res=$(WTResult)
$box show "$res"
assert_eq $($res val) "Result"
}
readonly -f test_whiptail_input_box
Running Tests¶
gobash test
command can be used for running tests.
./gobash test # arguments as you wish
Below is the help message for gobash test
, which is printed when
running the framework without any arguments. (While we strive to keep
this doc up-to-date, the latest help message is best obtained by
running gobash test
.)
Testing package.
--paths (string) - Name pattern for finding files with tests.
--tests (string) - Regular expression for test functions to run.
--verbose (bool) - Enables verbose output.
--quiet (bool) - Disable any output.
--junitxml (string) - File name for a report in the JUnit xml format.
--max_secs (int) - Max seconds per test.
--stdout (bool) - Enable stdout from tests.
Run all tests available in files in the current directory and any sub directory.
$ gobash test --paths .
Note
Given value to the paths
flag is used as a name pattern of a
find
command to find files with tests.
Run all available in the given file (the file does not need to be in the current working directory.):
$ gobash test --paths src/util/strings_test.sh
./src/util/strings_test.sh 8[sec]
Tests run: 23, failed: 0, skipped: 0.
Total time: 8[sec]
Run selected tests available in the given file:
$ gobash test --paths strings_test.sh --tests test_strings_len
./src/util/strings_test.sh 783[ms]
Tests run: 1, failed: 0, skipped: 0.
Total time: 852[ms]
Note
Given value to the tests
flag is used as a value for a grep
command to filter tests of interest to run.