Tests for private methods

I’ve been thinking about this recently as I’ve been writing my own test runner for shell scripts.

I have found myself decomposing longer methods into shorter, private ones (thinking ruby also here), but I’ve found that during the refactor of red-green-refactor I’ve not been using unit tests to ‘drive’ out the code that is in these private methods.

After reflecting on this and reading  http://programmers.stackexchange.com/questions/279117/tdd-red-green-refactor-and-if-how-to-test-methods-that-become-private I think the approach I’ve been using of testing the public interface is ok.  A significant caveat worth remembering is in the answer

http://programmers.stackexchange.com/a/279118/34069

is “if your class that does one thing contains extensive code for doing another thing that is required for, but separate from, its primary purpose, that code should live in another class”

TDD for sh

#!/usr/bin/env bash
. colors_for_text.sh 2> /dev/null

do_test () {
  if [[ $# -eq 0 ]]; then
    printf "$fail_color Error - do_test: No parameters provided - Usage is do_test [function_name] \$LINENO [expected result] [optional params]\n"
    exit 1
  elif [[ $# -eq 1 ]]; then
    printf "$fail_color Error - do_test: Two parameters missing - \$LINENO and expected result (other params as needed)\n"
    exit 1
  fi
  declare -ig test_runs test_passes test_fails
  local -r function="$1";shift
  local -r line_number="$1";shift
  local failfast="False"
  local debug="False"
  local verbose="False"
  local test_description=
  local test_matcher=
  local expected=
  local pass_on_params=
  local command_not_found=False
  type "setup" &> /dev/null && setup
  # TODO Replace with for loop (initial attempts failed)
  [[ "$1" == "failfast" ]] && failfast="True" && shift
  [[ "$1" == "debug" ]] && debug="True" && shift
  [[ "$1" == "verbose" ]] && verbose="True" && shift
  [[ "$1" == "failfast" ]] && failfast="True" && shift
  [[ "$1" == "debug" ]] && debug="True" && shift
  [[ "$1" == "failfast" ]] && failfast="True" && shift
  if [[ "$1" =~ ^description= ]]; then
    [[ `echo $1 | grep =` ]] && test_description=`echo $1 | sed 's/.*=//'`' ' &&
    shift
  fi
  if [[ "$1" =~ ^matcher= ]]; then
    [[ `echo "$1" | grep =` ]] && test_matcher="`echo "$1" | sed 's/matcher=//'`" &&
    shift
  fi
  if [[ $# -eq 0 ]]; then
    printf "$fail_color Error - Function: $function, Line: $line_number \n
do_test: Third parameter missing - expected result\n"
    exit 1
  fi
  expected="$1";shift
  pass_on_params="$@"
  $function ${pass_on_params[@]} 2> $TMPDIR"/output$$-"$test_runs".txt"
  cat $TMPDIR"/output$$-"$test_runs".txt" | grep -i "command not found" &&
    command_not_found=True
  debug_printf "(debug)"
  if [[ $command_not_found == "True" ]]; then
    record_script_failure "$function"
  elif [[ "$test_matcher" == "==" ]]; then
    [[ "$result" == $expected ]] &&
    record_test_success "$function" ||
    record_test_failure "$function";
  elif [[ "$expected" =~ ^-?[0-9]+$ ]]; then
    [[ "$result" -eq $expected ]] &&
    record_test_success "$function" ||
    record_test_failure "$function";
  else
    [[ "$result" == $expected ]] &&
    record_test_success "$function" ||
    record_test_failure "$function";
  fi
  test_runs+=1
}
record_test_success () {
  [[ $# -eq 0 ]] && no_param_quit $FUNCNAME
  local -r function="$1"
  printf "$pass_color"."$color_end"
  [[ "$verbose" == "True" ]] && printf "$function"
  test_passes+=1
}
record_test_failure () {
  [[ $# -eq 0 ]] && no_param_quit $FUNCNAME
  local -r function="$1"
  test_fails+=1
  printf "$fail_color"F"$color_end"
  function_exists? "$function" &&
    error_messages=$error_messages"$fail_color""$test_description""Line: $line_number - \
'$function ${pass_on_params[@]}' failed:\n\
Expected: $expected\n\
Received: $result $color_end\n\n" ||
    error_messages=$error_messages"$fail_color""Line: $line_number: \
Function '$function' is undefined\n"
  [[ "$failfast" == "True" ]] && failfast "Test" $error_messages
}
record_script_failure () {
  [[ $# -eq 0 ]] && no_param_quit $FUNCNAME
  local -r function="$1"
  test_fails+=1
  err=`cat $TMPDIR"/output$$-"$test_runs".txt"`
  printf $err
  error_messages=$error_messages"$fail_color""Line: $line_number: \
Error within function '$function', getting \"command not found\" in $err \n"
  [[ "$failfast" == "True" ]] && failfast "Script" $error_messages
}
failfast () {
  printf "\n$1 error & failfast set to exit immediately.\n$2\n" && exit 1
}

no_param_quit () {
  printf "\nError - 1 parameter required for: $1 - exiting\n" && exit 1
}
function_exists?() {
    declare -f -F $1 > /dev/null
    return $?
}
debug_echo () {
  [[ "$debug" == "True" ]] && echo "$1"
}
debug_printf () {
  [[ "$debug" == "True" ]] && printf "$1"
}
13:11:33 durrantm Castle2012 /home/durrantm/Dropbox/_/sh__sed__awk/euler 
$

Usage:
Screenshot from 2015-04-02 13:25:18