Programming/Bash/Unit Testing
For UnitTesting in bash, use Bats.
Bats is a Bash UnitTesting framework, that's imported as a Git Submodule.
Installing Bats Within a Project
Effectively, first add the submodule:
git submodule add https://github.com/sstephenson/bats tests/libs/bats git submodule add https://github.com/ztombol/bats-assert tests/libs/bats-assert git submodule add https://github.com/ztombol/bats-support tests/libs/bats-support git commit
Using Bats
Because it's not officially supported, and instead imported as a git submodule, initially calling Bats isn't quite straight forward.
From what I've seen, documentation on the web is inconsistent in regards to calling bats, so I document here what I found to work best for me:
1) Create a new test script in your project. Maybe something like <project_root>/tests/test.sh
2) Add execution permissions to this test script.
chmod +x <project_root>/tests/test.sh
3) In the first line of the script, prefix it with the standard #!/usr/bin/env bash
header.
4) As the very first line of code to execute, set the command:
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
This will set the scripts internal directory to the folder it exists in, regardless of where the terminal is upon calling it.
5) At this point, create one or more separate test files, group related tests together.
6) Call these subfiles them from your main tests.sh
file via the command:
<path_to_local_bats> <test_file_to_call>
For example, using the directions listed on this page, I have my tests.sh
at <project_root>/tests/test.sh>
. I have the bats submodule at <project_root>/tests/libs/bats/bin/bats
. Thus I can call a hypothetical <project_root>/tests/my_tests.sh
file with:
./libs/bats/bin/bats ./my_tests.sh
If done correctly, all tests in that file should execute. Repeat for every test file you wish to run.
For added clarification, add echo statements between file imports, in order to visually split up test output.
Functions
To initialize functions that run at the start and end of every test with:
### # This will run at the start of every test. ## setup () { # Setup logic here. } ### # This will run at the end of every test. ## teardown () { # Teardown logic here. }
Create actual tests with:
@test "<name_of_test>" { # Test logic here. }
Assertion Statements
Import assertion functions with:
load 'libs/bats-assert/load' load 'libs/bats-support/load'
Where the above paths are relative, based on the calling file and the location of your bats submodules.
Then, you can call the following assertion statements:
# Assert success. assert <command> # Assert failure. refute <command> # Assert two values are equal. assert_equal <val_1> <val_2> # Assert function succeeds (returns 0). run <function_call> assert_success # Assert function fails (returns non-0). run <function_call> assert_failure # Assert that a variable called ${output} matches the expected value. run <function_call> assert_output <expected_value> # Assert that a variable called ${output} does not match the expected value. run <function_call> refute_output <expected_value>
# Assert that a given line appears in "output".
assert_line <line_to_match>
# Assert that a given line does not appear in "output".
refute_line <line_to_match>
Other Assertions
It's possible to also do assertions via standard if statement syntax. Simply include the condition clause but nothing else.
Ex:
# Check if ${my_var} is equal to 5. [[ ${my_var} == 5 ]] # Check if ${my_var} does not match the string "This is a string." [[ ${my_var} != "This is a string." ]] # Check if ${some_number} is greater than 5. [[ ${some_number} > 5 ]]
If any of these checks were to fail, then the test itself would stop on the given line and return a failure.