Bash和测试驱动开发(Bash and Test-Driven Development)

2019-07-03 12:03发布

当写比在bash一个平凡的剧本多了,我常常在想,如何使代码测试。

它通常是很难写的bash的代码测试,由于它是低上取一个值,返回值的函数,以及高对检查并设定某些方面的环境下,修改文件系统的功能,调用程序等 - 的功能依赖于环境或有副作用。 因此,安装和测试代码变得比他们测试的代码复杂得多。


例如,考虑一个简单的功能测试:

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

此功能可能包括的测试代码:

add_to_file.before:

foo
bar
baz

add_to_file.after:

bar
baz
foo
qux

和测试代码:

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
}

这里5行代码是由测试代码6行和7行数据的测试。


现在考虑一个稍微复杂的情况:

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

我甚至不能说怎么开始写一个测试...


那么,有没有做在bash脚本TDD的好方法,或者我应该放弃,把我的努力在其他地方?

Answer 1:

因此,这里是我学到了什么:

  1. 还有一些测试 框架写在bash和对于bash,但是...

  2. 这与其说是击不适合TDD(虽然一些其他的语言来记,是一个更好的配合),但猛砸用于(安装,系统配置)的典型任务,是很难写出试验,在特别硬来设置测试。

  3. 在击所述差数据结构支撑使得它很难分离从副作用的逻辑,并且实际上通常有在Bash脚本小逻辑。 这使得它很难打破脚本到可测试块。 有一些可测试的功能,但那是例外,而不是规则。

  4. 功能是一件好事(TM),但他们只能走这么远。

  5. 嵌套功能可以更好,但他们也很有限。

  6. 在一天结束的时候,有重大努力一些报道可以得到,但将测试码的更少有趣的部分,并且将保持大部分的测试作为一个好(或坏)古老的手工测试。

元:我决定回答(接受)我自己的问题,因为我无法之间做出选择思南Ünür的 (投票上)和mouviciel的 (投票了)回答说,这里同样有用和有见地的。 我要指出斯特凡诺博里尼的答案,虽然不是给我的印象最初,我学到了时间去欣赏它。 也是他设计模式或最佳实践的shell脚本回答 (最多投票)项以上是有益的。



Answer 2:

如果你是在同一时间与测试编写代码,尽量做到高在不使用任何东西,除了自己的参数,不改变环境的功能。 也就是说,如果你的功能可能会在一个子shell,以及运行,那么这将是容易测试。 这需要一些参数和输出的东西到标准输出,或文件,或者也许它在系统上的东西,但调用者不感到副作用。

是的,你将最终的传承一些WORKING_DIR变量,它很可能会成为全球功能大连锁,但是这是比较跟踪什么呢每个功能读取和修改的任务小小的不便。 启用单元测试仅仅是一个自由的奖金了。

尽量减少你需要输出的情况。 有一点子shell滥用会去很长的路要走,以保持事物很好地分离(以性能为代价)。

取而代之的线性结构,其中函数的调用,设置一些环境中,那么其他的叫,都在一个水平差不多的,尝试去用最小的数据可以追溯到深呼树。 在bash返回的东西是不方便,如果你采用从全局变量自我强加的禁欲...



Answer 3:

从观看的实施点,我建议shUnit 。

从实用的角度来看,我建议不要放弃。 我使用的TDD上的bash脚本,我确认它是值得的。

当然,我得到两次测试的多行比的代码,但与复杂的脚本,在测试工作是一个很好的投资。 当你的客户改变主意附近的项目结束,并修改了一些要求,这是特别真实。 有一个回归测试套件是在不断变化的复杂的bash代码很大帮助。



Answer 4:

如果你的代码足够大,一个bash程序需要TDD,你使用了错误的语言。

我建议你阅读我以前在bash编程最佳实践后,你可能会找到让您的bash程序可测试非常有用,但我上面的说法保持。

设计模式或shell脚本的最佳实践



Answer 5:

写什么梅萨罗斯呼吁消费者测试中的任何一种语言是很难的。 另一种方法是手动验证诸如rsync的命令的行为,那么写单元测试,以证明特定的功能没有击中该网络。 在此略变形例,$运行用于打印的副作用,如果脚本与关键字“test”运行

function distribute {
    local file=$1 ; shift
    for host in $@ ; do
        $run rsync -ae ssh $file $host:$file
    done
}

if [[ $1 == "test" ]]; then
    run="echo"
else
    distribute schedule.txt $*
    exit 0
fi

#    
# Built-in self-tests
#

output=$(mktemp)
expected=$(mktemp)
set -e
trap "rm $got $expected" EXIT

distribute schedule.txt login1 login2 > $output
cat << EOF > $expected
rsync -ae ssh schedule.txt login1:schedule.txt
rsync -ae ssh schedule.txt login2:schedule.txt
EOF
diff $output $expected
echo -n '.'

echo; echo "PASS"


Answer 6:

你可能想看看黄瓜/阿鲁巴。 做了相当不错的工作对我来说。

此外,您可以存根只是你想要做这样的事情的一切:

#
# code.sh
#
some_function_calling_some_external_binary()
{
    if ! external_binary action_1; then
        # ...
    fi  

    if ! external_binary action_2; then
        # ...
    fi
}

#
# test.sh
#

# now for the test, simply stub your external binary:
external_binary()
{
    if [ "$@" = "action_1" ]; then
        # stub action_1

    elif [ "$@" = "action_2" ]; then
        # stub action_2

    else
        external_binary $@
    fi  
}


Answer 7:

在先进的bash脚本编程指南有一个assert函数的例子,但这里是一个更简单,更灵活的断言功能-只需使用的$ EVAL *测试任何条件。

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和来电的bash内置的是bash shell的具体。



Answer 8:

看看Outthentic框架-它的目的是创造它运行的任何bash代码情景,然后使用正式DSL分析标准输出,这是很容易在这个工具来构建任何TDD /黑盒测试套件。



文章来源: Bash and Test-Driven Development
标签: bash tdd