如何在Bash测试中使用'bats mock'断言对模拟脚本的调用
我正试图在我正在进行的项目中测试一些关键的shell脚本。我希望能够模拟脚本,以便断言在给定情况下,一个脚本调用另一个具有正确参数的脚本。图书馆似乎应该做到这一点,但它根本没有被记录在案 我已经尝试查看了和其他人创建的几个测试助手脚本(如),但不幸的是,我对bash不太熟悉,无法推断如何正确使用bats模拟库 如何使用bats模拟库模拟脚本并对模拟调用进行断言?简要建议: 有一个更新的、更积极的开发人员bats mock,它使用了一种稍微不同的方法,值得探索 稍后我会带着…更多回来如何在Bash测试中使用'bats mock'断言对模拟脚本的调用,bash,unit-testing,mocking,bats-core,Bash,Unit Testing,Mocking,Bats Core,我正试图在我正在进行的项目中测试一些关键的shell脚本。我希望能够模拟脚本,以便断言在给定情况下,一个脚本调用另一个具有正确参数的脚本。图书馆似乎应该做到这一点,但它根本没有被记录在案 我已经尝试查看了和其他人创建的几个测试助手脚本(如),但不幸的是,我对bash不太熟悉,无法推断如何正确使用bats模拟库 如何使用bats模拟库模拟脚本并对模拟调用进行断言?简要建议: 有一个更新的、更积极的开发人员bats mock,它使用了一种稍微不同的方法,值得探索 稍后我会带着…更多回来 返回更多内
返回更多内容: 它们之间的主要区别在于它们实现了哪种“双重测试”样式。Martin Fowler在其文章中引用了一本涵盖许多测试策略的书,简要解释了一些风格 Meszaros使用术语Test Double作为任何类型测试的通用术语 为了测试的目的,用假装对象代替真实对象。 这个名字来源于电影中特技替身的概念。(其中一个 他的目的是避免使用任何已经广泛使用的名称。) 梅萨罗斯随后定义了四种特殊的双重身份:
- 虚拟对象被传递,但从未实际使用过。通常它们只是用来填充参数列表
- 伪对象实际上有工作实现,但通常会采取一些使其不适合生产的快捷方式(例如 内存数据库就是一个很好的例子)
- 存根为测试过程中的呼叫提供了固定的答案,通常对测试程序之外的任何内容都没有响应 测试
- 间谍是一种存根,根据他们被称为间谍的方式记录一些信息。其中的一种形式可能是记录 发送了多少条消息
- 模拟就是我们在这里所讨论的:预先编程的对象,带有期望值,这些期望值形成了它们所属调用的规范 预计将收到
$)*#@$
中的代码在示例中是什么样子的,或者${u DATE\u ARGS}
是什么,下面是我根据另一个答案中的示例得出的关于从纪元开始以毫秒为单位的时间的最佳猜测:
您可以将其复制并粘贴到Bash/POSIX shell中,以查看第一个输出与第一个存根数据行将提供给get_timestamp
的内容相匹配,第二个输出与示例中第一个断言的输出相匹配
get_timestamp () {
# This should really be named get timestamp in milliseconds
# In truth it wouldn't accept input ie the ${1} below,
# but it is easier to show and test how it works with a fixed date (which is why we want to stub!)
GIVEN_DATE="$1"
# Pass in a human readable date and get back the epoch `%s` (seconds since 1-1-1970) and %N nanoseconds
# date +%s.%N -d'Mon Apr 18 03:19:58.184561556 CDT 2016'
EPOCH_NANO=$(date +%s.%N -d"$GIVEN_DATE")
echo "This reflects the data the date stub would return: $EPOCH_NANO"
# Accepts input in seconds.nanoseconds ie %s.%N and
# sets the output format to milliseconds,
# by combining the epoch `%s` (seconds since 1-1-1970) and
# first 3 digits of the nanoseconds with %3N
_DATE_ARGS='+%s%3N -d'
echo $(date ${_DATE_ARGS}"@${EPOCH_NANO}")
}
get_timestamp 'Mon Apr 18 03:19:58.184561556 CDT 2016' # The quotes make it a *single* argument $1 to the function
文档中的示例,左侧的注释:
是存根匹配所需的传入参数,如果您使用不同的参数调用date,它可能会通过并命中真实对象,但是我还没有测试这个,因为我已经花了太多的时间来计算原始函数,以便与其他bats模拟实现进行更好的比较
# In bats you can declare globals outside your tests if you want them to apply
# to all tests in a file, or in a `fixture` or `vars` file and `load`or `source` it
declare -g _DATE_ARGS='+%s.%N -d'
# The interesting thing about the order of the mocked call returns is they are actually moving backwards in time,
# very interesting behavior and possibly needs another test that should throw a really big exception if this is encountered in the real world
# Original example below
@test "get_timestamp" {
stub date \
"${_DATE_ARGS} : echo 1460967598.184561556" \
"${_DATE_ARGS} : echo 1460967598.084561556" \
"${_DATE_ARGS} : echo 1460967598.004561556" \
"${_DATE_ARGS} : echo 1460967598.000561556" \
"${_DATE_ARGS} : echo 1460967598.000061556"
run get_timestamp
assert_success
assert_output 1460967598184
run get_timestamp
assert_success
assert_output 1460967598084
run get_timestamp
assert_success
assert_output 1460967598004
run get_timestamp
assert_success
assert_output 1460967598000
run get_timestamp
assert_success
assert_output 1460967598000
unstub date
}
在的自述文件中的示例中,请注意酷的mock\u集-*
和mock\u get.*
选项
@test "postgres.sh starts Postgres" {
mock="$(mock_create)"
mock_set_side_effect "${mock}" "echo $$ > /tmp/postgres_started"
# Assuming postgres.sh expects the `_POSTGRES` variable to define a
# path to the `postgres` executable
_POSTGRES="${mock}" run postgres.sh
[[ "${status}" -eq 0 ]]
[[ "$(mock_get_call_num ${mock})" -eq 1 ]]
[[ "$(mock_get_call_user ${mock})" = 'postgres' ]]
[[ "$(mock_get_call_args ${mock})" =~ -D\ /var/lib/postgresql ]]
[[ "$(mock_get_call_env ${mock} PGPORT)" -eq 5432 ]]
[[ "$(cat /tmp/postgres_started)" -eq "$$" ]]
}
要获得与之非常相似的行为,您需要在调用函数之前将存根(也称为${mock}的符号链接)注入到路径中。如果在setup()
方法中执行此操作,则每次测试都会发生此操作,这可能不是您想要的,并且您还需要确保在teardown()
中删除符号链接,否则您可以在测试中执行存根操作,并在测试结束时进行清理(类似于存根/取消存根版本),但是如果你经常这样做,你会想让它成为一个测试助手(基本上是重新实现的stub
内部),并把助手和你的测试放在一起,这样你就可以在很多测试中加载或源代码并重用这些函数。或者你也可以提交一份PR来包含存根功能(DigitalOcean Hackertoberfest的名气和耻辱竞赛正在进行中,别忘了还有swag参与其中!)
如果有人需要更多的说明或希望有一个可用的存储库,请告诉我,我可以把我的代码推上去。自述文件中的文档似乎已经更新,因为您发布了这个问题以添加用法和示例,但用法文档似乎仍然不完整。现在我给一些人打电话,请回复。谢谢它真的帮助了我进一步模仿蝙蝠。实际上,我发现
ln-s“${externalApp}”/usr/local/bin/externalApp
对于在容器中进行测试非常有用。只是提醒一下:S
@test "get_timestamp" {
mocked_command="date"
mock="$(mock_create)"
mock_path="${mock%/*}" # Parameter expansion to get the folder portion of the temp mock's path
mock_file="${mock##*/}" # Parameter expansion to get the filename portion of the temp mock's path
ln -sf "${mock_path}/${mock_file}" "${mock_path}/${mocked_command}"
PATH="${mock_path}:$PATH" # Putting the stub at the beginning of the PATH so it gets picked up first
mock_set_output "${mock}" "1460967598.184561556" 1
mock_set_output "${mock}" "1460967598.084561556" 2
mock_set_output "${mock}" "1460967598.004561556" 3
mock_set_output "${mock}" "1460967598.000561556" 4
mock_set_output "${mock}" "1460967598.000061556" 5
mock_set_status "${mock}" 1 6
run get_timestamp
[[ "${status}" -eq 0 ]]
run get_timestamp
run get_timestamp
run get_timestamp
run get_timestamp
[[ "${status}" -eq 0 ]]
# Status is just of the previous invocation of `run`, so you can test every time or just once
# note that calling the mock more times than you set the output for does NOT change the exit status...
# unless you override it with `mock_set_status "${mock}" 1 6`
# Last bits are the exit code/status and index of call to return the status for
# This is a test to assert that mocked_command stub is in the path and points the right place
[[ "$(readlink -e $(which date))" == "$(readlink -e ${mock})" ]]
# This is a direct call to the stubbed command to show that it returns the `mock_set_status` defined code and shows up in the call_num
run ${mocked_command}
[[ "$status" -eq 1 ]]
[[ "$(mock_get_call_num ${mock})" -eq 6 ]]
# Check if your function exported something to the environment, the example get_timestamp function above does NOT
# [[ "$(mock_get_call_env ${mock} _DATE_ARGS 1)" -eq '~%s%3N' ]]
# Use the below line if you actually want to see all the arguments the function used to call the `date` 'stub'
# echo "# call_args: " $(mock_get_call_args ${mock} 1) >&3
# The actual args don't have the \ but the regex operator =~ treats + specially if it isn't escaped
date_args="\+%s%3N"
[[ "$(mock_get_call_args ${mock} 1)" =~ $date_args ]]
# Cleanup our stub and fixup the PATH
unlink "${mock_path}/${mocked_command}"
PATH="${PATH/${mock_path}:/}"
}