How To Build A Simple Testing Framework with Bash

A quick run-through

Asa LeHolland
Modules&Methods
3 min readDec 19, 2022

--

I love working with testing frameworks (Jest and Pytest are my current faves), but I do sometimes feel reliant on them. Occasionally, like when I’m dusting off an old, untested legacy codebase and just trying to get it to run, or when I’m starting a new project and haven’t set up a testing framework yet, I like to default to the good ol’ shell.

This article explains an approach to using bash to create a simple testing framework and provides examples of test functions that can be used to verify the presence and contents of files, check that certain programs are installed and functioning properly, and perform other types of checks.

The Framework

The core unit of this is a simple run_test helper function that takes a test function as an argument and runs it. The function prints a message indicating that it is running the test and then calls the test function. If the test function returns a failure status, the run_test function prints a message indicating that the test has failed and returns a failure status. If the test function returns a success status, the run_test function prints a message indicating that the test has passed and returns a success status.

run_test() {
local test_name=$1
echo "Testing $test_name"
if ! $test_name; then
echo "--- Failed: $test_name"
return 1
fi
echo "- Passed"
return 0
}

Building on that base unit, we can define a run_all_local_tests function that runs all of the test functions defined in the script via grep, and a main function that calls the run_all_local_tests function and reports the number of tests that have failed. If no tests have failed, the main function prints a message indicating that all tests have passed.

run_all_local_tests() {
local error_count=0
for test_name in $(declare -F | grep test_ | cut -d " " -f 3); do
run_test $test_name
error_count=$((error_count + $?))
done
return $error_count
}


main() {
local error_count=0
run_all_local_tests
error_count=$?
if [ $error_count -eq 0 ]; then
echo "All tests passed"
else
echo "$error_count tests failed"
fi
}

main

That’s it! Your framework is complete.

The Tests

Finally, with our shell testing infrastructure in place, we can add our tests, which must only start with our test_ prefix. For example, we could add a simple test that should prove that a .env is present in our project directory:

test_env_file_should_exist() {
if [ ! -f ".env" ]; then
echo "Error: .env file not found"
return 1
fi
return 0
}

We can verify that certain files are included in our .gitignore :

test_env_file_should_be_ignored() {
if ! grep -q ".env" .gitignore; then
echo "Error: .env file is not listed on .gitignore"
return 1
fi
return 0
}

test_log_files_should_be_ignored() {
if ! grep -q "*.log" .gitignore; then
echo "Error: *.log files are not listed on .gitignore"
return 1
fi
return 0
}

Through the use of grep we can test that a .env contains all the required variables for our project:

test_env_should_contain_required_variables() {
local required_variables=("POSTGRES_USER" "POSTGRES_PASSWORD" "POSTGRES_DB" "POSTGRES_HOST" "POSTGRES_PORT" "POSTGRES_TEST_DB")
local is_error=0
for variable in ${required_variables[@]}; do
if ! grep -q "$variable" .env; then
echo "Error: $variable is not defined on .env file"
is_error=1
fi
done
if [ $is_error -eq 1 ]; then
return 1
fi
return 0
}

Or we could even run more complex tests to verify that other tools, such as Docker, are installed and working properly:

test_docker_should_be_installed() {
if ! command -v docker &> /dev/null; then
return 1
fi
return 0
}

test_docker_should_run() {
docker run hello-world &> /dev/null
if [ $? -ne 0 ]; then
return 1
fi
docker stop $(docker ps -a -q) &> /dev/null
return 0
}

This can, of course, be expanded, but hopefully, this gives you an idea of how it can be helpful to have a simple testing framework like the one described to quickly write and run tests without the need for a full-featured testing library.

Wrapping up

By using these scripts and following best practices for testing, you can ensure that your code is working correctly and is ready for further development or deployment.

Even if you are using a more advanced testing framework like Jest or Pytest, having a simple testing script like the one described here can be a useful tool to have in your toolkit.

If you have any comments or suggestions, let me know!

--

--