140

我们有一个系统,除了 Java 代码之外还运行一些 Bash 脚本。由于我们正在尝试测试所有可能会破坏的东西,而那些 Bash 脚本可能会破坏,我们想要测试它们。

问题是很难测试 Bash 脚本。

有没有办法或最佳实践来测试 Bash 脚本?还是我们应该放弃使用 Bash 脚本并寻找可测试的替代解决方案?

4

16 回答 16

52

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

之前也有人问过类似的问题:

于 2009-08-27T07:43:18.627 回答
34

符合 TAP的 Bash 测试:Bash 自动化测试系统

TAP,即测试任何协议,是测试工具中测试模块之间基于文本的简单接口。TAP 最初是作为 Perl 测试工具的一部分,但现在已在 C、C++、Python、PHP、Perl、Java、JavaScript 等中实现。

于 2012-11-02T16:16:08.367 回答
31

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

可以从外部文件导入(包括,随便)一个过程(函数,无论它的名字是什么)。这是编写测试脚本的关键:将脚本分解为独立的程序,然后可以将它们导入到运行脚本和测试脚本中,然后让运行脚本尽可能简单。

这种方法类似于脚本的依赖注入,听起来很合理。避免使用 Bash 脚本并使用更可测试且不那么晦涩的语言是可取的。

于 2009-09-01T07:43:40.387 回答
11

Nikita Sobolev 写了一篇优秀的博客文章,比较了几个不同的 Bash 测试框架:测试 Bash 应用程序

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

于 2018-03-14T08:04:38.840 回答
7

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

主要优点是相对较低的编码开销、无限的断言嵌套和灵活选择要验证的断言。

我做了一个演示,将它与BeakerLib进行了比较——Red Hat 一些人使用的框架。

于 2013-09-28T08:59:43.490 回答
7

我不敢相信没有人谈论OSHT!它与TAP JUnit兼容,它是纯 shell(即不涉及其他语言),它也可以独立工作,而且简单直接。

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

#!/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。对我来说,这个框架以最少的开销提供了最大的收益。

于 2019-06-12T17:50:35.983 回答
7

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

它由纯 POSIX shell 脚本编写。它已经用许多 shell 进行了测试,而不是 shunit2。它具有比 bats/bats-core 更强大的功能。

例如,它支持嵌套块、易于模拟/存根、易于跳过/挂起、参数化测试、断言行号、按行号执行、并行执行、随机执行、TAP / JUnit格式化程序、覆盖率和CI集成、分析器、等等

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

于 2019-07-09T06:52:17.087 回答
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
 }
于 2009-08-27T07:56:42.283 回答
4

试试assert.sh :

source "./assert.sh"

local expected actual
expected="Hello"
actual="World!"
assert_eq "$expected" "$actual" "not equivalent!"
# => x Hello == World :: not equivalent!
于 2017-05-06T20:47:58.710 回答
3

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

虽然 shell2junit 没有像shunit2那样提供全面的 Bash 脚本框架,但它确实允许您很好地报告测试结果。

于 2014-01-17T22:33:59.923 回答
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

您可以在此处和此处找到一些示例

于 2016-07-26T05:27:48.370 回答
3

也许这可以使用,或有助于:

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

它的目的是在 TAP 协议中编写结果,我认为这对CI有好处,对那些想要 shell 环境的人也有好处。我想有些东西在 shell 环境中运行,所以有些人可能会认为应该在他们的 shell 环境中进行测试。

于 2016-08-05T17:52:36.787 回答
2

我已经尝试了很多这里介绍的解决方案,但我发现它们中的大多数都太笨重且难以使用,所以我构建了自己的小测试框架:https ://github.com/meonlol/t-bash

它只是存储库中的一个文件,您可以直接运行它,并带有一组基本的JUnit样式断言。

我已经在几个内部项目中专业地使用它,并且能够使我们的 Bash 脚本超级稳定和抗回归。

于 2019-07-06T15:56:24.567 回答
0

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

于 2018-09-28T19:27:46.130 回答
0

You might want to take a look at bash_unit:

https://github.com/pgrange/bash_unit

于 2016-04-04T09:30:00.060 回答
0

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

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

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

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

mytest.sh > actual/mytest.stdout

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

diff expected/mytest.stdout actual/mytest.stdout
于 2021-02-18T01:38:03.040 回答