I wanted to impress on my colleagues that BASH was still hip, still relevant. And that I wasn't an old hacker. So I wrote a small BDD test framework for BASH.
Techniques
Fluent coding relies on several BASH features:
- Variable expansion happens before executing commands
- A shell function is indistinguishable from a program: they are called the same way
- Local function variables are dynamically scoped but only within a function, so are visible to other functions called within that scope, directly or indirectly through further function calls
Together with Builder pattern, it's easy to write given/when/then tests. (Builder pattern here solves the problem not of telescoping constructors, but massive, arbitrary argument lists.)
So when you run:
function c { echo "$message" } function b { "$@" } function a { local message="$1" shift "$@" } a "Bob's your uncle" b c
You see the output:
Bob's your uncle
How does this work?
First BASH expands variables. In function a
this means that after the first argument is remembered and removed from the argument list, "$@"
expands to b c
. Then b c
is executed.
Then BASH calls the function b
with argument "c". Similarly b
expands "$@"
to c
and calls it.
Finally as $message
is visible in functions called by a
, c
prints the first argument passed to a
(as it was remebered in the variable $message
), or "Bob's your uncle" in this example.
Running the snippet with xtrace makes this clear (assuming the snippet is saved in a file named example
):
bash -x example + a 'Bob'\''s your uncle' b c + local 'message=Bob'\''s your uncle' + shift + b c + c + echo 'Bob'\''s your uncle' Bob's your uncle
So the test functions for given_jar
, when_run
and then_expect
(along with other, similar functions) work the same way. Keep this in mind.
Application
So how does this buy me fluent BDD?
Given these functions:
function then_expect { local expectation="$1" shift if [[ some_test "$pre_condition" "$condition" "$expectation" ]] then echo "PASS: $scenario" return 0 else echo "FAIL: $scenario" return 1 fi } function when { local condition="$1" shift "$@" } function given { local pre_condition="$1" shift "$@" } function scenario { local scenario="$1" shift "$@" }
When you write:
scenario "Some test case" \ given "Something always true" \ when "Something you want to test" \ then_expect "Some outcome"
Then it executes:
some_test "Something always true" "Something you want to test" "Some outcome"
And prints one of:
PASS Some test case FAIL Some test case
A real example at https://github.com/binkley/shell/tree/master/testing-bash.