Why?

Of course the full question should be:
Why is unit testing for shell scripts important?

The simple answer to that is:
for all the same reasons that testing in general is important and even results in a whole process template like the Test Driven Development
A more complex answer is:
  • Reap the benefits of Shift Left Testing
    • Early detection of defects
    • Easier debugging through implicit documentation of expected behaviour
    • Extending the code coverage to include the deployment logic
    • Reduce technical debt at an early stage
  • Saving money:
    catching bugs early on, will save costs in QA and customer support
  • Reliable code that is easy to reuse
In a nutshell: shell scripts deserve the same love that we grant our code in general and implementing unit testing will provide many benefits.

Easy to get started

Once shunit2 is installed, writing tests is as easy as

#!/bin/sh

testForOneEqualsOne() {
  assertTrue "Someone has changed reality, suddenly 1 does not equal 1!" "[[ 1 -eq 1 ]]"
  return 0
}

testThatWillFail() {
  assertFalse "Failing as expected, your reality is fine :-)" "[[ 1 -eq 1 ]]"
  return 0
}

. shunit2
						
So we have a script with 3 parts:
  1. The shebang to select which shell interpreter to use
  2. A collection of tests, likely doing some assertion
  3. A call to shunit2 at the end of the script
And the result could look like
Run Example Image
At https://github.com/kward/shunit2#asserts you will find all available asserts, as of right now (2022-10-31) we have:

  • Simple value comparison with assertEquals, assertNotEquals, assertSame and assertNotSame
  • A very versatile check using the expression syntax of the shell interpreter with assertTrue and assertFalse
  • Substring checking with assertContains and assertNotContains
  • Empty string checking with assertNull and assertNotNull

Example Scenarios

On the following pages we will look at:
  • Checking your own code :-)
  • Interoperability checks
  • Binary availability
  • Version requirements
  • Environment issues

Checking your own code

This scenario comes without example code, but with some additional reasoning.
Imagine you have a script that just does:

#!/bin/sh

echo Hello World :-)
						
Writing a test and running it, already provides the insight of:
  • The script is located correctly
  • The script is executable
  • The shebang and shell binary work
  • The echo and shunit2 command work

Interoperability checks

Even with the most simple scripts and common tools, there is always an uncertainty if something will work as expected. Especially if you do not have full control about where the code is running.

sed -i 's/hello/goodbye/' example
sed -i.bak 's/hello/goodbye/' example
						
The first line represents the GNU flavour and will do the in-file replacement without a backup file. Not compatible to the BSD flavour.
The second line will create a backup copy with the extension .bak and works for both, the GNU as well as the BSD flavour of sed.

Binary availability

This simple error cause has inflicted hours of lost time,
as sometimes the clue is hidden deep in some error logs.
Well, just write a test for it!

#!/bin/sh

testAvailabilityBASH() {
  assertTrue "Did not find the bash executable" "[[ -x "$(which 'bash')" ]]"
  return 0
}

. shunit2
						

Version requirements

There is a number of examples available at
"examples/preconditions/verify_versions.test",
but version checking is a difficult topic.

The safe route is, to check if the expected functionality is working.

As this is a quick test method and if you do have full control of the target environments, or do only checks on your own components, then it should be safe to choose this method.

Environment issues

There is a number of examples available at
"examples/preconditions/check_requirements.test".

Common causes for errors can be:
  • Wrong target architecture or operating system version
  • Bad permissions on directories and/or files
  • Not enough memory and/or disk space
  • Networking issues

To check for such errors before an installation,
or during troubleshooting can save a lot of time.