我们有一个系统,除了 Java 代码之外还运行一些 Bash 脚本。由于我们正在尝试测试所有可能会破坏的东西,而那些 Bash 脚本可能会破坏,我们想要测试它们。
问题是很难测试 Bash 脚本。
有没有办法或最佳实践来测试 Bash 脚本?还是我们应该放弃使用 Bash 脚本并寻找可测试的替代解决方案?
我们有一个系统,除了 Java 代码之外还运行一些 Bash 脚本。由于我们正在尝试测试所有可能会破坏的东西,而那些 Bash 脚本可能会破坏,我们想要测试它们。
问题是很难测试 Bash 脚本。
有没有办法或最佳实践来测试 Bash 脚本?还是我们应该放弃使用 Bash 脚本并寻找可测试的替代解决方案?
实际上有一个shunit2,一个基于 xUnit 的单元测试框架,用于基于 Bourne 的 shell 脚本。我自己没有使用过,但可能值得一试。
之前也有人问过类似的问题:
符合 TAP的 Bash 测试:Bash 自动化测试系统
TAP,即测试任何协议,是测试工具中测试模块之间基于文本的简单接口。TAP 最初是作为 Perl 测试工具的一部分,但现在已在 C、C++、Python、PHP、Perl、Java、JavaScript 等中实现。
我从一个讨论组得到以下答案:
可以从外部文件导入(包括,随便)一个过程(函数,无论它的名字是什么)。这是编写测试脚本的关键:将脚本分解为独立的程序,然后可以将它们导入到运行脚本和测试脚本中,然后让运行脚本尽可能简单。
这种方法类似于脚本的依赖注入,听起来很合理。避免使用 Bash 脚本并使用更可测试且不那么晦涩的语言是可取的。
Nikita Sobolev 写了一篇优秀的博客文章,比较了几个不同的 Bash 测试框架:测试 Bash 应用程序
对于不耐烦的人:Nikita 的结论是使用Bats,但似乎 Nikita 错过了Bats-core项目,在我看来,这是一个可以使用的项目,因为原始的 Bats 项目自 2013 年以来一直没有得到积极维护。
我不敢相信没有人谈论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
。对我来说,这个框架以最少的开销提供了最大的收益。
为什么说测试 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
}
试试assert.sh :
source "./assert.sh"
local expected actual
expected="Hello"
actual="World!"
assert_eq "$expected" "$actual" "not equivalent!"
# => x Hello == World :: not equivalent!
我非常喜欢shell2junit ,这是一个从 Bash 脚本测试生成类似JUnit的输出的实用程序。这很有用,因为生成的报告可以被持续集成系统读取,例如Jenkins和Bamboo的 JUnit 插件。
虽然 shell2junit 没有像shunit2那样提供全面的 Bash 脚本框架,但它确实允许您很好地报告测试结果。
尝试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
也许这可以使用,或有助于:
https://thorsteinssonh.github.io/bash_test_tools/
它的目的是在 TAP 协议中编写结果,我认为这对CI有好处,对那些想要 shell 环境的人也有好处。我想有些东西在 shell 环境中运行,所以有些人可能会认为应该在他们的 shell 环境中进行测试。
我已经尝试了很多这里介绍的解决方案,但我发现它们中的大多数都太笨重且难以使用,所以我构建了自己的小测试框架:https ://github.com/meonlol/t-bash
它只是存储库中的一个文件,您可以直接运行它,并带有一组基本的JUnit样式断言。
我已经在几个内部项目中专业地使用它,并且能够使我们的 Bash 脚本超级稳定和抗回归。
看看Outthentic。它很简单,可通过多种语言(可选择的Perl、Python、Ruby和 Bash)和跨平台(Linux 和 Windows)框架进行扩展,以测试任何命令行应用程序。
You might want to take a look at bash_unit:
创建一个测试mytest.sh
。它使用特定的输入调用您的脚本。
创建一个文本文件expected/mytest.stdout
。它包含测试的预期输出。
将它们提交给版本控制(如果有)。
运行测试并将输出重定向到另一个文件:
mytest.sh > actual/mytest.stdout
然后只是我们一个文本差异工具来查看结果偏离的地方。我想你可以做
diff expected/mytest.stdout actual/mytest.stdout