Announcing bash-spec-2

How I learned to stop worrying and love bash scripting

The horror…

It’s 3am and PagerDuty is waking you up. You just want to re-deploy an app because that’s the quickest way to get things going again and you like sleep. But wait – it turns out this deploy relies on a version of ruby you don’t have, the bundle won’t install because nokogiri is having problems and you wonder if gardening might have been a more rewarding career.

Bash scripting provides a simple way to get things done and avoids many of the above dramas. However, we hear that bash scripts are ok for really short things that can be tested manually and anything else is better left to a “Real Programming Language™” (whether ruby qualifies is left as an exercise for the reader).

bash-spec-2 to the rescue. With this nifty little testing framework you can TDD your way to a set of comprehensible, documented functions.

BDD your way to bashy happiness (bashiness?)

There was already a decent xunit style framework to unit test bash. I have found, however, that the one thing I miss most whenever I go from ruby to something else like c# is rspec. The ability to actually describe and document behaviour at the level necessary to do TDD properly is priceless.

Hunting around the web led to bash-spec, which had an “it” syntax but missed “describe” and “context” (and therefore, it also missed the point a bit). Along came hack day and with it the inspiration to make bash-spec better.

Command substitution FTW

The biggest improvement is in the syntax. Command substitution allows for nesting and therefore we can have its within contexts within describes etc. The changes to implement this were trivial and the end result is the ability to write tests like so:

#! /bin/bash
. bash-spec.sh

describe "The echo command" "$(
  
  result="$( echo "Hello there" )"

  it "Outputs the text passed to it" "$(
    expect "$result" to_be "Hello there"
  )"

  context "When we include a variable in the input" "$(

    my_name="James"
    result=$( echo "Hello $my_name" )

    it "Happily outputs the variable" "$(
      expect "$result" to_be "Hello James"
    )"

  )"

)"

Yes, that’s a bash script. Seriously.

Also worth noting is that command substitution spawns subshells. This is very powerful, since any changes in the subshell do not affect the parent shell or other subshells. Tests are therefore independent of each other (at least until they write files or have some other side effect).

This is also a double edged sword, since it means that test counts, failure counts etc can’t be passed back to the calling shell via variables. The brute force way around this is to redirect STDOUT to a file for the duration of the test run, swap it back at the end, do some processing on the output and make decisions on exit codes at that point. I’d like to see something more elegant, but it works.

Mocking

At some point you might want to mock (especially for integration testing). bash-spec-2 plays nicely with stub.sh. For example, I’ve found this useful making sure that AWS command lines are built correctly from parameters, by stubbing the AWS command and then checking the call with an assertion. That was the main reason to add a new matcher to bash-spec-2: “to_be_true”

function start_up {
  local instance_id=$1
  aws ec2 start-instances --instance-ids $instance_id
}

describe "#start_up" "$(

  stub aws

  start_up an_instance

  it "Starts the instance passed to it" "$(
    expect to_be_true stub_called_with aws ec2 start-instances --instance-ids an_instance
  )"

)"

Next steps

Hack day is only so long and there are some nice to haves that didn’t get haved.

  • Proper output nesting would be nice. Some nesting is currently done, but only one level deep.
  • A version of the let syntax would be useful to help DRY up tests.

So there’s more work to do, but it’s better to have something out in the wild gathering feedback and being shaped by real needs, rather than guessing what might be necessary. Please clone it, use it, abuse it and send issues and pull requests:

https://github.com/realestate-com-au/bash-spec-2