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.
No comments:
Post a Comment