如何编写可测试的bash shell代码?

时间:2019-02-05 07:06:41

标签: bash shell unit-testing mocking bats-core

在OOP大学中,有很多information关于如何设计和重构代码以使其对单元测试友好的。但是我不知道如何将这些原理/实践应用/转换(使mocking更容易等)到shell脚本编写,这显然是不同的编程。

我必须处理非常庞大的代码库;许多可执行和不可执行的过程,大型函数,较大的全局状态,许多环境变量以及通过重定向/管道和(不必要)使用外部实用程序进行的各处(不必要的)进程间通信和文件处理。

如何重构shell代码(或在开始时进行设计),以便能够使用蝙蝠和模拟插件等框架进行“良好的”自动化单元测试?

3 个答案:

答案 0 :(得分:1)

好问题!

恕我直言,shell脚本通常只是调用其他程序来完成工作,例如cpmvtarrsync,...甚至对于bash正在使用表达式的表达式二进制test(如果使用[和](例如if [ -f $file ]; then; fi)。

记住这一点,请考虑一下真正仅在bash脚本中发生的事情:用三个参数调用该程序。因此,您可以编写一个单元测试,以检查bash脚本是否调用所需的程序并使用正确的参数,并检查程序的返回值/退出代码。

您绝对不希望将这些内容放入用于shell脚本的单元测试中,而这实际上是由另一个程序来完成的(例如,检查rsync是否确实将文件从机器A复制到机器B)。

只有我的两分钱

答案 1 :(得分:1)

单元测试用于隔离代码中的发现错误。但是,典型的shell代码主要是与其他可执行文件或操作系统的交互。 Shell代码交互中存在的问题类型沿以下方向:我是否以正确的顺序用正确格式的参数值以正确的顺序调用正确的可执行文件,并且是以我期望它们的形式输出要测试所有这些,您不应该应用单元测试,而应该进行集成测试。

但是,有一些适合单元测试的shell代码。例如,这是在外壳内执行计算或执行字符串操作的代码。我什至会认为调用某些基本工具(例如basename)的shell代码适合进行单元测试(如果愿意,可以将这些工具解释为“标准库”的一部分)。

如何在外壳中使那些适合进行单元测试的代码部分实际可以通过单元测试进行测试?根据我的经验,最有用的方法之一是将交互与计算分开。即,尝试将计算部分放在要测试的单独的外壳函数中,或提取交互作用占主导地位的部分在单独的外壳函数中。这样可以节省大量的模拟工作。

答案 2 :(得分:0)

TL; DR

这里是模板存储库*,它具有使用Travis-CI进行的Shell文件的持续集成单元测试:https://github.com/a-t-0/shell_unit_testing_template

由于回购可能有一天会消失,因此这是可重复性的想法。 (请注意,这不一定是执行此操作的最佳方法,这只是我发现正在工作的一种方法。)

文件结构

Shell脚本位于/src/文件夹中。单元测试位于/test/文件夹中。在/src/中,有一个main.sh可以调用其他shell脚本。其他shell脚本可以包含可单独测试的功能,例如文件active_function_string_manipulation.sh。 (包括在下面)

要使其工作,我需要安装对bats文件的支持,这些文件是单元测试文件。这是通过包含内容的文件install-bats-libs.sh完成的:

mkdir -p test/libs

git submodule add https://github.com/sstephenson/bats test/libs/bats
git submodule add https://github.com/ztombol/bats-support test/libs/bats-support
git submodule add https://github.com/ztombol/bats-assert test/libs/bats-assert

Shell脚本

/src/ is: active_function_string_manipulation.sh`中的shell脚本示例。


##################################################################
# Purpose: Converts a string to lower case
# Arguments:
#   $@ -> String to convert to lower case
##################################################################
function to_lower() 
{
    local str="$@"
    local output
    output=$(tr '[A-Z]' '[a-z]'<<<"${str}")
    echo $output
}
to_lower "$@"

单元测试

单元测试由存储库根目录中名为test.sh的文件运行。内容:

# Run this file to run all the tests, once
./test/libs/bats/bin/bats test/*.bats

一个示例是对active_function_string_manipulation.sh/test/test_active_function_string_manipulation.bats进行测试:

#!./test/libs/bats/bin/bats

load 'libs/bats-support/load'
load 'libs/bats-assert/load'

@test "running the file in /src/active_function_string_manipulation.sh." {
    input="This Is a TEST"
    run ./src/active_function_string_manipulation.sh "This Is a TEST"
    assert_output "this is a test"
}

Travis CI

Travis CI是使用yml文件实现的,该文件基本上会创建环境并在自动化环境中运行测试。该文件名为:.travis.yml,包含:

language: bash

script:
    - ./test.sh

披露*

我参与了this repository的构建,它是指令in this article的“傻瓜/我”实现。

注意

我目前对该系统的扩展程度还没有足够的了解,并且我目前无法估计该系统是否有可能成为“生产就绪”系统,或者它是否适合此类大型项目,仅仅是外壳代码的自动化单元测试环境。