Testing bash scripts
A few years ago, I was looking into the idea of writing unit testing for bash scripts, and that's when I stumbled across a project called bats
.
Coming from a Perl background, It caught my attention due to its use of the TAP protocol (Test Anything Protocol). However the opensource project didn't seem active and also my needs and attention have also shifted elsewhere.
Doing a similar search this week led me to a Stack Overflow answer that refered to a blog post:
and went on to mention that BATS has a maintained fork nowadays: bats-core
That's good news. The only things I need to find out now is how to export functions out of a bash script. Because my plan is to move the core functionality into a function, which I export into a wrapper script for normal operation, and into a test script so I can make test assertions using the aforementioned test framework.
This was answered on another Stack Overflow page, so that we can do something like this:
Setup and real example
There are various ways to install bats-core
in a project. On a mac, an easy way is using homebrew.
$ brew install bats-core
Here is something about a new project I'm working on: There is a bug, I created a GitHub issue for it, I have reproduced the bug locally, and then I wrote a test with bats-core
:
Then I run the suite:
$ bats tests
bug-19-homepage-link-empty.bats
✗ link to homepage on other page is not empty
(in test file tests/bug-19-homepage-link-empty.bats, line 6)
`[[ $link =~ http://localhost:9999 ]]' failed
Creating ghost-ssg_export_run ...
Creating ghost-ssg_export_run ... done
pages_repo_setup.bats
- noop if local repo exists (skipped: wrong branch)
2 tests, 1 failure, 1 skipped
That's good, because TDD tells us that we should start with a failing test (it's actually red colored on my terminal). You can notice another test, pages_repo_setup.bats
is present, which is a new test I created for another issue but I am on the wrong branch for it, since I'm focused on this bug, so I marked it to be skipped.
Then, after I've fixed the bug, I can run the test suite again:
$ bats tests
bug-19-homepage-link-empty.bats
✓ link to homepage on other page is not empty
pages_repo_setup.bats
- noop if local repo exists (skipped: wrong branch)
2 tests, 0 failures, 1 skipped
Good, the test is now green!
Just so you get an idea how it works with a code organisation I sketched at the beginning, I'll show you the other test (the skipped one):
#!/usr/bin/env bats
source ./src/lib/repo_setup.sh
teardown () {
if [ -f "tests/gitlab" ];then
rmdir "tests/gitlab"
fi
}
@test "noop if local repo exists" {
skip "wrong branch"
baseDir="tests"
mkdir "$baseDir/gitlab"
existingRemoteRepo="https://gitlab.com/user/myblog.git"
create_dir_for_repo $baseDir $existingRemoteRepo
[ $? -eq 0 ]
}
Here, the code being tested is the create_dir_for_repo()
function defined in the file src/lib/repo_setup.sh
which will be used in the main script too. You can also see from the two example test cases, that you have the traditional setup()
and teardown()
functions at your disposal, and from a cursory look at the documentation, I noticed other features that I'm used to see in test frameworks for "proper" programming languages.
Finally, here's a partial view on the directory strucure of the project:
├── src
│ └── lib
│ └── repo_setup.sh
├── stage
├── tests
│ ├── bug-19-homepage-link-empty.bats
│ └── pages_repo_setup.bats
├── up
├── versions
└── whatsup
The tests
parameter that was passed to the bats
command is the directory where the tests are located.
Finally, we look into setting up continuous integration for this project. Using GitLab as CI server, here's the configuration to get this working (notice how we use another method - NPM - to install bats-core
):
Then we can check the GitLab pipeline log:
...
$ bats tests
1..1
ok 1 link to homepage on other page is not empty
Cleaning up project directory and file based variables 00:00
Job succeeded