在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编写的测试 框架,但是......
并不是说Bash不适合TDD(尽管其他一些语言更适合考虑),但是Bash用于(安装,系统配置)的典型任务很难编写测试,特别难以设置测试.
Bash中糟糕的数据结构支持使得很难将逻辑与副作用分开,实际上Bash脚本中的逻辑通常很少.这使得很难将脚本分解成可测试的块.有一些功能可以测试,但这是例外,而不是规则.
功能是一件好事(tm),但它们只能走得那么远.
嵌套函数可以更好,但它们也是有限的.
在一天结束时,通过重大努力可以获得一些覆盖,但它将测试代码中不太有趣的部分,并将大部分测试保持为良好(或糟糕)的旧手动测试.
元:我决定回答(接受)我自己的问题,因为我无法之间做出选择思南Ünür的(投票上)和mouviciel的(投票了)回答说,这里同样有用和有见地的.我想要注意Stefano Borini的回答,虽然最初没有给我留下深刻的印象,但随着时间的推移,我学会了欣赏它.另外,他的设计模式或shell脚本的最佳实践回答(投票)上面提到的是有用的.
如果您正在使用测试同时编写代码,请尝试在不使用其参数之外的任何函数的情况下使其处于高位,并且不要修改环境.也就是说,如果您的函数也可以在子shell中运行,那么它将很容易测试.它需要一些参数并输出到stdout或文件,或者它可能在系统上做某事,但调用者不会感觉到副作用.
是的,您将最终传递一些函数,这些函数传递一些可能全局的WORKING_DIR变量,但与跟踪每个函数读取和修改的内容相比,这是一个小麻烦.启用单元测试也是免费的奖励.
尽量减少需要输出的情况.一个小的子shell滥用将很长一段时间保持良好的分离(以牺牲性能为代价).
而不是线性结构,在调用函数的地方,设置一些环境,然后调用其他环境,几乎都在一个级别上,尝试使用最少的数据返回深度调用树.如果你从全球变量中采取自我禁欲,那么在bash中返回东西是不方便的......
从实现的角度来看,我建议使用shUnit.
从实际的角度来看,我建议不要放弃.我在bash脚本上使用TDD,我确认这是值得的.
当然,我获得的测试行数是代码的两倍,但是使用复杂的脚本,测试工作是一项很好的投资.特别是当您的客户在项目结束时改变主意并修改某些要求时,情况就是如此.拥有回归测试套件对于更改复杂的bash代码非常有帮助.
如果你编写一个足够大的bash程序来要求TDD,你使用的是错误的语言.
我建议你阅读我之前关于bash编程最佳实践的帖子,你可能会找到一些有用的东西来使你的bash程序可测试,但我的上述陈述仍然存在.
shell脚本的设计模式或最佳实践