Bash and Test-Driven Development

Viewed 13848

When writing more than a trivial script in bash, I often wonder how to make the code testable.

It is typically hard to write tests for bash code, due to the fact that it is low on functions that take a value and return a value, and high on functions that check and set some aspect in the environment, modify the file-system, invoke a program, etc. - functions that depend on the environment or have side effects. Thus the setup and test code become much more complicated than the code they test.


For example, consider a simple function to test:

function add_to_file() {
  local f=$1
  cat >> $f
  sort -u $f -o $f
}

Test code for this function might consist of:

add_to_file.before:

foo
bar
baz

add_to_file.after:

bar
baz
foo
qux

And test code:

function test_add_to_file() {
   cp add_to_file.{before,tmp}
   add_to_file add_to_file.tmp
   cmp add_to_file.{tmp,after} && echo pass || echo fail
   rm add_to_file.tmp
}

Here 5 lines of code are tested by 6 lines of test code and 7 lines of data.


Now consider a slightly more complicated case:

function distribute() {
   local file=$1 ; shift
   local hosts=( "$@" )
   for host in "${hosts[@]}" ; do
     rsync -ae ssh $file $host:$file
   done
}

I can't even say how to start write a test for that...


So, is there a good way to do TDD in bash scripts, or should I give up and put my efforts elsewhere?

8 Answers

The advanced bash scripting guide has an example of an assert function but here is a simpler and more flexible assert function - just use eval of $* to test any condition.

assert() {
  if ! eval $* ; then
      echo
      echo "===== Assertion failed:  \"$*\" ====="
      echo "File \"$0\", line:$LINENO line:${BASH_LINENO[*]}"
      echo line:$(caller 0)
      exit 99
  fi  
}

# e.g. USAGE:
assert [[ $r == 42 ]]
assert "((r==42))"

BASH_LINENO and caller bash builtin are bash shell specific.

take a look at Outthentic framework - it is designed to create scenarios which runs any Bash code and then analyze the stdout using formal DSL, it's pretty easy to build any Tdd/blackbox tests suite upon this tool.

Related