当写比在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的好方法,或者我应该放弃,把我的努力在其他地方?
因此,这里是我学到了什么:
还有一些测试 框架写在bash和对于bash,但是...
这与其说是击不适合TDD(虽然一些其他的语言来记,是一个更好的配合),但猛砸用于(安装,系统配置)的典型任务,是很难写出试验,在特别硬来设置测试。
在击所述差数据结构支撑使得它很难分离从副作用的逻辑,并且实际上通常有在Bash脚本小逻辑。 这使得它很难打破脚本到可测试块。 有一些可测试的功能,但那是例外,而不是规则。
功能是一件好事(TM),但他们只能走这么远。
嵌套功能可以更好,但他们也很有限。
在一天结束的时候,有重大努力一些报道可以得到,但将测试码的更少有趣的部分,并且将保持大部分的测试作为一个好(或坏)古老的手工测试。
元:我决定回答(接受)我自己的问题,因为我无法之间做出选择思南Ünür的 (投票上)和mouviciel的 (投票了)回答说,这里同样有用和有见地的。 我要指出斯特凡诺博里尼的答案,虽然不是给我的印象最初,我学到了时间去欣赏它。 也是他设计模式或最佳实践的shell脚本回答 (最多投票)项以上是有益的。
如果你是在同一时间与测试编写代码,尽量做到高在不使用任何东西,除了自己的参数,不改变环境的功能。 也就是说,如果你的功能可能会在一个子shell,以及运行,那么这将是容易测试。 这需要一些参数和输出的东西到标准输出,或文件,或者也许它在系统上的东西,但调用者不感到副作用。
是的,你将最终的传承一些WORKING_DIR变量,它很可能会成为全球功能大连锁,但是这是比较跟踪什么呢每个功能读取和修改的任务小小的不便。 启用单元测试仅仅是一个自由的奖金了。
尽量减少你需要输出的情况。 有一点子shell滥用会去很长的路要走,以保持事物很好地分离(以性能为代价)。
取而代之的线性结构,其中函数的调用,设置一些环境中,那么其他的叫,都在一个水平差不多的,尝试去用最小的数据可以追溯到深呼树。 在bash返回的东西是不方便,如果你采用从全局变量自我强加的禁欲...
从观看的实施点,我建议shUnit 。
从实用的角度来看,我建议不要放弃。 我使用的TDD上的bash脚本,我确认它是值得的。
当然,我得到两次测试的多行比的代码,但与复杂的脚本,在测试工作是一个很好的投资。 当你的客户改变主意附近的项目结束,并修改了一些要求,这是特别真实。 有一个回归测试套件是在不断变化的复杂的bash代码很大帮助。
如果你的代码足够大,一个bash程序需要TDD,你使用了错误的语言。
我建议你阅读我以前在bash编程最佳实践后,你可能会找到让您的bash程序可测试非常有用,但我上面的说法保持。
设计模式或shell脚本的最佳实践
写什么梅萨罗斯呼吁消费者测试中的任何一种语言是很难的。 另一种方法是手动验证诸如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"
你可能想看看黄瓜/阿鲁巴。 做了相当不错的工作对我来说。
此外,您可以存根只是你想要做这样的事情的一切:
#
# 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
}
在先进的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的具体。
看看Outthentic框架-它的目的是创造它运行的任何bash代码情景,然后使用正式DSL分析标准输出,这是很容易在这个工具来构建任何TDD /黑盒测试套件。