当前位置: 首页 > Linux

有了这个神技,脚本调试从此告别echo、set、test

时间:2023-04-06 04:33:22 Linux

为什么要为Bash脚本写单元测试?由于Bash脚本通常会执行一些与操作系统相关的操作,因此可能会对操作环境造成一些不可逆的操作,例如修改或删除文件、升级系统中的软件包等。因此,为了保证Bash脚本的安全性和可靠性,在将其部署到生产环境之前必须进行充分的测试,以确保其行为符合我们的预期。如何安全可靠地测试Bash脚本?可能有人会说我们可以使用Docker容器。是的,这样做既安全又方便。在容器隔离的环境下,不用担心脚本破坏我们的系统,重建可用的测试环境也非常简单快捷。但是,请考虑以下常见场景:场景一:在执行Bash脚本测试之前,我们需要提前安装好所有Bash脚本中会用到的第三方工具,否则这些测试会因为命令而被发现执行失败。例如,我们在脚本中使用Bazel构建工具。我们必须提前安装并配置好Bazel,同时不要忘记为了正常使用Bazel,我们需要一个支持使用Bazel构建的工程。场景二:测试结果的稳定性可能取决于脚本中访问的第三方服务的稳定性。比如我们在脚本中使用curl命令从一个网络服务中获取数据,但是这个服务有时会访问不上。可能是网络不稳定造成的,也可能是服务本身不稳定。或者如果我们为了测试脚本不同的分支逻辑,需要第三方服务返回不同的数据,我们可能很难去修改这个第三方服务的数据。场景三:Bash脚本的测试用例的执行时间取决于脚本中使用的命令的执行时间。比如我们在脚本中使用Gradle构建一个项目,由于项目大小不同,一次Gradle的构建可能需要3分钟或者3个小时。这只是一个测试用例,如果我们有20个或100个测试用例怎么办?也能秒出检测报告吗?即使使用容器来进行Bash脚本测试,也无法避免上述问题。环境的准备过程可能会随着测试用例的增加而变得繁琐。测试用例的稳定性和执行时间取决于第三方命令和服务的稳定性和执行时间,可能很难用不同的数据覆盖不同的测试场景。对于测试Bash脚本,我们真正要验证的是Bash脚本的执行逻辑。例如,在Bash脚本中,内部调用的命令的选项和参数可能会根据传入的参数进行组合,我们需要验证的是这些选项和参数确实符合预期。至于为什么在接受这些选项和参数后调用的命令失败,也许我们并不关心所有可能的原因。因为会有更多的外部因素,比如硬件和网络是否正常工作,第三方服务是否正常运行,构建项目所需的编译器是否安装和配置正确,授权和认证信息是否正确。有效等。但是对于Bash脚本来说,这些外部原因的结果就是被调用的命令执行成功或者失败。所以Bash脚本只需要关注脚本中调用的命令是否能成功执行,命令输出什么,决定后续执行脚本中的哪些不同的分支逻辑。如果我们只想知道是否此命令是否按我们预期的方式使用这些选项?很简单,单独在命令行执行即可。如果它在命令行上没有按预期工作,它也不会在Bash脚本中按预期工作。这种错误几乎与Bash脚本无关了。因此,为了尽可能去除那些影响Bash脚本验证的外部因素,我们应该考虑为Bash脚本编写单元测试,重点关注Bash脚本的执行逻辑。Bash脚本的单元测试是什么样的测试?首先,不应在单元测试中执行PATH环境变量中存在的所有命令。对于Bash脚本,调用的命令可以正常运行,有返回值,有输出。但是,脚本中调用的这些命令都是模拟的,以模拟对应的真实命令的行为。这样我们在Bash脚本的单元测试中就避免了很大一部分外部依赖,测试的执行速度不会受到真实命令的影响。其次,每个单元测试用例应该相互独立。这意味着这些测试用例可以独立执行或任意乱序执行,而不会影响验证结果。最后,这些测试用例可以在不同的操作系统上执行,并且都应该得到相同的验证结果。例如,在Bash脚本中使用了只能在GNU/Linux上可用的命令,在Windows或macOS上也可以执行相应的单元测试,结果是一致的。如何为Bash脚本编写单元测试?与其他编程语言一样,Bash也有多种测试框架,如Bats、Shunit2等,但这些框架实际上并不能隔离PATH环境变量中的所有命令。有一个名为BachTestingFramework的测试框架,它是目前唯一可以为Bash脚本编写真正单元测试的框架。BachTestingFramework最独特的特点是它默认不执行任何位于PATH环境变量中的命令,因此BachTestingFramework非常适合验证Bash脚本的执行逻辑。并且它还带来了以下好处:简单且无需安装。我们可以执行这些测试。例如,调用大量第三方命令的Bash脚本可以在全新的环境中执行。快速因为不会真正执行所有的命令,所以每个测试用例的执行速度都非常快。安全,因为它不会执行任何外部命令,所以即使Bash脚本中的某些错误导致执行危险命令,例如rm-rf*。巴赫将确保这些危险命令不会被执行。无论运行环境如何,只能在GNU/Linux上运行的脚本测试都可以在Windows上执行。由于操作系统和Bash的一些限制,BachTestingFramework无法做到:拦截使用绝对路径调用的命令实际上,我们应该避免在Bash脚本中使用绝对路径。如果实在无法避免,我们可以将这个绝对路径抽取出来作为一个变量,或者放到一个函数中,然后使用@mockAPI来模拟这个函数。拦截>、>>、<<等I/O重定向。是的,I/O重定向是无法拦截的。我们也可以把这些重定向操作隔离成一个函数,然后模拟这个函数。Bach测试框架使用Bach测试框架需要Bashv4.3或更高版本。GNU/Linux也需要Coreutils和Diffutils,它们已经默认安装在常用的发行版中。Bach已通过Linux/macOS/Cygwin/GitBash/FreeBSD等操作系统或运行环境的验证。bashv4.3+Coreutils(GNU/Linux)Diffutils(GNU/Linux)安装Bach测试框架Bach测试框架安装非常简单,只需下载https://github.com/bach-sh/ba...到你的In项目,在测试脚本中使用source命令导入Bach测试框架的bach.sh。例如:sourcepath/to/bach.sh一个简单的例子不同于其他测试框架,BachTestingFramework的每个测试用例由两个Bash函数组成,一个是以test-开头的测试执行函数,另一个是一个是以-assert结尾的同名测试验证函数。例如,在下面的例子中,有两个测试用例,分别是–test-rm-rf–test-rm-your-dot-git一个完整的测试用例:#!/usr/bin/envbashset-euopipefailsourcebach。sh#导入Bach测试框架test-rm-rf(){#Bach的标准测试用例由两个方法组成#-test-rm-rf#-test-rm-rf-assert#这个方法`test-rm-rf`是测试用例的执行project_log_path=/tmp/project/logssudorm-rf"$project_log_ptah/"#注意,这里有错别字!}test-rm-rf-assert(){#这个方法`test-rm-rf-assert`是对测试用例的验证sudorm-rf/#这是真正要执行的命令#不要惊慌!使用Bach测试框架不会使此命令真正起作用!}test-rm-your-dot-git(){#模拟`find`命令查找主目录下的所有`.git`目录,假设会找到两个目录@mockfind~-typed-name.git===@stdout~/src/your-awesome-project/.git~/src/code/.git#开始执行!删除主目录中的所有`.git`目录!找到~-typed-name.git|xargs--rm-rf}test-rm-your-dot-git-assert(){#在`test-rm-your-dot-git`测试执行方法中验证最后会不会执行下面的命令。rm-rf~/src/your-awesome-project/.git~/src/code/.git}bach会分别运行每个测试用例的两个方法,验证两个方法中执行的命令和参数是否一致.比如第一个方法test-rm-rf就是执行Bach的测试用例,对应的测试验证方法是test-rm-rf-assert第二个测试用例test-rm-your-dot-中使用了这个方法@mockAPI在git中用于模拟命令find~typed-name.git的行为,用于查找用户目录下的所有.git目录。模拟之后,这条命令并不会真正执行,而是会使用@stdoutAPI在标准终端上输出两个虚拟目录名。然后我们可以执行真正的命令,将find命令的输出传递给xargs命令,并在rm-rf命令之后合并。在对应的测试验证函数test-rm-your-dot-git-assert中,验证find的结果是否为~-typed-name.git|xargs--rm-rf相当于命令rm-rf~/src/your-awesome-project/.git~/src/code/.git@mock是Bach测试框架中非常重要的API。使用此API,我们可以模拟Bash脚本中使用的任何命令的行为或输出。例如@mockcurl--silentgoogle.com===@stdout"baidu.com"模拟命令curl--silentgoogle.com的执行结果输出baidu.com。在真实的正常场景下,我们无法访问google.com和获取baidu.com。此模拟可用于验证Bash脚本在处??理对命令的不同响应时的行为。@mockAPI甚至支持更复杂的行为模拟,我们可以自定义一个复杂的模拟逻辑,比如:@mockls<