单元测试bash脚本

时间:2009-08-27 07:33:47

标签: bash testing tdd automated-tests extreme-programming

我们有一个系统除了Java代码外还有一些运行bash脚本。由于我们正在尝试测试可能会破坏的所有内容,并且这些bash脚本可能会中断,我们希望对它们进行测试。

问题是很难测试bash脚本。

有没有办法或最佳做法来测试bash脚本?或者我们应该退出使用bash脚本并寻找可测试的替代解决方案吗?

17 个答案:

答案 0 :(得分:42)

实际上有一个shunit2,一个基于xUnit的单元测试框架,用于基于Bourne的shell脚本。我自己没有用过,但可能值得一试。

以前曾提出过类似的问题:

答案 1 :(得分:25)

我从讨论组得到了以下答案:

  

可以导入(包括,   无论如何)一个程序(功能,   无论它的名字来自外部   文件。这是写作的关键   测试脚本:你分手了   脚本进入独立程序   然后可以导入到两者中   你的运行脚本和你的测试   脚本,然后你运行   脚本尽可能简单。

这种方法就像脚本的依赖注入一样,听起来很合理。最好避免使用bash脚本并使用更易测试且不那么模糊的语言。

答案 2 :(得分:23)

TAP - 符合Bash测试: Bash Automated Testing System

  测试任务协议TAP是测试工具中测试模块之间的简单基于文本的界面。 TAP最初是Perl测试工具的一部分,但现在有C,C ++,Python,PHP,Perl,Java,JavaScript等实现。

答案 3 :(得分:7)

Epoxy是我设计的Bash测试框架,主要用于测试其他软件,但我也用它来测试bash模块,包括itselfCarton

主要优点是编码开销相对较低,无限制断言嵌套以及灵活选择断言以进行验证。

我将presentationBeakerLib进行了比较 - 这是红帽的一些人使用的框架。

答案 4 :(得分:6)

为什么你说测试bash脚本“很难”?

测试包装器有什么问题:

 #!/bin/bash
 set -e
 errors=0
 results=$($script_under_test $args<<ENDTSTDATA
 # inputs
 # go
 # here
 #
 ENDTSTDATA
 )
 [ "$?" -ne 0 ] || {
     echo "Test returned error code $?" 2>&1
     let errors+=1
     }

 echo "$results" | grep -q $expected1 || {
      echo "Test Failed.  Expected $expected1"
      let errors+=1
 }
 # and so on, et cetera, ad infinitum, ad nauseum
 [ "$errors" -gt 0 ] && {
      echo "There were $errors errors found"
      exit 1
 }

答案 5 :(得分:5)

Nikita Sobolev撰写了一篇精彩的博客文章,比较了一些不同的bash测试框架:Testing Bash applications

对于不耐烦的人:Nikita的结论是使用Bats但是看起来Nikita错过了Bats-core项目,在我看来这是一个可以使用的项目,因为原来的Bats项目还没有自2013年以来积极维护。

答案 6 :(得分:3)

我非常喜欢shell2junit,一个从Bash脚本测试生成类似JUnit的输出的实用程序。这很有用,因为生成的报告可以由持续集成系统读取,例如Jenkins和Bamboo的JUnit插件。

虽然shell2junit不提供像shunit2这样全面的Bash脚本框架,但它确实可以很好地报告测试结果。

答案 7 :(得分:3)

试试bashtest。这是测试脚本的简单方法。例如,您有do-some-work.sh更改了一些配置文件。例如,将新行PASSWORD = 'XXXXX'添加到配置文件/etc/my.cfg

您逐行编写bash命令,然后检查输出。

安装:

pip3 install bashtest

创建测试只是编写bash命令。

档案test-do-some-work.bashtest

# run the script  
$ ./do-some-work.sh > /dev/null

# testing that the line "PASSWORD = 'XXXXX'" is in the file /etc/my.cfg   
$ grep -Fxq "PASSWORD = 'XXXXX'" /etc/my.cfg && echo "YES"
YES

运行测试:

bashtest *.bashtest

您可以找到some examples herehere

答案 8 :(得分:3)

也许这可以用于

https://thorsteinssonh.github.io/bash_test_tools/

打算在TAP协议中写入结果,我认为它对CI有好处,对那些想要shell环境的人有好处。我想有些东西在shell环境中运行,所以有些人可能认为应该在shell环境中进行测试。

答案 9 :(得分:3)

尝试assert.sh

source "./assert.sh"

local expected actual
expected="Hello"
actual="World!"
assert_eq "$expected" "$actual" "not equivalent!"
# => x Hello == World :: not equivalent!

希望它有所帮助!

答案 10 :(得分:1)

我创建了shellspec是因为我想要一个易于使用且有用的工具。

它是由纯POSIX shell脚本编写的。它比shunit2对许多外壳进行了更多测试。它具有比bats / bats-core强大的功能。

例如,支持嵌套块,易于模拟/存根,易于跳过/挂起,声明行号,按行号执行,并行执行,随机执行,TAP / JUnit格式化程序,覆盖率和CI集成,探查器等

请参阅项目页面上的演示。

答案 11 :(得分:1)

我不敢相信没有人谈论OSHT!它与 TAP和JUnit都兼容,它是纯外壳程序(也就是说,不涉及其他语言),它也可以独立工作,并且简单直接。

测试看起来像这样(摘录自项目页面):

#!/bin/bash
. osht.sh

# Optionally, indicate number of tests to safeguard against abnormal exits
PLAN 13

# Comparing stuff
IS $(whoami) != root
var="foobar"
IS "$var" =~ foo
ISNT "$var" == foo

# test(1)-based tests
OK -f /etc/passwd
NOK -w /etc/passwd

# Running stuff
# Check exit code
RUNS true
NRUNS false

# Check stdio/stdout/stderr
RUNS echo -e 'foo\nbar\nbaz'
GREP bar
OGREP bar
NEGREP . # verify empty

# diff output
DIFF <<EOF
foo
bar
baz
EOF

# TODO and SKIP
TODO RUNS false
SKIP test $(uname -s) == Darwin

简单运行:

$ bash test.sh
1..13
ok 1 - IS $(whoami) != root
ok 2 - IS "$var" =~ foo
ok 3 - ISNT "$var" == foo
ok 4 - OK -f /etc/passwd
ok 5 - NOK -w /etc/passwd
ok 6 - RUNS true
ok 7 - NRUNS false
ok 8 - RUNS echo -e 'foo\nbar\nbaz'
ok 9 - GREP bar
ok 10 - OGREP bar
ok 11 - NEGREP . # verify empty
ok 12 - DIFF <<EOF
not ok 13 - TODO RUNS false # TODO Test Know to fail

最后一个测试显示为“不正常”,但是退出代码为0,因为它是TODO。也可以设置冗长:

$ OSHT_VERBOSE=1 bash test.sh # Or -v
1..13
# dcsobral \!= root
ok 1 - IS $(whoami) != root
# foobar =\~ foo
ok 2 - IS "$var" =~ foo
# \! foobar == foo
ok 3 - ISNT "$var" == foo
# test -f /etc/passwd
ok 4 - OK -f /etc/passwd
# test \! -w /etc/passwd
ok 5 - NOK -w /etc/passwd
# RUNNING: true
# STATUS: 0
# STDIO <<EOM
# EOM
ok 6 - RUNS true
# RUNNING: false
# STATUS: 1
# STDIO <<EOM
# EOM
ok 7 - NRUNS false
# RUNNING: echo -e foo\\nbar\\nbaz
# STATUS: 0
# STDIO <<EOM
# foo
# bar
# baz
# EOM
ok 8 - RUNS echo -e 'foo\nbar\nbaz'
# grep -q bar
ok 9 - GREP bar
# grep -q bar
ok 10 - OGREP bar
# \! grep -q .
ok 11 - NEGREP . # verify empty
ok 12 - DIFF <<EOF
# RUNNING: false
# STATUS: 1
# STDIO <<EOM
# EOM
not ok 13 - TODO RUNS false # TODO Test Know to fail

重命名它以使用.t扩展名并将其放在t子目录中,您可以使用prove(1)(Perl的一部分)来运行它:

$ prove
t/test.t .. ok
All tests successful.
Files=1, Tests=13,  0 wallclock secs ( 0.03 usr  0.01 sys +  0.11 cusr  0.16 csys =  0.31 CPU)
Result: PASS

设置OSHT_JUNIT或传递-j产生JUnit输出。 JUnit也可以与prove(1)组合。

我已经使用了该库来测试两个功能,即通过获取它们的文件,然后使用IS / OK和它们的负数来运行断言,以及使用RUN / NRUN来运行脚本。对我来说,此框架以最少的开销提供了最大的收益。

答案 12 :(得分:0)

创建一个测试 mytest.sh。它使用特定输入调用您的脚本。

创建一个文本文件 expected/mytest.stdout。它包含测试的预期输出。

将它们提交到版本控制(如果有)。

运行测试并将输出重定向到另一个文件:

mytest.sh > actual/mytest.stdout

然后只是我们一个文本差异工具来查看结果偏离的地方。我认为你可以这样做

diff expected/mytest.stdout actual/mytest.stdout

答案 13 :(得分:0)

我尝试了此处介绍的许多解决方案,但是发现其中大多数解决方案体积庞大且难以使用,因此我建立了自己的小型测试框架:https://github.com/meonlol/t-bash

只有一个基本的JUnit样式断言,您可以直接运行该仓库中的一个文件。

我已经在多个内部项目中专业地使用过它,并且能够使我们的bash脚本具有超强的稳定性和抗回归性。

答案 14 :(得分:0)

看看Outthentic,它很简单,可以通过多种语言(可选Perl,Python,Ruby,Bash)和跨平台(Linux,Windows)框架进行扩展,以测试任何命令行应用程序。

答案 15 :(得分:0)

你可能想看看bash_unit:

https://github.com/pgrange/bash_unit

答案 16 :(得分:-3)

Python 具有如此巨大的优势时,我发现很难证明使用bash来处理更大的脚本:

  • Try / Except允许编写更强大的脚本,并能够在发生错误时撤消更改。
  • 您不必使用容易出错的“if [ x"$foo" = x"$bar"]; then ...”之类的模糊语法。
  • 使用getopt模块轻松解析选项和参数(并且有一个更简单的模块来解析参数,但名称让我感到厌烦。)
  • Python允许您使用列表/ dicts和对象而不是基本字符串和数组。
  • 访问正确的语言工具,如正则表达式,数据库(确保你可以在bash中将所有内容都输入mysql命令,但这不是编写代码的最好方法。)
  • 无需担心使用正确形式的$*"$*""$@"$1"$1",文件名中的空格不是问题等等。

现在我只使用bash作为最简单的脚本。