Shell 脚本单元测试:如何模拟复杂的实用程序



我正在对一些遗留的shell脚本进行单元测试。

在现实世界中,脚本通常用于调用实用程序像findtarcpiogrepsedrsyncdate等,还有一些相当复杂的命令行包含很多选项。 有时构造和使用正则表达式或通配符模式。

一个例子:通常由 cron 定期调用的 shell 脚本的任务是使用实用程序 rsync 将一些巨大的目录树从一台计算机镜像到另一台计算机。应从镜像过程:

  #!/usr/bin/env bash
  ...
  function mirror() {
      ...
      COMMAND="rsync -aH$VERBOSE$DRY $PROGRESS $DELETE $OTHER_OPTIONS 
                   $EXCLUDE_OPTIONS $SOURCE_HOST:$DIRECTORY $TARGET"
      ...
      if eval $COMMAND
      then ...
      else ...
      fi
      ...
  }
  ...

正如Michael Feathers在他著名的著作《Working Effective with Legacy Code》中所写的那样,一个好的单元测试运行得非常快,不会触及网络、文件系统或打开任何数据库。

按照Michael Feathers的建议,这里使用的技术是:依赖注入。 这里要替换的对象是实用程序rsync

我的第一个想法:在我的 shell 脚本测试框架(我使用 bats(中,$PATH我以一种找到模型rsync而不是真正的rsync效用。 此模型对象可以检查提供的命令行参数和选项。 与所测试脚本的这一部分中使用的其他实用程序类似。

我过去在脚本编写领域遇到实际问题的经验通常是由文件或目录名称中的特殊字符、引用或编码问题、缺少 ssh 键、错误权限等引起的错误。 这些类型的错误会逃脱这种单元测试技术。(我知道:对于其中一些问题,单元测试根本无法治愈(。

另一个缺点是,为rsyncfind等复杂实用程序编写模型容易出错,并且本身就是一项繁琐的工程任务。

我相信上面描述的情况足够普遍,其他人可能也遇到过类似的问题。谁有一些聪明的想法,并愿意在这里与我分享?

您可以使用函数模拟任何命令,如下所示:

function rsync() {
    # mock things here if necessary
}

然后导出函数并运行单元测试:

export -f rsync
unittest

嘉吉的窘境:

"任何设计问题都可以通过添加额外的间接级别来解决,除了太多的间接级别。

为什么要模拟系统命令?毕竟,如果你正在编写Bash,系统是你的目标,你应该使用系统评估你的脚本。

单元测试,顾名思义,将使您对正在设计的系统的单一部分充满信心。因此,在 bash 脚本的情况下,您必须定义您的单位是什么。函数 ?脚本文件 ?命令 ?

鉴于您想将单位定义为函数,我建议您编写上面列出的已知错误列表:

  • 文件名或目录名中的特殊字符
  • 引用或编码问题
  • 缺少 ssh 密钥
  • 权限错误等。

并为其编写一个测试用例。并尽量不要偏离系统命令,因为它们是您交付的系统不可或缺的一部分。

最新更新