Unit testing Bash
Xunit tests has been the most commonly and I dare-say most widely used testing structure that exists, and it didn’t surprise me to find that there is a framework for bash scripts in this form, namely shunit2.
ShUnit2 is an xunit style testing framework made to work with Bash scripts. It has a very simple setup process on my systems (especially on debian based systems).
To install shUnit2 on a debian based system you only need to run the below command.
apt-get install shunit2
If everything is setup correctly you should see output similiar to that below.
Ran 0 tests.
You can also manually install it by downloading the latest build from the Github source directly. This is actually a nice thing to pull down as it contains some useful examples for shUnit2. After pulling it you will need to add it to your path for usage.
The easiest way to start learning a framework is often to use the framework, so lets quickly jump in using an example test.
Open up your favorite text editor and enter the code defined below.
Now to run this test you will need to make a few changes to the file, which for this case lets have the file called my-first-test.sh
$ chmod +x my-first-test.sh
Ran 1 tests.
If this doesn’t work, check a couple of things that might be the cause.
- shunit2 should be in the path
- function name starts with lowercase ‘test’
- assertTrue comparison has correct spacing and quotes — “[ 1 -eq 1 ]”
One of the most important part of tests is making sure that the test is descriptive in what it is trying to accomplish. To help achieve this these xUnit frameworks will often include a number of different assertions that can help to make the code more readable.
This is the list of current assertions as of version 2.1.6
- assertEquals [message] expected actual
- assertSame [message] expected actual
- assertNotEquals [message] expected actual
- assertNotSame [message] expected actual
- assertNull [message] value
- assertNotNull [message] value
- assertTrue [message] condition
- assertFalse [message] condition
There are a couple of things to be aware of when using these assertions, especially the assertNull and assertNotNull calls. They are used to compare a null in bash which is a zero length string.
Next to assertions you also have failures that you can place in your code to automatically trigger the test to fail and execution to stop. This is a list of the different failures that are supported in shUnit2.
Note: failures are not for value comparisons, if you need this functionality use assertions
- fail [message]
- failNotEquals [message] unexpected actual
- failSame [message] expected actual
- failNotSame [message] unexpected actual
One of the common situations that you will run across with unit testing is making sure that your code is testing the smallest piece possible, which can help you quickly and easily find bugs that could show up, and give you better confidence with refactoring.
In most other languages this is accomplished using mocking frameworks (java and mockito, .net and rhinomocks). In bash I have not yet found a framework that successfully mocks calls that could be made from the bash scripts, so instead the process that I recommend is accomplished using PATH manipulation and running calls.
Lets start with a simple script that is used to pull down a sample configuration using wget and then executes the configuration. configRetriever.sh
Now that we have our test file, there are two more files that we need, the tests and also a mock of wget. So lets first start with the mock. wget
And finally the actual tests. configTests.sh
A couple of things to point out from here, I am adjusting the path before each test and I am restoring it after a test using the setUp() and tearDown(). It is important to make sure that you are forcing a clean slate for each test, so you don’t get false negatives (or positives for that matter).
The next important thing to see is how to hide or handle output from the running script, you should do this otherwise you might find it hard to find the alerts from test outputs that are run.
So after getting my feet a little wet with shUnit2 and using path manipulation to handle script internal calls, I have to say that I think bash unit testing is much easier than I had originally figured and am excited to move forward with testing my scripts.