我正在尝试使用bats在我正在处理的项目中测试一些关键的 shell 脚本。我希望能够模拟脚本,以断言脚本在给定情况下使用正确的参数调用另一个脚本。bats -mock库似乎应该可以解决问题,但根本没有记录。
我试过查看bats-mock 代码和其他人创建的几个测试帮助脚本(比如这个),但不幸的是,我对 bash 不太满意,无法推断如何正确使用 bats-mock 库.
如何使用 bats-mock 库来模拟脚本并断言对模拟的调用?
我正在尝试使用bats在我正在处理的项目中测试一些关键的 shell 脚本。我希望能够模拟脚本,以断言脚本在给定情况下使用正确的参数调用另一个脚本。bats -mock库似乎应该可以解决问题,但根本没有记录。
我试过查看bats-mock 代码和其他人创建的几个测试帮助脚本(比如这个),但不幸的是,我对 bash 不太满意,无法推断如何正确使用 bats-mock 库.
如何使用 bats-mock 库来模拟脚本并断言对模拟的调用?
简要建议:
有一个更新的更积极的开发者 bats-mock,它使用了一种稍微不同的方法,值得探索。https://github.com/grayhemp/bats-mock
我稍后会回来......更多。
回来更多:
它们之间的主要区别在于它们实现了哪种“测试替身”风格。Martin Fowler 在他的文章mocksArentStubs中引用了一本涵盖许多测试策略的书,简要解释了一些样式
Meszaros 使用术语 Test Double 作为通用术语,用于代替真实对象用于测试目的的任何类型的假装对象。这个名字来源于电影中特技替身的概念。(他的目标之一是避免使用任何已经被广泛使用的名称。)Meszaros 然后定义了四种特殊类型的双精度:
- 虚拟对象被传递但从未实际使用过。通常它们仅用于填充参数列表。
- 假对象实际上有工作实现,但通常采取一些捷径,这使得它们不适合生产(内存数据库就是一个很好的例子)。
- 存根为测试期间拨打的电话提供预设答案,通常根本不响应任何超出测试程序的内容。
- 间谍是存根,它还根据调用方式记录一些信息。其中一种形式可能是电子邮件服务,它记录发送了多少消息。
- 模拟就是我们在这里讨论的内容:预先编程的对象具有期望,这些期望形成了它们期望接收的调用的规范。
在这些类型的替身中,只有模拟坚持行为验证。其他双打可以并且通常会使用状态验证。在练习阶段,模拟实际上确实像其他替身一样,因为他们需要让 SUT 相信它正在与其真正的合作者交谈 - 但模拟在设置和验证阶段有所不同。
JasonKarns似乎主要是围绕启用存根而设计的N行假数据。
Grayhemp的版本允许您创建一个 spy 对象和它应该产生的输出以及它的返回代码和运行模拟时应该触发的任何“副作用”(如下面示例中的 PID)。然后,您可以运行一个脚本或命令来调用模拟正在隐藏的命令,并查看它被调用了多少次,脚本的返回码是什么,以及调用模拟命令时存在的环境。总的来说,它似乎更容易断言脚本/二进制文件被调用了什么以及被调用了多少次。
顺便说一句,如果您太浪费时间想知道jasonkarns示例中$)*#@$
的代码是get_timestamp
什么样的,或者是什么,这是我最好的猜测,基于这个其他答案中的示例,以获取自纪元以来的时间(以毫秒为单位),https:/ /serverfault.com/a/588705/266525:${_DATE_ARGS}
您可以将其复制并粘贴到 Bash/POSIX shell 中,以查看第一个输出与第一个存根数据行将提供的内容匹配,第二个输出与bats-mock示例中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
来自jasonkarns/bats-mock文档的示例,注意左侧是:
存根匹配所需的传入参数,如果您使用不同的参数调用日期,它可能会通过并命中真实的东西,但我还没有测试过这个因为我已经花了太多时间来弄清楚原始功能,以便更好地与其他 bats-mock 实现进行比较。
# 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
}
grayhemp/bats-mock的自述文件中的示例,注意酷mock_set-*
和mock_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 "$$" ]]
}
要获得与jasonkarns 版本非常相似的行为,您需要在调用函数之前自己将存根(也称为 ${mock} 的符号链接)注入到 PATH 中。如果您在setup()
每个测试都发生的方法中执行此操作,这可能不是您想要的,并且您还需要确保删除 中的符号链接teardown()
,否则您可以在测试和清理中进行存根测试结束(类似于jasonkarns版本的 stub/unstub),但如果你经常这样做,你会想让它成为测试助手(基本上是在 grayhemp/bats-mock 中重新实现jasonkarns/ bats-mockstub
) 并将帮助程序保留在您的测试中,以便您可以加载或获取它并在许多测试中重用这些函数。或者,您可以向grayhemp/bats-mock提交 PR 以包含存根功能(DigitalOcean Hacktoberfest 名声和臭名昭著的竞赛正在进行中,别忘了也涉及到赃物!)。
@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}:/}"
}
如果有人需要更多说明或想要有一个工作存储库,请告诉我,我可以推送我的代码。