跳转到主要内容

保存的帖子

单元测试Shell脚本:
第二部分

Dave Nicolette |龙头
戴夫尼科尔特
阅读: 单元测试Shell脚本:第二部分

单元测试

系列的第一篇文章,我们编写了一个测试脚本来验证功能一个示例脚本Vivek乡村度假别墅。我们看到遵循单元检查的常规结构并不困难,arrange-act-assert,使用shell语言。我们还看到,伪造或“模拟”外部依赖关系,并使用标准条件语句断言函数或整个脚本的结果是非常简单的。我们还考虑了使用单元测试框架的好处,比如一致性、可重用性、简化性和可理解性。

在这一部分中,我们将使用shunit2为我们在第一期中使用的样例脚本编写单元检查,我们将解释为什么两者都没有蝙蝠也不Zunit.能够支持样品用例。

要重新标准,这是我们提醒的测试脚本来检查磁盘的功能.sh:

#!/ bin / bash shopt -s expand_aliases#之前所有别名邮件=“回声”邮件'> mailsent; false“echo”磁盘的测试结果。sh'> test_results tcnt = 0#当磁盘使用率低于90时%#前(安排)别名df =“回声”文件系统尺寸使用的可用百分比安装在'; echo'/ dev / sda2 100g 89.0g 11.0g 89%/'“echo”没有邮件'>邮件#运行代码(行为) 。./diskusage.sh#检查结果(senster)((tcnt = tcnt + 1))如果[$(> test_results else echo“$ tcnt。通过:在90%以下的磁盘使用情况下没有措施”>> test_results fi#它发送电子邮件通知当磁盘使用率为90%alias df =“echo”文件系统尺寸使用的可用性使用%安装在'; echo'/ dev / sda1 100g 90.0g 10.0g 90%/'“echo'没有邮件'> mailsent。./diskusage.sh((tcnt = tcnt + 1))如果[$(> test_results else echo“$ tcnt。失败:磁盘使用率为90%但未发送通知”>> test_results fi#all unealias df unalias之后。邮件#显示测试结果cat test_results

使用shunit2测试diskusage.sh

让我们看看使用shunit2我们的测试脚本会是什么样子。shunit2的主要优点是:

  • 支持多种shell语言和平台
  • 基于“Assertthat”模型的清洁断言语法
  • 支持跳过测试用例
  • 支持跳过断言
  • 支持模仿文件(但不支持命令)

在单个测试用例中跳过断言和失败调用的能力不是应用程序语言单元测试框架的特征。它在测试shell脚本时很有帮助,因为许多脚本都不是模块化的;它们按顺序执行大量的步骤,您不能出于隔离测试的目的而选择性地执行脚本的某些部分。

有选择地跳过特定断言的能力为您提供了一种机制,可以探索此类脚本的可能行为,或者在出现问题时对其进行故障排除,而不必将其分解以获得特定的功能。当您必须分解一个脚本来测试它的各个部分时,有一个风险,即当这些部分被重新组合到一个单一的脚本中时,行为将会不同。

shunit2的函数相当于测试脚本中的设置和删除步骤:

  • 之前所有=> oneTimesetup
  • 毕竟=> oneTimeteardown
  • = >之前设置
  • =>拆解后

第一个要做的是“安装”shunit2。“安装”是shell脚本的一个非常花哨的词。我们将从中下载这个项目维基,它提供了几个Shunit2版本。在这篇文章写的时候,最新的稳定版本为2.1.7,可从这个下载页面

shunit2显示测试结果,所以我们不需要脚本末尾的代码来显示测试输出,注释是“显示测试结果”。我们将删除。

现在,让我们使用这些设置和分解函数。我们将用一个使用保留名称的函数定义替换注释“Before all”下的代码oneTimesetup.,Shunit2将在运行时识别。我们不需要编号我们的测试案例,因为Shunit2将为我们照顾这一点。这让我们留下了这一点:

oneTimeSetUp() {} alias mail="echo 'mail' > mailsent;false"}

对“After all”代码做同样的操作,我们有:

oneTimeTearDown() {unalias df unalias mail}

