首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何在Bash测试中使用``bats mock`来断言对模拟脚本的调用

如何在Bash测试中使用``bats mock`来断言对模拟脚本的调用
EN

Stack Overflow用户
提问于 2016-07-12 03:58:10
回答 1查看 2.1K关注 0票数 4

我正在尝试使用bats来测试我正在处理的一个项目中的一些关键shell脚本。我希望能够模拟脚本,以便断言一个脚本在给定的情况下使用正确的参数调用另一个脚本。bats-mock库看起来应该可以做到这一点,但是它根本没有文档。

我尝试过查看bats-mock code和其他人创建的几个测试助手脚本(如this one),但不幸的是,我对bash不太熟悉,无法推断如何正确使用bats-mock库。

如何使用bats-mock库模拟脚本并针对对mock的调用进行断言?

EN

回答 1

Stack Overflow用户

发布于 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).

  • Stubs,它为测试期间进行的调用提供封装的应答,通常对测试中编程之外的任何东西都不响应。
  • Spie是存根,它还根据它们的调用方式记录一些信息。其中一种形式可能是电子邮件服务,它记录发送了多少条消息。
  • Mock就是我们在这里讨论的:使用期望预先编程的对象,这些对象形成了期望接收的呼叫的规范。

在这些替身中,只有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示例中的第一个断言所显示的输出相匹配。

代码语言:javascript
运行
复制
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实现进行比较。

代码语言:javascript
运行
复制
# 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_*选项。

代码语言:javascript
运行
复制
@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-mockstub ),并将该助手与您的测试一起保留,这样您就可以加载或源化它,并在许多测试中重用这些函数。或者你可以向grayhemp/bats-mock提交一份PR,以包含存根功能( DigitalOcean Hacktoberfest的名气和恶名之争正在进行中,别忘了还涉及到一些小玩意!)。

代码语言:javascript
运行
复制
@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}:/}"
}

如果任何人需要更多的澄清,或者想要有一个可用的存储库,请让我知道,我可以将我的代码推上来。

票数 11
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38315185

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档