我正在尝试使用bats来测试我正在处理的一个项目中的一些关键shell脚本。我希望能够模拟脚本,以便断言一个脚本在给定的情况下使用正确的参数调用另一个脚本。bats-mock库看起来应该可以做到这一点,但是它根本没有文档。
我尝试过查看bats-mock code和其他人创建的几个测试助手脚本(如this one),但不幸的是,我对bash不太熟悉,无法推断如何正确使用bats-mock库。
如何使用bats-mock库模拟脚本并针对对mock的调用进行断言?
发布于 2018-09-28 07:47:23
简要建议:
有一个更新的、更积极的开发者bats-mock,它使用了一种稍微不同的方法,可能值得探索。https://github.com/grayhemp/bats-mock
我一会儿就回来,with.....MORE。
下面是更多内容:
它们之间的主要区别在于它们实现了哪种“test double”风格。每个Martin Fowler在他的文章mocksArentStubs中引用了一本涵盖许多测试策略的书,简要解释了一些风格
Meszaros使用术语Test Double作为任何种类的伪装对象的通用术语,用于代替真实对象进行测试。这个名字来源于电影中特技替身的概念。(他的目标之一是避免使用任何已经被广泛使用的名字。)Meszaros随后定义了四种特殊类型的double:
Spie伪对象实际上有工作实现,但通常会采取一些捷径,这使得它们不适合生产(内存中的数据库是一个很好的example).
在这些替身中,只有mock坚持行为验证。其他替身可以使用状态验证,通常也是这样做的。在练习阶段,模拟确实像其他替身一样,因为它们需要让SUT相信它是在与真正的合作者交谈-但模拟在设置和验证阶段有所不同。
JasonKarns似乎主要是围绕启用存根设计的,它返回对脚本或二进制文件的N次调用的虚拟数据,并保留对存根的调用次数的内部计数,如果调用与N行假数据不匹配,则返回错误代码。
Grayhemp的版本允许你创建一个间谍对象和它应该产生的输出,以及它的返回代码和在模拟运行时应该触发的任何“副作用”(就像下面例子中的PID )。然后你可以运行一个脚本或命令,调用模拟正在隐藏的命令,并查看它被调用了多少次,脚本的返回代码是什么,以及当模拟命令被调用时的环境。总的来说,它似乎更容易断言脚本/二进制被调用了什么以及它被调用了多少次。
顺便说一句,如果你想知道jasonkarns示例中get_timestamp
的代码在$)*#@$
中是什么样子,或者${_DATE_ARGS}
是什么,如果你也浪费了几个小时,那么我最好的猜测是基于另一个答案中的示例,以获取自纪元以来的时间(以毫秒为单位),https://serverfault.com/a/588705/266525
您可以将其复制并粘贴到Bash/POSIX shell中,以查看第一个输出是否与第一个存根数据行将提供给get_timestamp
的内容相匹配,第二个输出是否与bats-mock示例中的第一个断言所显示的输出相匹配。
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文档的例子中,注意:
左边是存根匹配所需的传入参数,如果你用不同的参数调用date,它可能会传递并命中真正的东西,但我没有测试这一点,因为我已经花费了太多的时间来弄清楚原始函数,以便更好地与其他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 version非常相似的行为,您需要在调用函数之前将存根(也称为指向${mock}的符号链接)注入到路径中。如果您在setup()
方法中执行此操作,则在每次测试时都会发生此操作,这可能不是您想要的,而且您还希望确保删除teardown()
中的符号链接,否则您可以在测试结束时在测试中执行存根和清除(类似于jasonkarns版本的存根/未存根),但是如果您经常这样做,您将希望将其作为测试助手(基本上是在grayhemp/bats-mock中重新实现jasonkarns/bats-mock的stub
),并将该助手与您的测试一起保留,这样您就可以加载或源化它,并在许多测试中重用这些函数。或者你可以向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}:/}"
}
如果任何人需要更多的澄清,或者想要有一个可用的存储库,请让我知道,我可以将我的代码推上来。
https://stackoverflow.com/questions/38315185
复制相似问题