我们必须在测试脚本中包含shunit2的源代码。语句位于测试脚本的末尾。

。。/ shunit2

现在,让我们用Shunit2断言替换我们的硬编码条件逻辑。为此,我们必须将每个测试用例提交到名称以“测试”开头的函数。我们最终结束了:

#!/ bin / bash shopt -s expand_aliases test_itdoesnotsendnotification_whenusageisbelowthreshold(){alias df =“echo'文件系统大小使用可用的百分比上安装在'; echo'/ dev / sda2 100g 89.0g 11.0g 89%/'echo'没有邮件'>邮件。./diskusage.sh aserttrue“当磁盘使用率低于90%”\'[$(邮件。./diskusage.sh aserttrue“它应该在磁盘使用情况下或高于90%”\'以上时发送通知[[$( mailsent; false“} oneTimeteardown(){unalias df unalias mail}。./ Shunit2.

将其与原始测试脚本进行比较,您可以看到几个优点。首先,代码总体更简单,更易于阅读。我们没有硬编码的逻辑来给出测试用例唯一数字,并且我们不需要忙碌的条件语句来检查每种情况的结果。Shunit2函数的命名约定有助于阐明代码的意图并确保一致性。

个人测试案例函数的名称反映了一些喜欢使用的应用程序开发人员。测试框架需要字符串“测试”。之后,函数名称的下一段代表我们预期的代码正在进行的代码,并且以下段以人类可读方式总结了前提条件。

这种命名约定并不是单元测试的硬性要求。任何合理且一致的命名约定都可以。要避免的是随机或随意的名称,因为这将使理解测试套件变得更加困难。

没有蝙蝠或zunit运气

Shell脚本广泛用于系统管理任务和系统配置。这种现实的自然结果是,许多生产shell脚本(A)依赖于运行系统的当前状态,和/或(b)通过安装包和/或更改配置设置来更改目标系统的状态。

要在单位级别测试此类脚本,以与单位测试应用程序代码相同的方式,我们需要一种方法来定义“假”或“模拟”系统命令。要测试我们的示例脚本,DiskUsage.sh,我们使用别名来替换具有假的real system命令。在我们的示例中,我们以这种方式模拟“DF”和“邮件”命令。

BATS和zunit的实现方式使我们无法支持这些别名的“mock”命令。以回答用户的问题2016年12月Suewon Bahng和Michael Diamond(分别)使用BATS提出了不同的解决方案,它们都不支持我们的示例用例。

简而言之,问题在于,即使指定了' shopt -s expand_aliases ',这些工具似乎也会“吞下”别名定义。工作区不可见到正在测试的代码,但只对测试脚本本身。因此,这些方法不能将虚假的系统命令输出注入到测试代码中以提供隔离。

作为一家实际的替代品存在,我们决定不在蝙蝠或Zunit中投入更多时间,并继续前进。如果将来解决此问题,我们将很乐意重新审视这些工具。

接下来是什么?

到目前为止,我们已经看到了可以手卷shell脚本的单元检查,并且看到了使用单元测试框架的一些好处。我们已经了解到,shunit2是实现这一目的的可靠选择,而且我们还发现了一些其他框架的局限性,这些框架在企业环境中可能不太有用,即使它们在其他情况下可以很好地工作。

在第三部分中,我们将看看我的两个副项目,bash-spec和korn-spec。这些框架采用“行为”方法。这意味着断言采用“期望alpha匹配beta”的形式,而不是更传统的形式,“断言等于alpha, beta”或“断言beta等于alpha”。他们还试图使用“流动”调用风格,试图使测试用例更适合人类。

仍然来:纠缠(对于PowerShell),厨师(厨师)和Rspec-Puppet(用于傀儡)。

下一个>现场:Q&A环节与麦克科特梅尔vol. 3

Dave Nicolette自1977年以来一直是一名IT专业人士。他曾担任过各种技术和管理职务。自1984年以来,他主要从事顾问工作,一只脚在技术阵营,一只脚在管理阵营。

发表评论

您的电子邮件地址将不会被公布。必填字段被标记*